Revision 27140946
Grosse simplification de la gestion des plugins
Ces modifications permettent d'isoler un peu plus les plugins
du code principal de VigiBoard (de sorte qu'il serait possible
de fournir les plugins dans des paquets RPM séparés).
Ajout d'un validateur type !FormEncode pour la conversion d'une date
selon un format (obtenu dans les traductions).
Ajout d'un mécanisme permettant aux plugins d'ajouter dynamiquement
des lignes dans le formulaire de recherche.
Ajout de nouveaux plugins pour coller au mécanisme décrit ci-dessus.
Transformation des plugins existants pour profiter de ce modèle
dynamique pour la recherche. En conséquence, le code nécessaire
pour gérer la recherche dans le contrôleur Root a été grandement
simplifié.
Ajout de la possibilité de filtrer en fonction de la priorité des
événements affichés (cf. #572).
Légère correction dans les tests (pour ne pas dépendre de l'i18n).
git-svn-id: https://vigilo-dev.si.c-s.fr/svn@6559 b22e2e97-25c9-44ff-b637-2e5ceca36478
vigiboard/config/app_cfg.py | ||
---|---|---|
65 | 65 |
plugin_class = ep.load(require=True) |
66 | 66 |
if issubclass(plugin_class, VigiboardRequestPlugin): |
67 | 67 |
plugins.append((unicode(ep.name), plugin_class())) |
68 |
except Exception, e:
|
|
69 |
LOGGER.error('Unable to import plugin %s : %s' % (plugin_name, e))
|
|
68 |
except: |
|
69 |
LOGGER.exception(u'Unable to import plugin %s', plugin_name)
|
|
70 | 70 |
|
71 | 71 |
config['columns_plugins'] = plugins |
72 | 72 |
|
... | ... | |
115 | 115 |
base_config['vigiboard_plugins'] = ( |
116 | 116 |
# 'id', |
117 | 117 |
'details', |
118 |
'groups', |
|
118 | 119 |
'date', |
119 | 120 |
'priority', |
120 | 121 |
'occurrences', |
121 | 122 |
'hostname', |
122 | 123 |
'servicename', |
123 | 124 |
'output', |
125 |
'masked_events', |
|
124 | 126 |
'hls', |
125 | 127 |
'status', |
126 | 128 |
# 'test', |
vigiboard/controllers/plugins/__init__.py | ||
---|---|---|
86 | 86 |
@rtype: C{int} |
87 | 87 |
""" |
88 | 88 |
return 1 |
89 |
|
|
90 |
def get_search_fields(self): |
|
91 |
return [] |
|
92 |
|
|
93 |
def handle_search_fields(self, query, search): |
|
94 |
pass |
vigiboard/controllers/plugins/date.py | ||
---|---|---|
22 | 22 |
Un plugin pour VigiBoard qui ajoute une colonne avec la date à laquelle |
23 | 23 |
est survenu un événement et la durée depuis laquelle l'événement est actif. |
24 | 24 |
""" |
25 |
import tw.forms as twf |
|
26 |
from pylons.i18n import ugettext as _, lazy_ugettext as l_ |
|
27 |
from tg.i18n import get_lang |
|
28 |
import tg |
|
29 |
|
|
30 |
from vigilo.models import tables |
|
25 | 31 |
|
26 | 32 |
from vigiboard.controllers.plugins import VigiboardRequestPlugin |
33 |
from vigiboard.lib.dateformat import DateFormatConverter |
|
34 |
|
|
35 |
def get_calendar_lang(): |
|
36 |
# TODO: Utiliser le champ "language" du modèle pour cet utilisateur ? |
|
37 |
# On récupère la langue du navigateur de l'utilisateur |
|
38 |
lang = get_lang() |
|
39 |
if not lang: |
|
40 |
lang = tg.config['lang'] |
|
41 |
else: |
|
42 |
lang = lang[0] |
|
43 |
|
|
44 |
# TODO: Il faudrait gérer les cas où tout nous intéresse dans "lang". |
|
45 |
# Si l'identifiant de langage est composé (ex: "fr_FR"), |
|
46 |
# on ne récupère que la 1ère partie. |
|
47 |
lang = lang.replace('_', '-') |
|
48 |
lang = lang.split('-')[0] |
|
49 |
return lang |
|
50 |
|
|
51 |
def get_date_format(): |
|
52 |
# @HACK: nécessaire car l_() retourne un object LazyString |
|
53 |
# qui n'est pas sérialisable en JSON. |
|
54 |
return _('%Y-%m-%d %I:%M:%S %p').encode('utf-8') |
|
27 | 55 |
|
28 | 56 |
class PluginDate(VigiboardRequestPlugin): |
29 | 57 |
"""Plugin pour l'ajout d'une colonne Date.""" |
30 |
pass |
|
58 |
def get_search_fields(self): |
|
59 |
return [ |
|
60 |
twf.CalendarDateTimePicker( |
|
61 |
'from_date', |
|
62 |
label_text=l_('From'), |
|
63 |
button_text=l_("Choose"), |
|
64 |
not_empty=False, |
|
65 |
validator=DateFormatConverter(if_missing=None), |
|
66 |
date_format=get_date_format, |
|
67 |
calendar_lang=get_calendar_lang, |
|
68 |
), |
|
69 |
twf.CalendarDateTimePicker( |
|
70 |
'to_date', |
|
71 |
label_text=l_('To'), |
|
72 |
button_text=l_("Choose"), |
|
73 |
not_empty=False, |
|
74 |
validator=DateFormatConverter(if_missing=None), |
|
75 |
date_format=get_date_format, |
|
76 |
calendar_lang=get_calendar_lang, |
|
77 |
), |
|
78 |
] |
|
79 |
|
|
80 |
def handle_search_fields(self, query, search): |
|
81 |
if search.get('from_date'): |
|
82 |
query.add_filter(tables.CorrEvent.timestamp_active >= |
|
83 |
search['from_date']) |
|
84 |
if search.get('to_date'): |
|
85 |
query.add_filter(tables.CorrEvent.timestamp_active <= |
|
86 |
search['to_date']) |
vigiboard/controllers/plugins/groups.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# vim:set expandtab tabstop=4 shiftwidth=4: |
|
3 |
################################################################################ |
|
4 |
# |
|
5 |
# Copyright (C) 2007-2011 CS-SI |
|
6 |
# |
|
7 |
# This program is free software; you can redistribute it and/or modify |
|
8 |
# it under the terms of the GNU General Public License version 2 as |
|
9 |
# published by the Free Software Foundation. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, |
|
12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
# GNU General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
|
19 |
################################################################################ |
|
20 |
|
|
21 |
""" |
|
22 |
Un plugin pour VigiBoard qui ajoute une colonne avec les groupes |
|
23 |
d'éléments supervisés auxquels appartient l'objet associé |
|
24 |
à l'événement corrélé. |
|
25 |
""" |
|
26 |
import tw.forms as twf |
|
27 |
from pylons.i18n import lazy_ugettext as l_ |
|
28 |
|
|
29 |
from vigiboard.controllers.plugins import VigiboardRequestPlugin |
|
30 |
from vigilo.models.session import DBSession |
|
31 |
from vigilo.models.tables.group import Group |
|
32 |
from vigilo.models.tables.grouphierarchy import GroupHierarchy |
|
33 |
|
|
34 |
class GroupSelector(twf.InputField): |
|
35 |
params = ["choose_text", "text_value", "clear_text"] |
|
36 |
choose_text = l_('Choose') |
|
37 |
clear_text = l_('Clear') |
|
38 |
text_value = '' |
|
39 |
|
|
40 |
template = """ |
|
41 |
<div xmlns="http://www.w3.org/1999/xhtml" |
|
42 |
xmlns:py="http://genshi.edgewall.org/" py:strip=""> |
|
43 |
<input type="hidden" name="${name}" class="${css_class}" |
|
44 |
id="${id}.value" value="${value}" py:attrs="attrs" /> |
|
45 |
<input type="text" class="${css_class}" id="${id}.ui" |
|
46 |
value="${text_value}" readonly="readonly" py:attrs="attrs" /> |
|
47 |
<input type="button" class="${css_class}" id="${id}" |
|
48 |
value="${choose_text}" py:attrs="attrs" /> |
|
49 |
<input type="button" class="${css_class}" id="${id}.clear" |
|
50 |
value="${clear_text}" py:attrs="attrs" /> |
|
51 |
</div> |
|
52 |
""" |
|
53 |
|
|
54 |
def update_params(self, d): |
|
55 |
super(GroupSelector, self).update_params(d) |
|
56 |
text_value = DBSession.query(Group.name).filter( |
|
57 |
Group.idgroup == d.value).scalar() |
|
58 |
if not text_value: |
|
59 |
d.value = '' |
|
60 |
else: |
|
61 |
d.text_value = text_value |
|
62 |
|
|
63 |
|
|
64 |
class PluginGroups(VigiboardRequestPlugin): |
|
65 |
""" |
|
66 |
Affiche les groupes d'éléments supervisés auxquels |
|
67 |
appartient l'événement corrélé. |
|
68 |
""" |
|
69 |
def get_search_fields(self): |
|
70 |
return [ |
|
71 |
GroupSelector( |
|
72 |
'supitemgroup', |
|
73 |
label_text=l_('Group'), |
|
74 |
validator=twf.validators.Int(if_invalid=None, if_missing=None), |
|
75 |
) |
|
76 |
] |
|
77 |
|
|
78 |
def handle_search_fields(self, query, search): |
|
79 |
if search.get('supitemgroup'): |
|
80 |
query.add_join((GroupHierarchy, GroupHierarchy.idchild == |
|
81 |
query.items.c.idsupitemgroup)) |
|
82 |
query.add_filter(GroupHierarchy.idparent == |
|
83 |
search['supitemgroup']) |
vigiboard/controllers/plugins/hostname.py | ||
---|---|---|
17 | 17 |
# along with this program; if not, write to the Free Software |
18 | 18 |
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
19 | 19 |
################################################################################ |
20 |
|
|
21 | 20 |
""" |
22 | 21 |
Un plugin pour VigiBoard qui ajoute une colonne avec le nom de l'hôte |
23 | 22 |
sur lequel porte l'événement corrélé. |
24 | 23 |
""" |
24 |
import tw.forms as twf |
|
25 |
from pylons.i18n import lazy_ugettext as l_ |
|
26 |
|
|
27 |
from vigilo.models.functions import sql_escape_like |
|
25 | 28 |
from vigiboard.controllers.plugins import VigiboardRequestPlugin |
26 | 29 |
|
27 | 30 |
class PluginHostname(VigiboardRequestPlugin): |
28 | 31 |
""" |
29 | 32 |
Ajoute une colonne avec le nom de l'hôte impacté par un événement corrélé. |
30 | 33 |
""" |
34 |
def get_search_fields(self): |
|
35 |
return [ |
|
36 |
twf.TextField( |
|
37 |
'host', |
|
38 |
label_text=l_('Host'), |
|
39 |
validator=twf.validators.String(if_missing=None), |
|
40 |
) |
|
41 |
] |
|
42 |
|
|
43 |
def handle_search_fields(self, query, search): |
|
44 |
if search.get('host'): |
|
45 |
host = sql_escape_like(search['host']) |
|
46 |
query.add_filter(query.items.c.hostname.ilike(host)) |
vigiboard/controllers/plugins/masked_events.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# vim:set expandtab tabstop=4 shiftwidth=4: |
|
3 |
################################################################################ |
|
4 |
# |
|
5 |
# Copyright (C) 2007-2011 CS-SI |
|
6 |
# |
|
7 |
# This program is free software; you can redistribute it and/or modify |
|
8 |
# it under the terms of the GNU General Public License version 2 as |
|
9 |
# published by the Free Software Foundation. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, |
|
12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
# GNU General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
|
19 |
################################################################################ |
|
20 |
|
|
21 |
""" |
|
22 |
Un plugin pour VigiBoard qui ajoute une colonne avec le nombre |
|
23 |
d'événements masqués d'un événement corrélé. |
|
24 |
""" |
|
25 |
from vigiboard.controllers.plugins import VigiboardRequestPlugin |
|
26 |
|
|
27 |
class PluginMaskedEvents(VigiboardRequestPlugin): |
|
28 |
""" |
|
29 |
Affiche le nombre d'événements masqués par l'événement corrélé. |
|
30 |
""" |
vigiboard/controllers/plugins/output.py | ||
---|---|---|
22 | 22 |
Un plugin pour VigiBoard qui ajoute une colonne avec la sortie |
23 | 23 |
de la commande de test exécutée par Nagios sur cet hôte/service. |
24 | 24 |
""" |
25 |
import tw.forms as twf |
|
26 |
from pylons.i18n import lazy_ugettext as l_ |
|
27 |
|
|
28 |
from vigilo.models.tables import Event |
|
29 |
from vigilo.models.functions import sql_escape_like |
|
25 | 30 |
from vigiboard.controllers.plugins import VigiboardRequestPlugin |
26 | 31 |
|
27 | 32 |
class PluginOutput(VigiboardRequestPlugin): |
28 | 33 |
"""Ajoute une colonne avec le message de Nagios.""" |
34 |
def get_search_fields(self): |
|
35 |
return [ |
|
36 |
twf.TextField( |
|
37 |
'output', |
|
38 |
label_text=l_('Output'), |
|
39 |
validator=twf.validators.String(if_missing=None), |
|
40 |
) |
|
41 |
] |
|
42 |
|
|
43 |
def handle_search_fields(self, query, search): |
|
44 |
if search.get('output'): |
|
45 |
output = sql_escape_like(search['output']) |
|
46 |
query.add_filter(Event.message.ilike(output)) |
vigiboard/controllers/plugins/priority.py | ||
---|---|---|
22 | 22 |
Un plugin pour VigiBoard qui ajoute une colonne avec la priorité |
23 | 23 |
ITIL de l'événement corrélé. |
24 | 24 |
""" |
25 |
import tw.forms as twf |
|
26 |
from pylons.i18n import lazy_ugettext as l_ |
|
27 |
from formencode import schema, validators |
|
28 |
|
|
29 |
from vigilo.models.tables import CorrEvent |
|
25 | 30 |
from vigiboard.controllers.plugins import VigiboardRequestPlugin |
26 | 31 |
|
32 |
from tw.forms.fields import ContainerMixin, FormField |
|
33 |
from tw.core.base import WidgetsList |
|
34 |
|
|
35 |
class HorizontalBox(ContainerMixin, FormField): |
|
36 |
""" |
|
37 |
Container de widgets, qui se contente de les placer |
|
38 |
côte-à-côte horizontalement. |
|
39 |
""" |
|
40 |
|
|
41 |
template = """<div |
|
42 |
xmlns="http://www.w3.org/1999/xhtml" |
|
43 |
xmlns:py="http://genshi.edgewall.org/" |
|
44 |
id="${id}" |
|
45 |
name="${name}" |
|
46 |
class="${css_class}" |
|
47 |
py:attrs="attrs"> |
|
48 |
<py:for each="field in fields"> |
|
49 |
${field.display(value_for(field), **args_for(field))} |
|
50 |
</py:for> |
|
51 |
</div> |
|
52 |
""" |
|
53 |
|
|
54 |
def generate_schema(self): |
|
55 |
""" |
|
56 |
Fait en sorte que l'absence de saisie dans les sous-champs |
|
57 |
du container ne génère pas une erreur (Valeur manquante) |
|
58 |
sur le container lui-même. |
|
59 |
""" |
|
60 |
super(HorizontalBox, self).generate_schema() |
|
61 |
self.validator.if_missing = None |
|
62 |
|
|
63 |
|
|
27 | 64 |
class PluginPriority(VigiboardRequestPlugin): |
28 | 65 |
""" |
29 | 66 |
Ce plugin affiche la priorité ITIL des événements corrélés. |
... | ... | |
36 | 73 |
(ordre opposé). L'ordre utilisé par VigiBoard pour le tri est |
37 | 74 |
défini dans la variable de configuration C{vigiboard_priority_order}. |
38 | 75 |
""" |
76 |
|
|
77 |
def get_search_fields(self): |
|
78 |
options = [ |
|
79 |
('eq', u'='), |
|
80 |
('neq', u'≠'), |
|
81 |
('gt', u'>'), |
|
82 |
('gte', u'≥'), |
|
83 |
('lt', u'<'), |
|
84 |
('lte', u'≤'), |
|
85 |
] |
|
86 |
|
|
87 |
return [ |
|
88 |
HorizontalBox( |
|
89 |
'priority', |
|
90 |
label_text=l_('Priority'), |
|
91 |
fields=[ |
|
92 |
twf.SingleSelectField( |
|
93 |
'op', |
|
94 |
options=options, |
|
95 |
validator=twf.validators.OneOf( |
|
96 |
dict(options).keys(), |
|
97 |
if_invalid=None, |
|
98 |
if_missing=None, |
|
99 |
), |
|
100 |
), |
|
101 |
twf.TextField( |
|
102 |
'value', |
|
103 |
validator=twf.validators.Int( |
|
104 |
if_invalid=None, |
|
105 |
if_missing=None, |
|
106 |
), |
|
107 |
), |
|
108 |
], |
|
109 |
) |
|
110 |
] |
|
111 |
|
|
112 |
def handle_search_fields(self, query, search): |
|
113 |
if (not search.get('priority')): |
|
114 |
return |
|
115 |
|
|
116 |
op = search['priority']['op'] |
|
117 |
value = search['priority']['value'] |
|
118 |
|
|
119 |
if (not op) or (not value): |
|
120 |
search['priority'] = None |
|
121 |
return |
|
122 |
|
|
123 |
if op == 'eq': |
|
124 |
query.add_filter(CorrEvent.priority == value) |
|
125 |
elif op == 'neq': |
|
126 |
query.add_filter(CorrEvent.priority != value) |
|
127 |
elif op == 'gt': |
|
128 |
query.add_filter(CorrEvent.priority > value) |
|
129 |
elif op == 'gte': |
|
130 |
query.add_filter(CorrEvent.priority >= value) |
|
131 |
elif op == 'lt': |
|
132 |
query.add_filter(CorrEvent.priority < value) |
|
133 |
elif op == 'lte': |
|
134 |
query.add_filter(CorrEvent.priority <= value) |
vigiboard/controllers/plugins/servicename.py | ||
---|---|---|
22 | 22 |
Un plugin pour VigiBoard qui ajoute une colonne avec le nom du service |
23 | 23 |
à l'origine de l'événement corrélé. |
24 | 24 |
""" |
25 |
import tw.forms as twf |
|
26 |
from pylons.i18n import lazy_ugettext as l_ |
|
27 |
|
|
28 |
from vigilo.models.functions import sql_escape_like |
|
25 | 29 |
from vigiboard.controllers.plugins import VigiboardRequestPlugin |
26 | 30 |
|
27 | 31 |
class PluginServicename(VigiboardRequestPlugin): |
... | ... | |
30 | 34 |
Si l'événement corrélé porte directement sur un hôte, |
31 | 35 |
alors le nom de service vaut None. |
32 | 36 |
""" |
37 |
def get_search_fields(self): |
|
38 |
return [ |
|
39 |
twf.TextField( |
|
40 |
'service', |
|
41 |
label_text=l_('Service'), |
|
42 |
validator=twf.validators.String(if_missing=None), |
|
43 |
) |
|
44 |
] |
|
45 |
|
|
46 |
def handle_search_fields(self, query, search): |
|
47 |
if search.get('service'): |
|
48 |
service = sql_escape_like(search['service']) |
|
49 |
query.add_filter(query.items.c.servicename.ilike(service)) |
vigiboard/controllers/plugins/status.py | ||
---|---|---|
26 | 26 |
- la dernière colonne permet de (dé)sélectionner l'événement pour |
27 | 27 |
effectuer un traitement par lot. |
28 | 28 |
""" |
29 |
import tw.forms as twf |
|
30 |
from pylons.i18n import lazy_ugettext as l_ |
|
31 |
|
|
32 |
from vigilo.models.tables import CorrEvent |
|
33 |
from vigilo.models.functions import sql_escape_like |
|
29 | 34 |
from vigiboard.controllers.plugins import VigiboardRequestPlugin |
30 | 35 |
|
31 | 36 |
class PluginStatus(VigiboardRequestPlugin): |
... | ... | |
40 | 45 |
Ce plugin en ajoute 4, au lieu de 1 comme la plupart des plugins. |
41 | 46 |
""" |
42 | 47 |
return 4 |
48 |
|
|
49 |
def get_search_fields(self): |
|
50 |
return [ |
|
51 |
twf.TextField( |
|
52 |
'trouble_ticket', |
|
53 |
label_text=l_('Trouble Ticket'), |
|
54 |
validator=twf.validators.String(if_missing=None), |
|
55 |
) |
|
56 |
] |
|
57 |
|
|
58 |
def handle_search_fields(self, query, search): |
|
59 |
if search.get('trouble_ticket'): |
|
60 |
tt = sql_escape_like(search['trouble_ticket']) |
|
61 |
query.add_filter(CorrEvent.trouble_ticket.ilike(tt)) |
vigiboard/controllers/root.py | ||
---|---|---|
22 | 22 |
|
23 | 23 |
from datetime import datetime |
24 | 24 |
from time import mktime |
25 |
import math |
|
26 | 25 |
|
27 |
from tg.exceptions import HTTPNotFound, HTTPInternalServerError
|
|
26 |
from tg.exceptions import HTTPNotFound |
|
28 | 27 |
from tg import expose, validate, require, flash, url, \ |
29 | 28 |
tmpl_context, request, config, session, redirect |
30 | 29 |
from webhelpers import paginate |
... | ... | |
33 | 32 |
from sqlalchemy import asc |
34 | 33 |
from sqlalchemy.sql import func |
35 | 34 |
from sqlalchemy.orm import aliased |
36 |
from sqlalchemy.orm import contains_eager |
|
37 | 35 |
from sqlalchemy.sql.expression import or_ |
38 | 36 |
from repoze.what.predicates import Any, All, in_group, \ |
39 | 37 |
has_permission, not_anonymous, \ |
40 | 38 |
NotAuthorizedError |
41 | 39 |
from formencode import schema |
42 |
from pkg_resources import working_set |
|
43 | 40 |
|
44 | 41 |
from vigilo.models.session import DBSession |
45 | 42 |
from vigilo.models.tables import Event, EventHistory, CorrEvent, Host, \ |
46 | 43 |
SupItem, SupItemGroup, LowLevelService, \ |
47 | 44 |
StateName, State, DataPermission |
48 | 45 |
from vigilo.models.tables.grouphierarchy import GroupHierarchy |
49 |
from vigilo.models.functions import sql_escape_like |
|
50 | 46 |
from vigilo.models.tables.secondary_tables import EVENTSAGGREGATE_TABLE, \ |
51 | 47 |
USER_GROUP_TABLE, SUPITEM_GROUP_TABLE |
52 | 48 |
|
... | ... | |
60 | 56 |
|
61 | 57 |
from vigiboard.widgets.edit_event import edit_event_status_options, \ |
62 | 58 |
EditEventForm |
63 |
from vigiboard.widgets.search_form import create_search_form, get_calendar_lang
|
|
59 |
from vigiboard.widgets.search_form import create_search_form |
|
64 | 60 |
|
65 | 61 |
__all__ = ('RootController', 'get_last_modification_timestamp', |
66 | 62 |
'date_to_timestamp') |
... | ... | |
88 | 84 |
|
89 | 85 |
def process_form_errors(self, *argv, **kwargv): |
90 | 86 |
""" |
91 |
Gestion des erreurs de validation : On affiche les erreurs
|
|
87 |
Gestion des erreurs de validation : on affiche les erreurs
|
|
92 | 88 |
puis on redirige vers la dernière page accédée. |
93 | 89 |
""" |
94 | 90 |
for k in tmpl_context.form_errors: |
... | ... | |
103 | 99 |
class DefaultSchema(schema.Schema): |
104 | 100 |
"""Schéma de validation de la méthode default.""" |
105 | 101 |
page = validators.Int(min=1, if_missing=1, if_invalid=1) |
106 |
supitemgroup = validators.Int(if_missing=None, if_invalid=None) |
|
107 |
host = validators.String(if_missing=None)
|
|
108 |
service = validators.String(if_missing=None)
|
|
109 |
output = validators.String(if_missing=None) |
|
110 |
trouble_ticket = validators.String(if_missing=None)
|
|
111 |
from_date = validators.String(if_missing=None)
|
|
112 |
to_date = validators.String(if_missing=None)
|
|
102 |
|
|
103 |
# Nécessaire pour que les critères de recherche soient conservés.
|
|
104 |
allow_extra_fields = True
|
|
105 |
|
|
106 |
# 2ème validation, cette fois avec les champs
|
|
107 |
# du formulaire de recherche.
|
|
108 |
chained_validators = [create_search_form.validator]
|
|
113 | 109 |
|
114 | 110 |
@validate( |
115 | 111 |
validators=DefaultSchema(), |
116 | 112 |
error_handler = process_form_errors) |
117 | 113 |
@expose('events_table.html') |
118 | 114 |
@require(access_restriction) |
119 |
def default(self, page, supitemgroup, host, service, |
|
120 |
output, trouble_ticket, from_date, to_date): |
|
115 |
def default(self, page, **search): |
|
121 | 116 |
""" |
122 | 117 |
Page d'accueil de Vigiboard. Elle affiche, suivant la page demandée |
123 | 118 |
(page 1 par defaut), la liste des événements, rangés par ordre de prise |
... | ... | |
125 | 120 |
Pour accéder à cette page, l'utilisateur doit être authentifié. |
126 | 121 |
|
127 | 122 |
@param page: Numéro de la page souhaitée, commence à 1 |
128 |
@param host: Si l'utilisateur souhaite sélectionner seulement certains |
|
129 |
événements suivant leur hôte, il peut placer une expression |
|
130 |
ici en suivant la structure du LIKE en SQL |
|
131 |
@param service: Idem que host mais sur les services |
|
132 |
@param output: Idem que host mais sur le text explicatif |
|
133 |
@param trouble_ticket: Idem que host mais sur les tickets attribués |
|
123 |
@type page: C{int} |
|
124 |
@param search: Dictionnaire contenant les critères de recherche. |
|
125 |
@type search: C{dict} |
|
134 | 126 |
|
135 | 127 |
Cette méthode permet de satisfaire les exigences suivantes : |
136 | 128 |
- VIGILO_EXIG_VIGILO_BAC_0040, |
... | ... | |
152 | 144 |
Event.idsupitem == aggregates.items.c.idsupitem)) |
153 | 145 |
aggregates.add_order_by(asc(aggregates.items.c.hostname)) |
154 | 146 |
|
155 |
search = {} |
|
156 |
|
|
157 |
# Application des filtres si nécessaire |
|
158 |
if supitemgroup: |
|
159 |
search['supitemgroup'] = supitemgroup |
|
160 |
aggregates.add_join((GroupHierarchy, GroupHierarchy.idchild == |
|
161 |
aggregates.items.c.idsupitemgroup)) |
|
162 |
aggregates.add_filter(GroupHierarchy.idparent == supitemgroup) |
|
163 |
|
|
164 |
if host: |
|
165 |
search['host_'] = host |
|
166 |
host = sql_escape_like(host) |
|
167 |
aggregates.add_filter(aggregates.items.c.hostname.ilike( |
|
168 |
'%s' % host)) |
|
169 |
|
|
170 |
if service: |
|
171 |
search['service'] = service |
|
172 |
service = sql_escape_like(service) |
|
173 |
aggregates.add_filter(aggregates.items.c.servicename.ilike( |
|
174 |
'%s' % service)) |
|
175 |
|
|
176 |
if output: |
|
177 |
search['output'] = output |
|
178 |
output = sql_escape_like(output) |
|
179 |
aggregates.add_filter(Event.message.ilike('%s' % output)) |
|
180 |
|
|
181 |
if trouble_ticket: |
|
182 |
search['tt'] = trouble_ticket |
|
183 |
trouble_ticket = sql_escape_like(trouble_ticket) |
|
184 |
aggregates.add_filter(CorrEvent.trouble_ticket.ilike( |
|
185 |
'%s' % trouble_ticket)) |
|
186 |
|
|
187 |
if from_date: |
|
188 |
search['from_date'] = from_date.lower() |
|
189 |
try: |
|
190 |
# TRANSLATORS: Format de date et heure Python/JavaScript. |
|
191 |
# TRANSLATORS: http://www.dynarch.com/static/jscalendar-1.0/doc/html/reference.html#node_sec_5.3.5 |
|
192 |
# TRANSLATORS: http://docs.python.org/release/2.5/lib/module-time.html |
|
193 |
from_date = datetime.strptime( |
|
194 |
from_date.encode('utf8'), |
|
195 |
_('%Y-%m-%d %I:%M:%S %p').encode('utf8')) |
|
196 |
except ValueError: |
|
197 |
# On ignore silencieusement la date invalide reçue. |
|
198 |
pass |
|
199 |
else: |
|
200 |
aggregates.add_filter(CorrEvent.timestamp_active >= from_date) |
|
201 |
|
|
202 |
if to_date: |
|
203 |
search['to_date'] = to_date.lower() |
|
204 |
try: |
|
205 |
# TRANSLATORS: Format de date et heure Python/JavaScript. |
|
206 |
# TRANSLATORS: http://www.dynarch.com/static/jscalendar-1.0/doc/html/reference.html#node_sec_5.3.5 |
|
207 |
# TRANSLATORS: http://docs.python.org/release/2.5/lib/module-time.html |
|
208 |
to_date = datetime.strptime( |
|
209 |
to_date.encode('utf8'), |
|
210 |
_('%Y-%m-%d %I:%M:%S %p').encode('utf8')) |
|
211 |
except ValueError: |
|
212 |
# On ignore silencieusement la date invalide reçue. |
|
213 |
pass |
|
214 |
else: |
|
215 |
aggregates.add_filter(CorrEvent.timestamp_active <= to_date) |
|
147 |
# Application des filtres des plugins si nécessaire. |
|
148 |
for plugin, instance in config.get('columns_plugins', []): |
|
149 |
instance.handle_search_fields(aggregates, search) |
|
150 |
|
|
151 |
# Certains arguments sont réservés dans url_for(). |
|
152 |
# On effectue les substitutions adéquates. |
|
153 |
# Par exemple: "host" devient "host_". |
|
154 |
reserved = ('host', ) |
|
155 |
copy = search.copy() |
|
156 |
for column in copy: |
|
157 |
if column in reserved: |
|
158 |
search[column + '_'] = search[column] |
|
159 |
del search[column] |
|
216 | 160 |
|
217 | 161 |
# Pagination des résultats |
218 | 162 |
aggregates.generate_request() |
219 | 163 |
items_per_page = int(config['vigiboard_items_per_page']) |
220 |
page = paginate.Page(aggregates.req, page=page, items_per_page=items_per_page) |
|
164 |
page = paginate.Page(aggregates.req, page=page, |
|
165 |
items_per_page=items_per_page) |
|
221 | 166 |
|
222 | 167 |
# Récupération des données des plugins |
223 | 168 |
plugins_data = {} |
... | ... | |
246 | 191 |
event_edit_status_options = edit_event_status_options, |
247 | 192 |
search_form = create_search_form, |
248 | 193 |
search = search, |
249 |
get_calendar_lang = get_calendar_lang, |
|
250 | 194 |
) |
251 | 195 |
|
252 | 196 |
|
... | ... | |
310 | 254 |
# Pagination des résultats |
311 | 255 |
events.generate_request() |
312 | 256 |
items_per_page = int(config['vigiboard_items_per_page']) |
313 |
page = paginate.Page(events.req, page=page, items_per_page=items_per_page) |
|
257 |
page = paginate.Page(events.req, page=page, |
|
258 |
items_per_page=items_per_page) |
|
314 | 259 |
|
315 | 260 |
# Vérification que l'événement existe |
316 | 261 |
if not page.item_count: |
... | ... | |
325 | 270 |
page = page, |
326 | 271 |
search_form = create_search_form, |
327 | 272 |
search = {}, |
328 |
get_calendar_lang = get_calendar_lang, |
|
329 | 273 |
) |
330 | 274 |
|
331 | 275 |
|
... | ... | |
388 | 332 |
page = page, |
389 | 333 |
search_form = create_search_form, |
390 | 334 |
search = {}, |
391 |
get_calendar_lang = get_calendar_lang, |
|
392 | 335 |
) |
393 | 336 |
|
394 | 337 |
|
... | ... | |
436 | 379 |
# Pagination des résultats |
437 | 380 |
aggregates.generate_request() |
438 | 381 |
items_per_page = int(config['vigiboard_items_per_page']) |
439 |
page = paginate.Page(aggregates.req, page=page, items_per_page=items_per_page) |
|
382 |
page = paginate.Page(aggregates.req, page=page, |
|
383 |
items_per_page=items_per_page) |
|
440 | 384 |
|
441 | 385 |
# Vérification qu'il y a au moins 1 événement qui correspond |
442 | 386 |
if not page.item_count: |
... | ... | |
460 | 404 |
event_edit_status_options = edit_event_status_options, |
461 | 405 |
search_form = create_search_form, |
462 | 406 |
search = {}, |
463 |
get_calendar_lang = get_calendar_lang, |
|
464 | 407 |
) |
465 | 408 |
|
466 | 409 |
|
... | ... | |
554 | 497 |
for event in events.req: |
555 | 498 |
if trouble_ticket and trouble_ticket != event.trouble_ticket: |
556 | 499 |
history = EventHistory( |
557 |
type_action="Ticket change", |
|
500 |
type_action=u"Ticket change",
|
|
558 | 501 |
idevent=event.idcause, |
559 | 502 |
value=unicode(trouble_ticket), |
560 | 503 |
text="Changed trouble ticket from '%(from)s' " |
vigiboard/lib/dateformat.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
""" |
|
3 |
Validateur et convertisseur de dates selon un format. |
|
4 |
""" |
|
5 |
from formencode.api import FancyValidator, Invalid |
|
6 |
from datetime import datetime |
|
7 |
|
|
8 |
from pylons.i18n import ugettext as _, lazy_ugettext as l_ |
|
9 |
|
|
10 |
class DateFormatConverter(FancyValidator): |
|
11 |
""" |
|
12 |
Valide une date selon un format identique à ceux |
|
13 |
acceptés par la fonction strptime(). |
|
14 |
""" |
|
15 |
messages = { |
|
16 |
'invalid': 'Invalid value', |
|
17 |
} |
|
18 |
|
|
19 |
def _to_python(self, value, state): |
|
20 |
if not isinstance(value, basestring): |
|
21 |
raise Invalid(self.message('invalid', state), value, state) |
|
22 |
|
|
23 |
str_date = value.lower() |
|
24 |
if isinstance(str_date, unicode): |
|
25 |
str_date = str_date.encode('utf-8') |
|
26 |
|
|
27 |
try: |
|
28 |
# TRANSLATORS: Format de date et heure Python/JavaScript. |
|
29 |
# TRANSLATORS: http://www.dynarch.com/static/jscalendar-1.0/doc/html/reference.html#node_sec_5.3.5 |
|
30 |
# TRANSLATORS: http://docs.python.org/release/2.5/lib/module-time.html |
|
31 |
date = datetime.strptime(str_date, _('%Y-%m-%d %I:%M:%S %p').encode('utf8')) |
|
32 |
except ValueError, e: |
|
33 |
raise Invalid(self.message('invalid', state), value, state) |
|
34 |
return date |
|
35 |
|
|
36 |
def _from_python(self, value, state): |
|
37 |
if not isinstance(value, datetime): |
|
38 |
raise Invalid(self.message('invalid', state), value, state) |
|
39 |
|
|
40 |
# Même format que pour _to_python. |
|
41 |
return datetime.strftime( |
|
42 |
value, |
|
43 |
_('%Y-%m-%d %I:%M:%S %p').encode('utf8') |
|
44 |
).decode('utf-8') |
vigiboard/tests/functional/test_search_form_misc.py | ||
---|---|---|
133 | 133 |
transaction.commit() |
134 | 134 |
|
135 | 135 |
# Préparation des dates/heures. |
136 |
# On réutilise le formattage attendu par le contrôleur |
|
137 |
# (donc dépendant de la locale des tests, "fr" par défaut). |
|
138 |
# TRANSLATORS: Format de date et heure. |
|
139 |
from_date = timestamp.strftime(str(_("%Y-%m-%d %I:%M:%S %p"))) |
|
140 |
# TRANSLATORS: Format de date et heure. |
|
141 |
to_date = datetime.max.strftime(str(_("%Y-%m-%d %I:%M:%S %p"))) |
|
136 |
from_date = timestamp.strftime("%Y-%m-%d %I:%M:%S %p") |
|
137 |
to_date = datetime.max.strftime("%Y-%m-%d %I:%M:%S %p") |
|
142 | 138 |
|
143 | 139 |
# Permet également de vérifier que la recherche |
144 | 140 |
# par date est inclusive. |
... | ... | |
148 | 144 |
'to_date': to_date, |
149 | 145 |
}, |
150 | 146 |
extra_environ={'REMOTE_USER': 'user'}) |
151 |
transaction.commit() |
|
152 | 147 |
|
153 | 148 |
# Il doit y avoir 1 seule ligne de résultats. |
154 | 149 |
rows = response.lxml.xpath('//table[@class="vigitable"]/tbody/tr') |
vigiboard/widgets/search_form.py | ||
---|---|---|
23 | 23 |
from pylons.i18n import lazy_ugettext as l_ |
24 | 24 |
from tw.api import WidgetsList |
25 | 25 |
import tw.forms as twf |
26 |
from tg.i18n import get_lang |
|
27 | 26 |
import tg |
28 | 27 |
|
29 |
from vigilo.models.session import DBSession |
|
30 |
from vigilo.models.tables.group import Group |
|
31 |
|
|
32 | 28 |
__all__ = ( |
33 | 29 |
'SearchForm', |
34 | 30 |
'create_search_form', |
35 | 31 |
) |
36 | 32 |
|
37 |
class GroupSelector(twf.InputField): |
|
38 |
params = ["choose_text", "text_value", "clear_text"] |
|
39 |
choose_text = l_('Choose') |
|
40 |
clear_text = l_('Clear') |
|
41 |
text_value = '' |
|
42 |
|
|
43 |
template = """ |
|
44 |
<div xmlns="http://www.w3.org/1999/xhtml" |
|
45 |
xmlns:py="http://genshi.edgewall.org/" py:strip=""> |
|
46 |
<input type="hidden" name="${name}" class="${css_class}" |
|
47 |
id="${id}.value" value="${value}" py:attrs="attrs" /> |
|
48 |
<input type="text" class="${css_class}" id="${id}.ui" |
|
49 |
value="${text_value}" readonly="readonly" py:attrs="attrs" /> |
|
50 |
<input type="button" class="${css_class}" id="${id}" |
|
51 |
value="${choose_text}" py:attrs="attrs" /> |
|
52 |
<input type="button" class="${css_class}" id="${id}.clear" |
|
53 |
value="${clear_text}" py:attrs="attrs" /> |
|
54 |
</div> |
|
55 |
""" |
|
56 |
|
|
57 |
def update_params(self, d): |
|
58 |
super(GroupSelector, self).update_params(d) |
|
59 |
text_value = DBSession.query(Group.name).filter( |
|
60 |
Group.idgroup == d.value).scalar() |
|
61 |
if not text_value: |
|
62 |
d.value = '' |
|
63 |
else: |
|
64 |
d.text_value = text_value |
|
65 |
|
|
66 | 33 |
class SearchForm(twf.TableForm): |
67 | 34 |
""" |
68 | 35 |
Formulaire de recherche dans les événements |
... | ... | |
78 | 45 |
method = 'GET' |
79 | 46 |
style = 'display: none' |
80 | 47 |
|
81 |
class fields(WidgetsList): |
|
82 |
supitemgroup = GroupSelector(label_text=l_('Group')) |
|
83 |
host = twf.TextField(label_text=l_('Host')) |
|
84 |
service = twf.TextField(label_text=l_('Service')) |
|
85 |
output = twf.TextField(label_text=l_('Output')) |
|
86 |
trouble_ticket = twf.TextField(label_text=l_('Trouble Ticket')) |
|
87 |
from_date = twf.CalendarDateTimePicker( |
|
88 |
label_text = l_('From'), |
|
89 |
button_text = l_("Choose"), |
|
90 |
not_empty = False) |
|
91 |
to_date = twf.CalendarDateTimePicker( |
|
92 |
label_text = l_('To'), |
|
93 |
button_text = l_("Choose"), |
|
94 |
not_empty = False) |
|
95 |
|
|
96 |
def get_calendar_lang(): |
|
97 |
# TODO: Utiliser le champ "language" du modèle pour cet utilisateur ? |
|
98 |
# On récupère la langue du navigateur de l'utilisateur |
|
99 |
lang = get_lang() |
|
100 |
if not lang: |
|
101 |
lang = tg.config['lang'] |
|
102 |
else: |
|
103 |
lang = lang[0] |
|
104 |
|
|
105 |
# TODO: Il faudrait gérer les cas où tout nous intéresse dans "lang". |
|
106 |
# Si l'identifiant de langage est composé (ex: "fr_FR"), |
|
107 |
# on ne récupère que la 1ère partie. |
|
108 |
lang = lang.replace('_', '-') |
|
109 |
lang = lang.split('-')[0] |
|
110 |
return lang |
|
48 |
fields = [ |
|
49 |
twf.HiddenField('page') |
|
50 |
] |
|
51 |
for plugin, instance in tg.config.get('columns_plugins', []): |
|
52 |
fields.extend(instance.get_search_fields()) |
|
111 | 53 |
|
112 | 54 |
create_search_form = SearchForm("search_form", |
113 | 55 |
submit_text=l_('Search'), action=tg.url('/'), |
Also available in: Unified diff