vigiboard / vigiboard / controllers / root.py @ 8c93d88b
History | View | Annotate | Download (21.3 KB)
1 |
# -*- coding: utf-8 -*-
|
---|---|
2 |
# vim:set expandtab tabstop=4 shiftwidth=4:
|
3 |
"""Vigiboard Controller"""
|
4 |
|
5 |
from tg import expose, validate, require, flash, \ |
6 |
tmpl_context, request, config, session, redirect, url |
7 |
from tw.forms import validators |
8 |
from pylons.i18n import ugettext as _ |
9 |
from pylons.i18n import lazy_ugettext as l_ |
10 |
from pylons.controllers.util import abort |
11 |
from sqlalchemy import not_, and_, asc |
12 |
from sqlalchemy.orm import aliased |
13 |
from sqlalchemy.sql import func |
14 |
from datetime import datetime |
15 |
from time import mktime |
16 |
import math |
17 |
import urllib |
18 |
|
19 |
from vigiboard.model import DBSession |
20 |
from vigiboard.model import Event, EventHistory, CorrEvent, \ |
21 |
Host, HostGroup, ServiceGroup, \ |
22 |
StateName, User, ServiceLowLevel |
23 |
from repoze.what.predicates import Any, not_anonymous |
24 |
from vigiboard.widgets.edit_event import edit_event_status_options |
25 |
from vigiboard.controllers.vigiboardrequest import VigiboardRequest |
26 |
from vigiboard.controllers.vigiboard_controller import VigiboardRootController |
27 |
from vigilo.turbogears.controllers.autocomplete import AutoCompleteController |
28 |
from vigilo.models.functions import sql_escape_like |
29 |
from vigilo.models.secondary_tables import HOST_GROUP_TABLE, \ |
30 |
SERVICE_GROUP_TABLE
|
31 |
from vigiboard.lib.base import BaseController |
32 |
|
33 |
__all__ = ('RootController', 'get_last_modification_timestamp', |
34 |
'date_to_timestamp')
|
35 |
|
36 |
class RootController(VigiboardRootController): |
37 |
"""
|
38 |
Le controller général de vigiboard
|
39 |
"""
|
40 |
autocomplete = AutoCompleteController(BaseController) |
41 |
|
42 |
def process_form_errors(self, *argv, **kwargv): |
43 |
"""
|
44 |
Gestion des erreurs de validation : On affiche les erreurs
|
45 |
puis on redirige vers la dernière page accédée.
|
46 |
"""
|
47 |
for k in tmpl_context.form_errors: |
48 |
flash("'%s': %s" % (k, tmpl_context.form_errors[k]), 'error') |
49 |
if request.environ.get('HTTP_REFERER') : |
50 |
redirect(request.environ.get('HTTP_REFERER'
|
51 |
).split(request.environ.get('HTTP_HOST'))[1]) |
52 |
else :
|
53 |
redirect('/')
|
54 |
|
55 |
@expose('vigiboard.html') |
56 |
@require(Any(not_anonymous(), msg=l_("You need to be authenticated"))) |
57 |
def default(self, page=None, hostgroup=None, servicegroup=None, |
58 |
host=None, service=None, output=None, trouble_ticket=None, |
59 |
from_date=None, to_date=None, *argv, **krgv): |
60 |
|
61 |
"""
|
62 |
Page d'accueil de Vigiboard. Elle affiche, suivant la page demandée
|
63 |
(page 1 par defaut), la liste des événements, rangés par ordre de prise
|
64 |
en compte, puis de sévérité.
|
65 |
Pour accéder à cette page, l'utilisateur doit être authentifié.
|
66 |
|
67 |
@param page: Numéro de la page souhaitée, commence à 1
|
68 |
@param host: Si l'utilisateur souhaite sélectionner seulement certains
|
69 |
événements suivant leur hôte, il peut placer une expression
|
70 |
ici en suivant la structure du LIKE en SQL
|
71 |
@param service: Idem que host mais sur les services
|
72 |
@param output: Idem que host mais sur le text explicatif
|
73 |
@param trouble_ticket: Idem que host mais sur les tickets attribués
|
74 |
"""
|
75 |
if page is None: |
76 |
page = 1
|
77 |
|
78 |
try:
|
79 |
page = int(page)
|
80 |
except ValueError: |
81 |
abort(404)
|
82 |
|
83 |
if page < 1: |
84 |
page = 1
|
85 |
|
86 |
username = request.environ['repoze.who.identity']['repoze.who.userid'] |
87 |
user = User.by_user_name(username) |
88 |
|
89 |
aggregates = VigiboardRequest(user) |
90 |
|
91 |
search = { |
92 |
'host': '', |
93 |
'service': '', |
94 |
'output': '', |
95 |
'tt': '', |
96 |
'from_date': '', |
97 |
'to_date': '', |
98 |
'hostgroup': '', |
99 |
'servicegroup': '', |
100 |
} |
101 |
|
102 |
# Application des filtres si nécessaire
|
103 |
if hostgroup:
|
104 |
search['hostgroup'] = hostgroup
|
105 |
hostgroup = sql_escape_like(hostgroup) |
106 |
hg_alias = aliased(HostGroup) |
107 |
aggregates.add_outer_join((hg_alias, hg_alias.idgroup == \ |
108 |
HOST_GROUP_TABLE.c.idgroup)) |
109 |
aggregates.add_filter(hg_alias.name.ilike('%%%s%%' % hostgroup))
|
110 |
|
111 |
if servicegroup:
|
112 |
search['servicegroup'] = servicegroup
|
113 |
servicegroup = sql_escape_like(servicegroup) |
114 |
sg_alias = aliased(ServiceGroup) |
115 |
aggregates.add_outer_join((sg_alias, sg_alias.idgroup == \ |
116 |
SERVICE_GROUP_TABLE.c.idgroup)) |
117 |
aggregates.add_filter(sg_alias.name.ilike( |
118 |
'%%%s%%' % servicegroup))
|
119 |
|
120 |
if host:
|
121 |
search['host'] = host
|
122 |
host = sql_escape_like(host) |
123 |
aggregates.add_filter(Host.name.ilike('%%%s%%' % host))
|
124 |
|
125 |
if service:
|
126 |
search['service'] = service
|
127 |
service = sql_escape_like(service) |
128 |
aggregates.add_filter(ServiceLowLevel.servicename.ilike( |
129 |
'%%%s%%' % service))
|
130 |
|
131 |
if output:
|
132 |
search['output'] = output
|
133 |
output = sql_escape_like(output) |
134 |
aggregates.add_filter(Event.message.ilike('%%%s%%' % output))
|
135 |
|
136 |
if trouble_ticket:
|
137 |
search['tt'] = trouble_ticket
|
138 |
trouble_ticket = sql_escape_like(trouble_ticket) |
139 |
aggregates.add_filter(CorrEvent.trouble_ticket.ilike( |
140 |
'%%%s%%' % trouble_ticket))
|
141 |
|
142 |
if from_date:
|
143 |
search['from_date'] = from_date
|
144 |
# TRANSLATORS: Format de date et heure.
|
145 |
try:
|
146 |
from_date = datetime.strptime( |
147 |
from_date, _('%Y-%m-%d %I:%M:%S %p'))
|
148 |
except ValueError: |
149 |
to_date = None
|
150 |
aggregates.add_filter(CorrEvent.timestamp_active >= from_date) |
151 |
|
152 |
if to_date:
|
153 |
search['to_date'] = to_date
|
154 |
# TRANSLATORS: Format de date et heure.
|
155 |
try:
|
156 |
to_date = datetime.strptime( |
157 |
to_date, _('%Y-%m-%d %I:%M:%S %p'))
|
158 |
except ValueError: |
159 |
to_date = None
|
160 |
aggregates.add_filter(CorrEvent.timestamp_active <= to_date) |
161 |
|
162 |
# Calcul des éléments à afficher et du nombre de pages possibles
|
163 |
total_rows = aggregates.num_rows() |
164 |
items_per_page = int(config['vigiboard_items_per_page']) |
165 |
|
166 |
id_first_row = items_per_page * (page-1)
|
167 |
id_last_row = min(id_first_row + items_per_page, total_rows)
|
168 |
|
169 |
aggregates.format_events(id_first_row, id_last_row) |
170 |
aggregates.generate_tmpl_context() |
171 |
|
172 |
nb_pages = int(math.ceil(total_rows / (items_per_page + 0.0))) |
173 |
if not total_rows: |
174 |
id_first_row = 0
|
175 |
else:
|
176 |
id_first_row += 1
|
177 |
|
178 |
return dict( |
179 |
events = aggregates.events, |
180 |
rows_info = { |
181 |
'id_first_row': id_first_row,
|
182 |
'id_last_row': id_last_row,
|
183 |
'total_rows': total_rows,
|
184 |
}, |
185 |
nb_pages = nb_pages, |
186 |
page = page, |
187 |
event_edit_status_options = edit_event_status_options, |
188 |
history = [], |
189 |
hist_error = False,
|
190 |
plugin_context = aggregates.context_fct, |
191 |
search = search, |
192 |
refresh_times = config['vigiboard_refresh_times'],
|
193 |
) |
194 |
|
195 |
@validate(validators={'idcorrevent': validators.Int(not_empty=True)}, |
196 |
error_handler=process_form_errors) |
197 |
@expose('json') |
198 |
@require(Any(not_anonymous(), msg=l_("You need to be authenticated"))) |
199 |
def history_dialog(self, idcorrevent): |
200 |
|
201 |
"""
|
202 |
JSon renvoyant les éléments pour l'affichage de la fenêtre de dialogue
|
203 |
contenant des liens internes et externes.
|
204 |
Pour accéder à cette page, l'utilisateur doit être authentifié.
|
205 |
|
206 |
@param id: identifiant de l'événement
|
207 |
"""
|
208 |
|
209 |
# Obtention de données sur l'événement et sur son historique
|
210 |
username = request.environ.get('repoze.who.identity'
|
211 |
).get('repoze.who.userid')
|
212 |
user = User.by_user_name(username) |
213 |
user_groups = user.groups |
214 |
|
215 |
# try:
|
216 |
event = DBSession.query( |
217 |
CorrEvent.priority, |
218 |
Event, |
219 |
).join( |
220 |
(Event, CorrEvent.idcause == Event.idevent), |
221 |
(ServiceLowLevel, Event.idsupitem == ServiceLowLevel.idservice), |
222 |
(Host, Host.idhost == ServiceLowLevel.idhost), |
223 |
(HOST_GROUP_TABLE, HOST_GROUP_TABLE.c.idhost == Host.idhost), |
224 |
(SERVICE_GROUP_TABLE, SERVICE_GROUP_TABLE.c.idservice == \ |
225 |
ServiceLowLevel.idservice), |
226 |
).filter(HOST_GROUP_TABLE.c.idgroup.in_(user_groups) |
227 |
).filter(SERVICE_GROUP_TABLE.c.idgroup.in_(user_groups) |
228 |
).filter( |
229 |
# On masque les événements avec l'état OK
|
230 |
# et traités (status == u'AAClosed').
|
231 |
not_(and_( |
232 |
StateName.statename == u'OK',
|
233 |
CorrEvent.status == u'AAClosed'
|
234 |
)) |
235 |
).filter(CorrEvent.idcorrevent == idcorrevent |
236 |
).one() |
237 |
# except:
|
238 |
# # XXX Raise some HTTP error.
|
239 |
# return None
|
240 |
|
241 |
history = DBSession.query( |
242 |
EventHistory, |
243 |
).filter(EventHistory.idevent == event[1].idevent
|
244 |
).order_by(asc(EventHistory.timestamp) |
245 |
).order_by(asc(EventHistory.type_action)).all() |
246 |
|
247 |
eventdetails = {} |
248 |
for edname, edlink in \ |
249 |
config['vigiboard_links.eventdetails'].iteritems():
|
250 |
|
251 |
# Rappel:
|
252 |
# event[0] = priorité de l'alerte corrélée.
|
253 |
# event[1] = alerte brute.
|
254 |
eventdetails[edname] = edlink[1] % {
|
255 |
'idcorrevent': idcorrevent,
|
256 |
'host': urllib.quote(event[1].supitem.host.name), |
257 |
'service': urllib.quote(event[1].supitem.servicename), |
258 |
'message': urllib.quote(event[1].message), |
259 |
} |
260 |
|
261 |
return dict( |
262 |
current_state = StateName.value_to_statename( |
263 |
event[1].current_state),
|
264 |
initial_state = StateName.value_to_statename( |
265 |
event[1].initial_state),
|
266 |
peak_state = StateName.value_to_statename( |
267 |
event[1].peak_state),
|
268 |
idcorrevent = idcorrevent, |
269 |
host = event[1].supitem.host.name,
|
270 |
service = event[1].supitem.servicename,
|
271 |
eventdetails = eventdetails, |
272 |
) |
273 |
|
274 |
@validate(validators={'idcorrevent': validators.Int(not_empty=True)}, |
275 |
error_handler=process_form_errors) |
276 |
@expose('vigiboard.html') |
277 |
@require(Any(not_anonymous(), msg=l_("You need to be authenticated"))) |
278 |
def event(self, idcorrevent): |
279 |
"""
|
280 |
Affichage de l'historique d'un événement.
|
281 |
Pour accéder à cette page, l'utilisateur doit être authentifié.
|
282 |
|
283 |
@param idevent: identifiant de l'événement souhaité
|
284 |
"""
|
285 |
|
286 |
username = request.environ['repoze.who.identity']['repoze.who.userid'] |
287 |
events = VigiboardRequest(User.by_user_name(username)) |
288 |
events.add_filter(CorrEvent.idcorrevent == idcorrevent) |
289 |
|
290 |
# Vérification que l'événement existe
|
291 |
if events.num_rows() != 1 : |
292 |
flash(_('Error in DB'), 'error') |
293 |
redirect('/')
|
294 |
|
295 |
events.format_events(0, 1) |
296 |
events.format_history() |
297 |
events.generate_tmpl_context() |
298 |
|
299 |
return dict( |
300 |
events = events.events, |
301 |
rows_info = { |
302 |
'id_first_row': 1, |
303 |
'id_last_row': 1, |
304 |
'total_rows': 1, |
305 |
}, |
306 |
nb_pages = 1,
|
307 |
page = 1,
|
308 |
event_edit_status_options = edit_event_status_options, |
309 |
history = events.hist, |
310 |
hist_error = True,
|
311 |
plugin_context = events.context_fct, |
312 |
search = { |
313 |
'host': None, |
314 |
'service': None, |
315 |
'output': None, |
316 |
'tt': None, |
317 |
'from_date': None, |
318 |
'to_date': None, |
319 |
'hostgroup': None, |
320 |
'servicegroup': None, |
321 |
}, |
322 |
refresh_times=config['vigiboard_refresh_times'],
|
323 |
) |
324 |
|
325 |
@validate(validators={'host': validators.NotEmpty(), |
326 |
'service': validators.NotEmpty()}, error_handler=process_form_errors)
|
327 |
@expose('vigiboard.html') |
328 |
@require(Any(not_anonymous(), msg=l_("You need to be authenticated"))) |
329 |
def host_service(self, host, service): |
330 |
|
331 |
"""
|
332 |
Affichage de l'historique de l'ensemble des événements correspondant
|
333 |
au host et service demandé.
|
334 |
Pour accéder à cette page, l'utilisateur doit être authentifié.
|
335 |
|
336 |
@param host: Nom de l'hôte souhaité.
|
337 |
@param service: Nom du service souhaité
|
338 |
"""
|
339 |
|
340 |
username = request.environ['repoze.who.identity']['repoze.who.userid'] |
341 |
events = VigiboardRequest(User.by_user_name(username)) |
342 |
events.add_join((ServiceLowLevel, ServiceLowLevel.idservice == Event.idsupitem)) |
343 |
events.add_join((Host, ServiceLowLevel.idhost == Host.idhost)) |
344 |
events.add_filter(Host.name == host, |
345 |
ServiceLowLevel.servicename == service) |
346 |
|
347 |
# XXX On devrait avoir une autre API que ça !!!
|
348 |
# Supprime le filtre qui empêche d'obtenir des événements fermés
|
349 |
# (ie: ayant l'état Nagios 'OK' et le statut 'AAClosed').
|
350 |
if len(events.filter) > 2: |
351 |
del events.filter[2] |
352 |
|
353 |
# Vérification qu'il y a au moins 1 événement qui correspond
|
354 |
if events.num_rows() == 0 : |
355 |
redirect('/')
|
356 |
|
357 |
events.format_events(0, events.num_rows())
|
358 |
events.format_history() |
359 |
events.generate_tmpl_context() |
360 |
|
361 |
return dict( |
362 |
events = events.events, |
363 |
rows_info = { |
364 |
'id_first_row': 1, |
365 |
'id_last_row': 1, |
366 |
'total_rows': 1, |
367 |
}, |
368 |
nb_pages = 1,
|
369 |
page = 1,
|
370 |
event_edit_status_options = edit_event_status_options, |
371 |
history = events.hist, |
372 |
hist_error = True,
|
373 |
plugin_context = events.context_fct, |
374 |
search = { |
375 |
'host': None, |
376 |
'service': None, |
377 |
'output': None, |
378 |
'tt': None, |
379 |
'from_date': None, |
380 |
'to_date': None, |
381 |
'hostgroup': None, |
382 |
'servicegroup': None, |
383 |
}, |
384 |
refresh_times=config['vigiboard_refresh_times'],
|
385 |
) |
386 |
|
387 |
@validate(validators={
|
388 |
"id":validators.Regex(r'^[^,]+(,[^,]*)*,?$'), |
389 |
# "trouble_ticket":validators.Regex(r'^[0-9]*$'),
|
390 |
"status": validators.OneOf([
|
391 |
'NoChange',
|
392 |
'None',
|
393 |
'Acknowledged',
|
394 |
'AAClosed'
|
395 |
])}, error_handler=process_form_errors) |
396 |
@require(Any(not_anonymous(), msg=l_("You need to be authenticated"))) |
397 |
def update(self,**krgv): |
398 |
|
399 |
"""
|
400 |
Mise à jour d'un événement suivant les arguments passés.
|
401 |
Cela peut être un changement de ticket ou un changement de statut.
|
402 |
|
403 |
@param krgv['id']: Le ou les identifiants des événements à traiter
|
404 |
@param krgv['last_modification']: La date de la dernière modification
|
405 |
dont l'utilisateur est au courant.
|
406 |
@param krgv['tt']: Nouveau numéro du ticket associé.
|
407 |
@param krgv['status']: Nouveau status de/des événements.
|
408 |
"""
|
409 |
|
410 |
# On vérifie que des identifiants ont bien été transmis via
|
411 |
# le formulaire, et on informe l'utilisateur le cas échéant.
|
412 |
if krgv['id'] is None: |
413 |
flash(_('No event has been selected'), 'warning') |
414 |
raise redirect(request.environ.get('HTTP_REFERER', url('/'))) |
415 |
ids = krgv['id'].split(',') |
416 |
|
417 |
# Si des changements sont survenus depuis que la
|
418 |
# page est affichée, on en informe l'utilisateur.
|
419 |
if datetime.fromtimestamp(float(krgv['last_modification'])) \ |
420 |
< get_last_modification_timestamp(ids): |
421 |
flash(_('Changes have occurred since the page was displayed, '
|
422 |
'please refresh it.'), 'warning') |
423 |
print "\n\n\n\n ##### ", datetime.fromtimestamp(float(krgv['last_modification'])), " #####" |
424 |
print "##### ", get_last_modification_timestamp(ids), "\n\n\n\n" |
425 |
raise redirect(request.environ.get('HTTP_REFERER', url('/'))) |
426 |
|
427 |
# Si l'utilisateur édite plusieurs événements à la fois,
|
428 |
# il nous faut chacun des identifiants
|
429 |
|
430 |
if len(ids) > 1 : |
431 |
ids = ids[:-1]
|
432 |
|
433 |
username = request.environ['repoze.who.identity']['repoze.who.userid'] |
434 |
events = VigiboardRequest(User.by_user_name(username)) |
435 |
events.add_filter(CorrEvent.idcorrevent.in_(ids)) |
436 |
|
437 |
# Vérification que au moins un des identifiants existe et est éditable
|
438 |
if events.num_rows() <= 0 : |
439 |
flash(_('No access to this event'), 'error') |
440 |
redirect('/')
|
441 |
|
442 |
# Modification des événements et création d'un historique
|
443 |
# pour chacun d'eux.
|
444 |
for req in events.req: |
445 |
if isinstance(req, CorrEvent): |
446 |
event = req |
447 |
else:
|
448 |
event = req[0]
|
449 |
|
450 |
if krgv['trouble_ticket'] != '' : |
451 |
history = EventHistory( |
452 |
type_action="Ticket change",
|
453 |
idevent=event.idcause, |
454 |
value=krgv['trouble_ticket'],
|
455 |
text=_("Changed trouble ticket from '%s' to '%s'") % (
|
456 |
event.trouble_ticket, krgv['trouble_ticket']
|
457 |
), |
458 |
username=username, |
459 |
timestamp=datetime.now(), |
460 |
) |
461 |
DBSession.add(history) |
462 |
event.trouble_ticket = krgv['trouble_ticket']
|
463 |
|
464 |
if krgv['ack'] != 'NoChange' : |
465 |
history = EventHistory( |
466 |
type_action="Acknowlegement change state",
|
467 |
idevent=event.idcause, |
468 |
value=krgv['ack'],
|
469 |
text=_("Changed acknowledgement status from '%s' to '%s'") % (
|
470 |
event.status, krgv['ack']
|
471 |
), |
472 |
username=username, |
473 |
timestamp=datetime.now(), |
474 |
) |
475 |
DBSession.add(history) |
476 |
event.status = krgv['ack']
|
477 |
|
478 |
DBSession.flush() |
479 |
flash(_('Updated successfully'))
|
480 |
redirect(request.environ.get('HTTP_REFERER', url('/'))) |
481 |
|
482 |
|
483 |
@validate(validators={"plugin_name": validators.OneOf( |
484 |
[i for [i, j] in config.get('vigiboard_plugins', [])])}, |
485 |
error_handler = process_form_errors) |
486 |
@expose('json') |
487 |
def get_plugin_value(self, plugin_name, *arg, **krgv): |
488 |
"""
|
489 |
Permet aux plugins de pouvoir récupérer des valeurs Json
|
490 |
"""
|
491 |
plugins = config['vigiboard_plugins']
|
492 |
if plugins is None: |
493 |
return
|
494 |
|
495 |
plugin = [i for i in plugins if i[0] == plugin_name][0] |
496 |
try:
|
497 |
mypac = __import__(
|
498 |
'vigiboard.controllers.vigiboard_plugin.' + plugin[0], |
499 |
globals(), locals(), [plugin[1]], -1) |
500 |
plug = getattr(mypac, plugin[1])() |
501 |
return plug.controller(*arg, **krgv)
|
502 |
except:
|
503 |
raise
|
504 |
|
505 |
# @validate(validators= {"fontsize": validators.Int()},
|
506 |
# error_handler = process_form_errors)
|
507 |
@expose('json') |
508 |
def set_fontsize(self, fontsize): |
509 |
"""
|
510 |
Save font size
|
511 |
"""
|
512 |
session['fontsize'] = fontsize
|
513 |
session.save() |
514 |
return dict() |
515 |
|
516 |
@validate(validators={"refresh": validators.Int()}, |
517 |
error_handler=process_form_errors) |
518 |
@expose('json') |
519 |
def set_refresh(self, refresh): |
520 |
"""
|
521 |
Save refresh time
|
522 |
"""
|
523 |
session['refresh'] = refresh
|
524 |
session.save() |
525 |
return dict() |
526 |
|
527 |
@expose('json') |
528 |
def set_theme(self, theme): |
529 |
"""
|
530 |
Save theme to use time
|
531 |
"""
|
532 |
session['theme'] = theme
|
533 |
session.save() |
534 |
return dict() |
535 |
|
536 |
def get_last_modification_timestamp(event_id_list): |
537 |
"""
|
538 |
Récupère le timestamp de la dernière modification
|
539 |
opérée sur l'un des événements dont l'identifiant
|
540 |
fait partie de la liste passée en paramètre.
|
541 |
"""
|
542 |
last_modification_timestamp = DBSession.query( |
543 |
func.max(EventHistory.timestamp), |
544 |
).filter(EventHistory.idevent.in_(event_id_list) |
545 |
).scalar() |
546 |
|
547 |
if last_modification_timestamp:
|
548 |
return last_modification_timestamp
|
549 |
return datetime.now()
|
550 |
|
551 |
def date_to_timestamp(date): |
552 |
"""
|
553 |
Convertit une date en timestamp (décimal)
|
554 |
"""
|
555 |
return mktime(date.timetuple()) + date.microsecond / 1000000.0 |