Commit 1184271c467d5fb0fa69285b722c15edebfe2a55
1 parent
5fbb0a50
Exists in
master
and in
1 other branch
Upload de softwares para deploy
Showing
5 changed files
with
281 additions
and
12 deletions
Show diff stats
src/Cacic/CommonBundle/Controller/AgenteController.php
@@ -9,6 +9,7 @@ | @@ -9,6 +9,7 @@ | ||
9 | namespace Cacic\CommonBundle\Controller; | 9 | namespace Cacic\CommonBundle\Controller; |
10 | 10 | ||
11 | use Cacic\CommonBundle\Form\Type\AgenteType; | 11 | use Cacic\CommonBundle\Form\Type\AgenteType; |
12 | +use Cacic\CommonBundle\Form\Type\DeployType; | ||
12 | use Symfony\Component\HttpFoundation\Request; | 13 | use Symfony\Component\HttpFoundation\Request; |
13 | use Symfony\Component\HttpFoundation\Response; | 14 | use Symfony\Component\HttpFoundation\Response; |
14 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; | 15 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; |
@@ -59,6 +60,8 @@ class AgenteController extends Controller { | @@ -59,6 +60,8 @@ class AgenteController extends Controller { | ||
59 | $finder = new Finder(); | 60 | $finder = new Finder(); |
60 | $agentes = new Finder(); | 61 | $agentes = new Finder(); |
61 | $saida = array(); | 62 | $saida = array(); |
63 | + $base_url = $request->getBaseUrl(); | ||
64 | + $base_url = preg_replace('/\/app.*.php/', "/", $base_url); | ||
62 | 65 | ||
63 | // Primeiro tratamos agentes Linux | 66 | // Primeiro tratamos agentes Linux |
64 | // A regra é que o agente mais atual estará na pasta current | 67 | // A regra é que o agente mais atual estará na pasta current |
@@ -73,10 +76,10 @@ class AgenteController extends Controller { | @@ -73,10 +76,10 @@ class AgenteController extends Controller { | ||
73 | foreach ($agentes as $file) { | 76 | foreach ($agentes as $file) { |
74 | array_push($saida['linux']['versions'][$version->getFileName()], array( | 77 | array_push($saida['linux']['versions'][$version->getFileName()], array( |
75 | 'name' => $file->getFileName(), | 78 | 'name' => $file->getFileName(), |
76 | - 'download_url' => 'downloads/linux/windows/' . $version->getFileName() . '/' . $file->getFileName(), | 79 | + 'download_url' => $base_url . 'downloads/cacic/linux/' . $version->getFileName() . '/' . $file->getFileName(), |
77 | 'hash' => md5_file($file->getRealPath()), | 80 | 'hash' => md5_file($file->getRealPath()), |
78 | 'size' => $file->getSize(), | 81 | 'size' => $file->getSize(), |
79 | - 'filename' => $windowsDir . $version->getFileName() . '/' . $file->getFileName() | 82 | + 'filename' => 'cacic/linux/' . $version->getFileName() . '/' . $file->getFileName() |
80 | )); | 83 | )); |
81 | 84 | ||
82 | } | 85 | } |
@@ -97,10 +100,10 @@ class AgenteController extends Controller { | @@ -97,10 +100,10 @@ class AgenteController extends Controller { | ||
97 | foreach ($agentes as $file) { | 100 | foreach ($agentes as $file) { |
98 | array_push($saida['windows']['versions'][$version->getFileName()], array( | 101 | array_push($saida['windows']['versions'][$version->getFileName()], array( |
99 | 'name' => $file->getFileName(), | 102 | 'name' => $file->getFileName(), |
100 | - 'download_url' => 'downloads/cacic/windows/' . $version->getFileName() . '/' . $file->getFileName(), | 103 | + 'download_url' => $base_url . 'downloads/cacic/windows/' . $version->getFileName() . '/' . $file->getFileName(), |
101 | 'hash' => md5_file($file->getRealPath()), | 104 | 'hash' => md5_file($file->getRealPath()), |
102 | 'size' => $file->getSize(), | 105 | 'size' => $file->getSize(), |
103 | - 'filename' => 'windows/' . $version->getFileName() . '/' . $file->getFileName() | 106 | + 'filename' => 'cacic/windows/' . $version->getFileName() . '/' . $file->getFileName() |
104 | )); | 107 | )); |
105 | 108 | ||
106 | } | 109 | } |
@@ -235,8 +238,7 @@ class AgenteController extends Controller { | @@ -235,8 +238,7 @@ class AgenteController extends Controller { | ||
235 | $rootDir = $this->container->get('kernel')->getRootDir(); | 238 | $rootDir = $this->container->get('kernel')->getRootDir(); |
236 | $webDir = $rootDir . "/../web/"; | 239 | $webDir = $rootDir . "/../web/"; |
237 | $downloadsDir = $webDir . "downloads/"; | 240 | $downloadsDir = $webDir . "downloads/"; |
238 | - $cacicDir = $downloadsDir . "cacic/"; | ||
239 | - $filepath = $cacicDir . $request->get('id'); | 241 | + $filepath = $downloadsDir . $request->get('id'); |
240 | 242 | ||
241 | $this->get('logger')->debug("Excluindo arquivo de agente ".$filepath); | 243 | $this->get('logger')->debug("Excluindo arquivo de agente ".$filepath); |
242 | 244 | ||
@@ -253,4 +255,92 @@ class AgenteController extends Controller { | @@ -253,4 +255,92 @@ class AgenteController extends Controller { | ||
253 | return $response; | 255 | return $response; |
254 | } | 256 | } |
255 | 257 | ||
258 | + public function deployAction(Request $request) { | ||
259 | + $logger = $this->get('logger'); | ||
260 | + // Cria diretório dos agentes se não existir | ||
261 | + $rootDir = $this->container->get('kernel')->getRootDir(); | ||
262 | + $webDir = $rootDir . "/../web/"; | ||
263 | + $downloadsDir = $webDir . "downloads/"; | ||
264 | + if (!is_dir($downloadsDir)) { | ||
265 | + mkdir($downloadsDir); | ||
266 | + } | ||
267 | + | ||
268 | + $outrosDir = $downloadsDir . "outros/"; | ||
269 | + if (!is_dir($outrosDir)) { | ||
270 | + mkdir($outrosDir); | ||
271 | + } | ||
272 | + | ||
273 | + | ||
274 | + $form = $this->createForm( new DeployType() ); | ||
275 | + $locale = $request->getLocale(); | ||
276 | + | ||
277 | + // Constrói array de arquivos e hashes | ||
278 | + $finder = new Finder(); | ||
279 | + $agentes = new Finder(); | ||
280 | + $saida = array(); | ||
281 | + $base_url = $request->getBaseUrl(); | ||
282 | + $base_url = preg_replace('/\/app.*.php/', "/", $base_url); | ||
283 | + | ||
284 | + // Tratamos upload de módulos genéricos | ||
285 | + $finder->files()->in($outrosDir); | ||
286 | + $saida['outros'] = array(); | ||
287 | + foreach($finder as $file) { | ||
288 | + array_push($saida['outros'], array( | ||
289 | + 'name' => $file->getFileName(), | ||
290 | + 'download_url' => $base_url . 'downloads/outros/' . $file->getFileName(), | ||
291 | + 'hash' => md5_file($file->getRealPath()), | ||
292 | + 'size' => $file->getSize(), | ||
293 | + 'filename' => "outros/" . $file->getFileName() | ||
294 | + )); | ||
295 | + | ||
296 | + } | ||
297 | + | ||
298 | + if ( $request->isMethod('POST') ) | ||
299 | + { | ||
300 | + // Aqui vamos fazer o tratamento dos agentes | ||
301 | + $files = $request->files->get('deploy'); | ||
302 | + | ||
303 | + //$logger->debug("99999999999999999999999999999999999 ".print_r($files, true)); | ||
304 | + if (!empty($files['outros'])) { | ||
305 | + //$logger->debug("88888888888888888888888888888888888888 ".print_r($files['outros'], true)); | ||
306 | + $result = $this->uploadFile($files['outros'], $outrosDir); | ||
307 | + if (!$result) { | ||
308 | + $logger->error("Erro no upload do módulo"); | ||
309 | + $this->get('session')->getFlashBag()->add('error', 'Erro no upload do módulo'); | ||
310 | + } else { | ||
311 | + // Make this version current | ||
312 | + $logger->debug("Upload do módulo realizado com sucesso"); | ||
313 | + $this->get('session')->getFlashBag()->add('success', 'Upload do módulo realizado com sucesso!'); | ||
314 | + } | ||
315 | + } | ||
316 | + | ||
317 | + } | ||
318 | + | ||
319 | + $logger->debug("3333333333333333333333333333333333333333 ".print_r($saida, true)); | ||
320 | + | ||
321 | + return $this->render( 'CacicCommonBundle:Agente:deploy.html.twig', | ||
322 | + array( | ||
323 | + 'local'=>$locale, | ||
324 | + 'saida' => $saida, | ||
325 | + 'form' => $form->createView() | ||
326 | + ) | ||
327 | + ); | ||
328 | + } | ||
329 | + | ||
330 | + public function uploadFile($file, $version) { | ||
331 | + $logger = $this->get('logger'); | ||
332 | + if (!$file->isValid()) { | ||
333 | + $logger->error("Erro no upload do arquivo. Arquivo inválido\n".$file->getErrorMessage()); | ||
334 | + $this->get('session')->getFlashBag()->add('error', "Erro no upload do arquivo. Arquivo inválido\n".$file->getErrorMessage()); | ||
335 | + return false; | ||
336 | + } | ||
337 | + | ||
338 | + mkdir($version); | ||
339 | + $file->move($version, $file->getClientOriginalName()); | ||
340 | + $result = true; | ||
341 | + $logger->debug("Upload do módulo realizado com sucesso"); | ||
342 | + | ||
343 | + return $result; | ||
344 | + } | ||
345 | + | ||
256 | } | 346 | } |
257 | \ No newline at end of file | 347 | \ No newline at end of file |
@@ -0,0 +1,43 @@ | @@ -0,0 +1,43 @@ | ||
1 | +<?php | ||
2 | +/** | ||
3 | + * Created by PhpStorm. | ||
4 | + * User: eduardo | ||
5 | + * Date: 18/10/14 | ||
6 | + * Time: 23:36 | ||
7 | + */ | ||
8 | + | ||
9 | +namespace Cacic\CommonBundle\Form\Type; | ||
10 | + | ||
11 | +use Symfony\Component\Form\AbstractType; | ||
12 | +use Symfony\Component\Form\FormBuilderInterface; | ||
13 | + | ||
14 | +/** | ||
15 | + * Formulário para upload dos agentes | ||
16 | + * | ||
17 | + * Class AgenteType | ||
18 | + * @package Cacic\CommonBundle\Form\Type | ||
19 | + */ | ||
20 | +class DeployType extends AbstractType { | ||
21 | + | ||
22 | + /** | ||
23 | + * Nome do Formulário | ||
24 | + * @return string | ||
25 | + */ | ||
26 | + | ||
27 | + public function getName() { | ||
28 | + return 'deploy'; | ||
29 | + } | ||
30 | + | ||
31 | + public function buildForm( FormBuilderInterface $builder, array $options ) | ||
32 | + { | ||
33 | + | ||
34 | + $builder->add('outros', 'file', | ||
35 | + array( | ||
36 | + 'label' => 'Software para Deploy', | ||
37 | + 'required' => false | ||
38 | + ) | ||
39 | + ); | ||
40 | + | ||
41 | + } | ||
42 | + | ||
43 | +} | ||
0 | \ No newline at end of file | 44 | \ No newline at end of file |
src/Cacic/CommonBundle/Resources/config/routing.yml
@@ -545,4 +545,8 @@ cacic_agente: | @@ -545,4 +545,8 @@ cacic_agente: | ||
545 | 545 | ||
546 | cacic_agente_excluir: | 546 | cacic_agente_excluir: |
547 | pattern: /admin/agente/excluir | 547 | pattern: /admin/agente/excluir |
548 | - defaults: { _controller: CacicCommonBundle:Agente:excluir } | ||
549 | \ No newline at end of file | 548 | \ No newline at end of file |
549 | + defaults: { _controller: CacicCommonBundle:Agente:excluir } | ||
550 | + | ||
551 | +cacic_deploy: | ||
552 | + pattern: /admin/deploy/ | ||
553 | + defaults: { _controller: CacicCommonBundle:Agente:deploy } | ||
550 | \ No newline at end of file | 554 | \ No newline at end of file |
src/Cacic/CommonBundle/Resources/views/Agente/deploy.html.twig
0 → 100644
@@ -0,0 +1,127 @@ | @@ -0,0 +1,127 @@ | ||
1 | +{% extends 'CacicCommonBundle::base.html.twig' %} | ||
2 | + | ||
3 | +{% macro bytesToSize(bytes) %} | ||
4 | + {% spaceless %} | ||
5 | + {% set kilobyte = 1024 %} | ||
6 | + {% set megabyte = kilobyte * 1024 %} | ||
7 | + {% set gigabyte = megabyte * 1024 %} | ||
8 | + {% set terabyte = gigabyte * 1024 %} | ||
9 | + | ||
10 | + {% if bytes < kilobyte %} | ||
11 | + {{ bytes ~ ' B' }} | ||
12 | + {% elseif bytes < megabyte %} | ||
13 | + {{ (bytes / kilobyte)|number_format(2, '.') ~ ' KB' }} | ||
14 | + {% elseif bytes < gigabyte %} | ||
15 | + {{ (bytes / megabyte)|number_format(2, '.') ~ ' MB' }} | ||
16 | + {% elseif bytes < terabyte %} | ||
17 | + {{ (bytes / gigabyte)|number_format(2, '.') ~ ' GB' }} | ||
18 | + {% else %} | ||
19 | + {{ (bytes / terabyte)|number_format(2, '.') ~ ' TB' }} | ||
20 | + {% endif %} | ||
21 | + {% endspaceless %} | ||
22 | +{% endmacro %} | ||
23 | + | ||
24 | +{% block breadcrumb %} | ||
25 | + <li class="active">{{ 'Deploy de Software'|trans }}</li> | ||
26 | +{% endblock %} | ||
27 | + | ||
28 | +{% block body %} | ||
29 | + {% import _self as format %} | ||
30 | + | ||
31 | +<div class="row-fluid"> | ||
32 | + | ||
33 | + <div class="span8"> | ||
34 | + <div class="box grad_colour_black"> | ||
35 | + | ||
36 | + <h2 class="box_head round_top"><i class="icon-file"></i> {{'Upload de módulos para deploy' |trans }}</h2> | ||
37 | + | ||
38 | + <div class="block box_content round_bottom padding_10"> | ||
39 | + {{ form_start(form, {'id': 'formDeploy'|trans, 'action': path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')), 'method': 'POST'}) }} | ||
40 | + | ||
41 | + {{ form_errors(form) }} | ||
42 | + | ||
43 | + {{ form_row(form.outros) }} | ||
44 | + | ||
45 | + <input type="submit"> | ||
46 | + | ||
47 | + {{ form_end(form) }} | ||
48 | + | ||
49 | + </div> <!-- /block --> | ||
50 | + </div> <!-- /box --> | ||
51 | + </div> <!-- /span8 --> | ||
52 | + | ||
53 | + <div class="span4"> | ||
54 | + <div class="box grad_colour_black"> | ||
55 | + | ||
56 | + <h2 class="box_head round_top"><i class="icon-info-sign"></i> {{ "Informações Adicionais"|trans }}</h2> | ||
57 | + | ||
58 | + <div class="block box_content round_bottom padding_10"> | ||
59 | + <p> | ||
60 | + {{ "Realize o upload do arquivo para deploy nas estações de trabalho."|trans }} | ||
61 | + </p> | ||
62 | + <p> | ||
63 | + {{ "Os módulos ainda precisam ser habilitados nas subredes"|trans }} | ||
64 | + </p> | ||
65 | + </div> <!-- /block --> | ||
66 | + </div> <!-- /box --> | ||
67 | + </div> <!-- span4 --> | ||
68 | + | ||
69 | +</div> <!-- /row --> | ||
70 | + | ||
71 | +<div class="row-fluid"> | ||
72 | + <div class="span12"> | ||
73 | + <div class="box grad_colour_black"> | ||
74 | + <h2 class="box_head round_top"><i class="icon-list"></i> {{'Lista de Softwares disponíveis' |trans }}</h2> | ||
75 | + | ||
76 | + <div class="block box_content round_bottom padding_10"> | ||
77 | + <h4><center>{{ "Softwares para deploy"|trans }}</h4> | ||
78 | + <table class="table table-striped table-bordered"> | ||
79 | + <thead> | ||
80 | + <tr> | ||
81 | + <th width="30%" style="text-align: center">{{ 'Arquivo'|trans }}</th> | ||
82 | + <th width="25%" style="text-align: center">{{ 'Hash'|trans }}</th> | ||
83 | + <th width="20%" style="text-align: center">{{ 'Tamanho'|trans }}</th> | ||
84 | + <th style="text-align: center">{{ "Ações"|trans }}</th> | ||
85 | + </tr> | ||
86 | + </thead> | ||
87 | + <tbody> | ||
88 | + {% for modulo in saida.outros %} | ||
89 | + {% if modulo is empty %} | ||
90 | + <tr> | ||
91 | + <td style="text-align: center" colspan="6"><b>{{ 'NENHUM REGISTRO ENCONTRADO!'|trans }}</b></td> | ||
92 | + </tr> | ||
93 | + {% else %} | ||
94 | + <tr id="{{ modulo['filename'] }}" class="{{ cycle(['row0', 'row1'], loop.index) }}"> | ||
95 | + <td style="text-align: center" >{{ modulo['name'] }}</td> | ||
96 | + <td style="text-align: center" >{{ modulo['hash'] }}</td> | ||
97 | + <td style="text-align: center" >{{ format.bytesToSize(modulo['size']) }}</td> | ||
98 | + <td class="td-actions"> | ||
99 | + <a href="{{ path('cacic_agente_excluir') }}" class="btn btn-small btn-danger bt-excluir" title="{{ "Excluir Item"|trans }}"> | ||
100 | + <i class="btn-icon-only icon-trash icon-large"></i> | ||
101 | + </a> | ||
102 | + <a href="{{ modulo['download_url'] }}" class="btn btn-small" title="{{ "Baixar"|trans }}" target="_blank"> | ||
103 | + <i class="btn-icon-only icon-download-alt icon-large"></i> | ||
104 | + </a> | ||
105 | + </td> | ||
106 | + </tr> | ||
107 | + {% endif %} | ||
108 | + {% else %} | ||
109 | + <tr> | ||
110 | + <td style="text-align: center" colspan="6"><b>{{ 'NENHUM REGISTRO ENCONTRADO!'|trans }}</b></td> | ||
111 | + </tr> | ||
112 | + {% endfor %} | ||
113 | + </tbody> | ||
114 | + </table> | ||
115 | + | ||
116 | + </div> <!-- /block --> | ||
117 | + </div> <!-- /box --> | ||
118 | + </div> <!-- /span --> | ||
119 | +</div> <!-- /row --> | ||
120 | + | ||
121 | +{% endblock %} | ||
122 | + | ||
123 | +{% block javascripts %} | ||
124 | + | ||
125 | + {{ parent() }} | ||
126 | + | ||
127 | +{% endblock %} | ||
0 | \ No newline at end of file | 128 | \ No newline at end of file |
src/Cacic/CommonBundle/Resources/views/Agente/index.html.twig
@@ -33,7 +33,7 @@ | @@ -33,7 +33,7 @@ | ||
33 | <div class="span8"> | 33 | <div class="span8"> |
34 | <div class="box grad_colour_black"> | 34 | <div class="box grad_colour_black"> |
35 | 35 | ||
36 | - <h2 class="box_head round_top"><i class="icon-search"></i> {{'Upload de agentes' |trans }}</h2> | 36 | + <h2 class="box_head round_top"><i class="icon-file"></i> {{'Upload de agentes' |trans }}</h2> |
37 | 37 | ||
38 | <div class="block box_content round_bottom padding_10"> | 38 | <div class="block box_content round_bottom padding_10"> |
39 | {{ form_start(form, {'id': 'formAgentes'|trans, 'action': path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')), 'method': 'POST'}) }} | 39 | {{ form_start(form, {'id': 'formAgentes'|trans, 'action': path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')), 'method': 'POST'}) }} |
@@ -63,13 +63,13 @@ | @@ -63,13 +63,13 @@ | ||
63 | 63 | ||
64 | <div class="block box_content round_bottom padding_10"> | 64 | <div class="block box_content round_bottom padding_10"> |
65 | <p> | 65 | <p> |
66 | - {{ "Este módulo permite a visualização dos computadores monitorados pelos agentes do cacic"|trans }}. | 66 | + {{ "Realize o upload do pacote de arquivos contendo a nova versão do agente que será disponibilizada no servidor"|trans }}. |
67 | </p> | 67 | </p> |
68 | <p> | 68 | <p> |
69 | - {{ "É possível pesquisar por IP's, nome ou Mac da máquina e por data de instalação da máquina, bastando selecionar uma de suas opções"|trans }}. | 69 | + {{ "Ao realizar upload do pacote de agentes, informe o número da versão. O último enviado sempre será considerado o mais atual"|trans }}. |
70 | </p> | 70 | </p> |
71 | <p> | 71 | <p> |
72 | - {{ "Não selecionar nenhum valor em determinado critério é o mesmo que selecionar todos"|trans }}. | 72 | + {{ "IMPORTANTE: só serão aceitos arquivos nos formatos .zip e .tar.gz"|trans }}. |
73 | </p> | 73 | </p> |
74 | </div> <!-- /block --> | 74 | </div> <!-- /block --> |
75 | </div> <!-- /box --> | 75 | </div> <!-- /box --> |
@@ -115,7 +115,6 @@ | @@ -115,7 +115,6 @@ | ||
115 | {% for modulo in value %} | 115 | {% for modulo in value %} |
116 | 116 | ||
117 | <tr id="{{ modulo['filename'] }}" class="{{ cycle(['row0', 'row1'], loop.index) }}"> | 117 | <tr id="{{ modulo['filename'] }}" class="{{ cycle(['row0', 'row1'], loop.index) }}"> |
118 | - <td style="text-align: center">{{ version }}</td> | ||
119 | <td style="text-align: center" >{{ modulo['name'] }}</td> | 118 | <td style="text-align: center" >{{ modulo['name'] }}</td> |
120 | <td style="text-align: center" >{{ modulo['hash'] }}</td> | 119 | <td style="text-align: center" >{{ modulo['hash'] }}</td> |
121 | <td style="text-align: center" >{{ format.bytesToSize(modulo['size']) }}</td> | 120 | <td style="text-align: center" >{{ format.bytesToSize(modulo['size']) }}</td> |
@@ -123,6 +122,9 @@ | @@ -123,6 +122,9 @@ | ||
123 | <a href="{{ path('cacic_agente_excluir') }}" class="btn btn-small btn-danger bt-excluir" title="{{ "Excluir Item"|trans }}"> | 122 | <a href="{{ path('cacic_agente_excluir') }}" class="btn btn-small btn-danger bt-excluir" title="{{ "Excluir Item"|trans }}"> |
124 | <i class="btn-icon-only icon-trash icon-large"></i> | 123 | <i class="btn-icon-only icon-trash icon-large"></i> |
125 | </a> | 124 | </a> |
125 | + <a href="{{ modulo['download_url'] }}" class="btn btn-small" title="{{ "Baixar"|trans }}" target="_blank"> | ||
126 | + <i class="btn-icon-only icon-download-alt icon-large"></i> | ||
127 | + </a> | ||
126 | </td> | 128 | </td> |
127 | </tr> | 129 | </tr> |
128 | {% else %} | 130 | {% else %} |
@@ -178,6 +180,9 @@ | @@ -178,6 +180,9 @@ | ||
178 | <a href="{{ path('cacic_agente_excluir') }}" class="btn btn-small btn-danger bt-excluir" title="{{ "Excluir Item"|trans }}"> | 180 | <a href="{{ path('cacic_agente_excluir') }}" class="btn btn-small btn-danger bt-excluir" title="{{ "Excluir Item"|trans }}"> |
179 | <i class="btn-icon-only icon-trash icon-large"></i> | 181 | <i class="btn-icon-only icon-trash icon-large"></i> |
180 | </a> | 182 | </a> |
183 | + <a href="{{ modulo['download_url'] }}" class="btn btn-small" title="{{ "Baixar"|trans }}" target="_blank"> | ||
184 | + <i class="btn-icon-only icon-download-alt icon-large"></i> | ||
185 | + </a> | ||
181 | </td> | 186 | </td> |
182 | </tr> | 187 | </tr> |
183 | {% else %} | 188 | {% else %} |