source: trunk/nagios-velvice/velvice.cgi @ 374

Last change on this file since 374 was 374, checked in by g7moreau, 6 years ago
  • Clean code
  • Property svn:keywords set to Id
File size: 21.8 KB
Line 
1#!/usr/bin/env perl
2#
3# 2014/05/15 Gabriel Moreau <Gabriel.Moreau@univ-grenoble-alpes.fr>
4# 2017/06/22 Gabriel Moreau - big update
5# 2018/06/25 Gabriel Moreau - make velvice generic
6# 2018/11/03 Gabriel Moreau - ajax
7#
8# velvice.cgi
9# Copyright (C) 2014-2018, LEGI UMR 5519 / CNRS UGA G-INP, Grenoble, France
10#
11# Need NagiosStatus http://exchange.nagios.org/directory/Addons/APIs/Perl/NagiosStatus-2Epm/details
12# Possible command http://old.nagios.org/developerinfo/externalcommands/commandlist.php
13#
14# apt-get install perl-modules libnagios-object-perl libhtml-parser-perl liburi-encode-perl libcolor-calc-perl libyaml-syck-perl
15# apt-get install libdatetime-event-recurrence-perl libdatetime-set-perl
16
17use strict;
18use warnings;
19use version; our $VERSION = version->declare('0.10.3');
20
21use CGI;
22use HTML::Entities ();
23use Nagios::StatusLog;
24use URI::Encode qw(uri_encode uri_decode);
25use Color::Calc ();
26use YAML::Syck;
27
28my $query           = CGI->new();
29my $cgi_check       = uri_decode($query->param('check'));
30my $cgi_script_name = $query->script_name();
31my $cgi_path        = $cgi_script_name =~ s{/[^/]+\.cgi$}{}r;
32my $cgi_only;
33$cgi_only++ if uri_decode($query->param('only')) eq 'body';
34undef $query;
35
36my %STATUS_DB = (
37   CRITICAL => {id => 3, color => '#F88888'},
38   WARNING  => {id => 2, color => '#FFFF00'},
39   PENDING  => {id => 1, color => '#E0E0E0'},
40   );
41
42my $config = {};
43$config = YAML::Syck::LoadFile('/etc/nagios3/velvice.yml') if -e '/etc/nagios3/velvice.yml';
44$config->{'nagios-server'}                ||= {};
45$config->{'nagios-server'}{'status-file'} ||= '/var/cache/nagios3/status.dat';
46$config->{'nagios-server'}{'nagios-cmd'}  ||= '/var/lib/nagios3/rw/nagios.cmd';
47$config->{'nagios-server'}{'portal-url'}  ||= $cgi_path =~ s{/cgi-bin/}{/}r . '/';
48$config->{'nagios-server'}{'status-cgi'}  ||= "$cgi_path/status.cgi";
49$config->{'nagios-server'}{'stylesheets'} ||= $config->{'nagios-server'}{'portal-url'} =~ s{/?$}{/stylesheets}r;
50$config->{'nagios-server'}{'image'}       ||= $config->{'nagios-server'}{'portal-url'} =~ s{/?$}{/images}r;
51$config->{'host-mapping'}                 ||= {};
52$config->{'color-downtime'}               ||= {};
53$config->{'color-downtime'}{'day-min'}    ||=  3;
54$config->{'color-downtime'}{'day-max'}    ||= 50;
55$config->{'color-downtime'}{'factor'}     ||=  0.7;
56$config->{'remote-action'}                ||= {};
57$config->{'refresh'}                      ||=  0;
58
59sub hostmapping {
60   my $host = shift;
61
62   return exists $config->{'host-mapping'}{$host} ? $config->{'host-mapping'}{$host} : $host;
63   }
64
65sub downtime {
66   my ($time_change) = @_;
67
68   my $now = time;
69   return sprintf '%.1f', ($now - $time_change) / (60 * 3600);
70   }
71
72sub alertcolor {
73   my ($status, $downtime) = @_;
74
75   my $color = '#0000FF';
76   $color = $STATUS_DB{$status}->{'color'} if exists $STATUS_DB{$status};
77
78   $downtime = $downtime - $config->{'color-downtime'}{'day-min'}; # same color first days
79   $downtime = $config->{'color-downtime'}{'day-max'} if $downtime > $config->{'color-downtime'}{'day-max'}; # max 50 days for color
80   $downtime =  0 if $downtime <  0;
81
82   my $factor = ($downtime * $config->{'color-downtime'}{'factor'}) / $config->{'color-downtime'}{'day-max'};
83   return Color::Calc::color_light_html($color, $factor);
84   }
85
86sub nosbreak {
87   my ($str) = @_;
88   
89   return $str =~ s/\s/\&nbsp;/gr;
90   }
91
92my $log = Nagios::StatusLog->new(
93   Filename => $config->{'nagios-server'}{'status-file'},
94   Version  => 3.0
95   );
96
97# refresh configuration
98if (exists $config->{'refreshments'}) {
99   require DateTime::Event::Recurrence;
100   require DateTime::SpanSet;
101
102   my @refreshments;
103   SET:
104   for my $set (@{$config->{'refreshments'}}) {
105      my $start   = DateTime::Event::Recurrence->weekly(days => $set->{'days'}, hours => $set->{'start'});
106      my $end     = DateTime::Event::Recurrence->weekly(days => $set->{'days'}, hours => $set->{'end'});
107      my $spanset = DateTime::SpanSet->from_sets(start_set => $start, end_set => $end);
108      push @refreshments, {refresh => $set->{'refresh'}, spanset => $spanset};
109      }
110
111   my $now = DateTime->now(time_zone => 'local');
112   SET:
113   for my $set (@refreshments) {
114      next SET if not $set->{'spanset'}->contains($now);
115 
116      $config->{'refresh'} = $set->{'refresh'};
117      last SET;
118      }
119   }
120
121my %hostdown;
122my @serviceproblems;
123my %hostcount;
124my @futurecheck;
125HOST:
126for my $host (sort $log->list_hosts()) {
127   my $host_stat = $log->host($host);
128
129   if ($host_stat->status eq 'DOWN') {TESTIF:{
130      for my $srv ($log->list_services_on_host($host)) {
131         last TESTIF if $log->service($host, $srv)->status eq 'OK' or $log->service($host, $srv)->status eq 'PENDING';
132         }
133
134      $hostdown{$host} = $host_stat;
135      next HOST;
136      }}
137
138   SRV:
139   for my $srv ($log->list_services_on_host($host)) {
140      my $status = $log->service($host, $srv)->status;
141
142      next SRV if $status eq 'OK';
143
144      push @serviceproblems, $log->service($host, $srv);
145   
146      my $downtime = downtime($log->service($host, $srv)->last_state_change);
147      my $color    = alertcolor($status, $downtime);
148
149      my $status_id = 0;
150      $status_id = $STATUS_DB{$status}->{'id'} if exists $STATUS_DB{$status};
151
152      #$hostcount{$host}++;
153      $hostcount{$host} ||= {count => 0, color => $color, status_id => $status_id, downtime => $downtime};
154      $hostcount{$host}->{'count'}++;
155      if (($status_id >= $hostcount{$host}->{'status_id'}) and ($downtime < $hostcount{$host}->{'downtime'})) {
156         $hostcount{$host}->{'downtime'}  = $downtime;
157         $hostcount{$host}->{'status_id'} = $status_id;
158         $hostcount{$host}->{'color'}     = $color;
159         }
160      }
161   }
162
163my $now = time;
164my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $now;
165$year += 1900;
166$mon++;
167my $date_sec = nosbreak(sprintf '%04i-%02i-%02i %02i:%02i:%02i', $year, $mon, $mday, $hour, $min, $sec);
168my $date_min = nosbreak(sprintf '%04i-%02i-%02i %02i:%02i',      $year, $mon, $mday, $hour, $min);
169
170my $htmlpage;
171
172$htmlpage .= <<"ENDH" if not $cgi_only;
173Content-Type: text/html
174
175<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
176<html lang="en">
177<head>
178 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
179ENDH
180
181$htmlpage .= <<"ENDH" if $cgi_only;
182Content-Type: text/xml
183
184<?xml version="1.0" encoding="utf-8"?>
185<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
186ENDH
187
188#$htmlpage .= " <meta http-equiv=\"Refresh\" content=\"$config->{'refresh'}\">" if $config->{'refresh'} > 59; # minimum 1 min
189$htmlpage .= <<"ENDH" if not $cgi_only;
190 <title>Nagios  Velvice</title>
191 <link rel="stylesheet"    type="text/css"  href="$config->{'nagios-server'}{'stylesheets'}/velvice.css">
192 <link rel="shortcut icon" type="image/ico" href="$config->{'nagios-server'}{'image'}/favicon.ico">
193 <script type="text/javascript">
194   var refresh_sec = 900; // 15 min = 900 s
195   var refresh_interval;
196   function refresh() {
197      var req = new XMLHttpRequest();
198      console.log("Grabbing Value");
199      req.open("GET", '$cgi_script_name?only=body', true); // Grabs whatever you've written in this file
200      req.onload = function () {
201         if (req.status == 200) {
202            document.getElementById('master-body').innerHTML = req.responseXML.getElementById('master-body').innerHTML;
203            console.log("Update Page: ", $date_sec);
204
205            refresh_next = req.responseXML.getElementById('master-body').getAttribute("refresh");
206            if (refresh_next < 60 && refresh_next != 0) {
207               refresh_next = 60;
208               }
209            if (refresh_next != refresh_sec) {
210               refresh_sec = refresh_next;
211               clearInterval(refresh_interval);
212               refresh_interval = setInterval(refresh, refresh_sec * 1000);
213               console.log("Refresh Interval: ", refresh_sec);
214               document.getElementById('master-body').setAttribute("refresh", refresh_sec);
215               }
216            }
217         }
218      req.send(null);
219      }
220
221   function init() { // This is the function the browser first runs when it's loaded.
222      refresh_sec = document.getElementById('master-body').getAttribute("refresh");
223      if (refresh_sec < 60 && refresh_sec != 0) {
224         refresh_sec = 60;
225         }
226      // Set the refresh() function to run every 900 seconds. 1 second would be 1000
227      refresh_interval = setInterval(refresh, refresh_sec * 1000);
228      }
229</script>
230</head>
231ENDH
232
233$htmlpage .= <<"ENDH";
234<body id="master-body" onload="init()" refresh="$config->{'refresh'}">
235<div class="header">
236 <h1>
237  <ul>
238    <li>Nagios Velvice Alert Panel : <a href="$config->{'nagios-server'}{'portal-url'}">Core Server</a></li>
239    <li><small><a id="refresh" href="$cgi_script_name">$date_min</a></small></li>
240  </ul>
241 </h1>
242</div>
243ENDH
244
245my %service_name   = ();
246my %service_status = ();
247for my $srv (@serviceproblems) {
248   $service_name{$srv->service_description}++;
249   $service_status{$srv->status}++;
250   }
251
252if (scalar @serviceproblems == 0) {
253   $htmlpage .= "<p>No alert to recheck.</p>\n";
254   }
255else {
256
257   $htmlpage .= "<p>Alert to recheck - Level:\n";
258   $htmlpage .= join ",\n",
259      " <span class='button'><a href='$cgi_script_name?check=all'>ALL</a><small>" . scalar(@serviceproblems) . '</small></span>',
260      map(" <span class='button'><a href='$cgi_script_name?check=" . lc(uri_encode($_)) . "'>$_</a><small>$service_status{$_}</small></span>",
261         sort keys %service_status);
262   $htmlpage .= ".\n";
263   $htmlpage .= " <br />\n";
264   $htmlpage .= " Service:\n";
265   $htmlpage .= join ",\n",
266      map(" <span class='button'><a href='$cgi_script_name?check=" . lc(uri_encode($_)) . "'>" . nosbreak($_) . "</a><small>$service_name{$_}</small></span>",
267         sort keys %service_name);
268   $htmlpage .= ".\n";
269   $htmlpage .= "</p>\n";
270
271   my $nagios_cmd;
272   open $nagios_cmd, '>>', $config->{'nagios-server'}{'nagios-cmd'} or die "Can't open file filename: $!";
273
274   my %remote_sshdown = ();
275   my %remote_db      = ();
276   my $remote_flag;
277
278   my $current_host  = '';
279   $htmlpage .= "<table border=\"1\">\n";
280   SERVICE_PROBLEMS:
281   for my $srv (@serviceproblems) {
282      my $hostname = $srv->host_name;
283      my $service  = $srv->service_description;
284      my $status   = $srv->status;
285      my $downtime = downtime($srv->last_state_change);
286      my $output   = HTML::Entities::encode($srv->plugin_output) =~ s/^[A-Z_\s]+?[:-]//r;
287
288      my $color = alertcolor($status, $downtime);
289      my $stylecolor = "style='background:$color;'";
290      $htmlpage .= " <tr>\n";
291      if ($hostname ne $current_host) {
292         $current_host  = $hostname;
293         my $rowspan    = $hostcount{$hostname}->{'count'};
294         my $rowcolor   = "style='background:" . $hostcount{$hostname}->{'color'} . ";'";
295         $htmlpage .= "  <td $rowcolor rowspan='$rowspan'>"
296            . "<a href=\"$cgi_script_name?check=" . uri_encode($hostname) . '">&#8623;</a></td>' . "\n";
297         $htmlpage .= "  <td $rowcolor class='hoop' rowspan='$rowspan'>"
298            . "<a href=\"$config->{'nagios-server'}{'status-cgi'}?host=" . uri_encode($hostname) . "\">$hostname</a></td>\n";
299         }
300
301      my $bold;
302      ACTION_STYLE:
303      for my $act_name (keys %{$config->{'remote-action'}}) {
304         my $act_regex = $config->{'remote-action'}{$act_name}{'regex'};
305         $bold++ if $service =~ m/$act_regex/ and $config->{'remote-action'}{$act_name}{'style'} eq 'bold';
306         }
307      $htmlpage .= $bold ? "  <td $stylecolor class='hoop bold'>" : "  <td $stylecolor class='hoop'>";
308      $htmlpage .= "$service</td>\n";
309
310      $htmlpage .= "  <td $stylecolor class='hoop'>$status</td>\n";
311      $htmlpage .= "  <td $stylecolor class='comment'>$output</td>\n";
312      $htmlpage .= "  <td $stylecolor class='days'>$downtime days</td>\n";
313
314      if (($cgi_check =~ m/all/i)
315            or ($cgi_check =~ m/^$service$/i)
316            or ($cgi_check =~ m/critical/i and $status eq 'CRITICAL')
317            or ($cgi_check =~ m/warning/i  and $status eq 'WARNING')
318            or ($cgi_check =~ m/pending/i  and $status eq 'PENDING')
319            or ($cgi_check eq $hostname    and $status =~ m/^(CRITICAL|WARNING|PENDING)$/)
320            ) {
321         $now++;
322         my $interval = $srv->next_check() - $srv->last_check() || 300; # 5 * 60 = 300
323         $interval =  240 if $interval <  240;
324         $interval = 3000 if $interval > 3000;
325         my $future = $now + 20 + int(rand($interval - 20));
326
327         $htmlpage .= "  <td class='checking'>" . ($future - $now) . "</td>\n";
328         #$htmlpage .= " -- <b>CHECK</b> [$now/" . ($future - $now) . "]";
329         printf $nagios_cmd "[%lu] SCHEDULE_FORCED_SVC_CHECK;%s;%s;%lu\n", $now, $hostname, $service, $now;
330         # delay future command
331         push @futurecheck, sprintf "[%lu] SCHEDULE_FORCED_SVC_CHECK;%s;%s;%lu", $future, $hostname, $service, $future;
332         }
333
334      ACTION_PUSH_AND_DEPEND:
335      for my $act_name (keys %{$config->{'remote-action'}}) {
336         my $act_regex  = $config->{'remote-action'}{$act_name}{'regex'};
337         my $act_status = $config->{'remote-action'}{$act_name}{'status'} || 'ALL';
338         my $act_depend = $config->{'remote-action'}{$act_name}{'depend'} || 'SSH';
339
340         if ($service =~ m/$act_regex/ and ($act_status eq 'ALL' or $status =~ m/$act_status/)) {
341            $remote_db{$act_name} ||= [];
342            push @{$remote_db{$act_name}}, $hostname;
343            $remote_flag++;
344            }
345
346         # check depend service otherwise
347         $remote_sshdown{$act_depend} ||= {};
348         $remote_sshdown{$act_depend}->{$hostname}++ if $service =~ m/$act_depend/;
349         }
350
351      $htmlpage .= " </tr>\n";
352      }
353
354   $htmlpage .= "</table>\n";
355   close $nagios_cmd;
356
357   # host down
358   if (%hostdown) {
359      $htmlpage .= "<br />\n";
360      $htmlpage .= "<table border='1'>\n";
361      HOST_DOWN:
362      for my $host (sort keys %hostdown) {
363         my $host_stat = $hostdown{$host};
364         my $hostname = $host_stat->host_name;
365         my $downtime = downtime($host_stat->last_state_change);
366         my $color = alertcolor('CRITICAL', $downtime);
367         $htmlpage .= " <tr style='background:$color'>\n";
368         $htmlpage .= "  <td class='hoop'><a href=\"$config->{'nagios-server'}{'status-cgi'}?host=" . uri_encode($hostname) . "\">$hostname</a></td>\n";
369         my @host_service;
370         for my $srv ($log->list_services_on_host($host)) {
371            push @host_service, $log->service($host, $srv)->service_description;
372            }
373         $htmlpage .= "  <td><small>" . join(', ', @host_service) . "</small></td>\n";
374         $htmlpage .= "  <td style='text-align:right;'>$downtime days</td>\n";
375         $htmlpage .= " </tr>\n";
376         }
377      $htmlpage .= "</table>\n";
378      }
379
380   # remote action
381   if ($remote_flag) {
382      require Nagios::Object::Config;
383      my $parser = Nagios::Object::Config->new();
384      $parser->parse("/var/cache/nagios3/objects.cache");
385
386      $htmlpage .= "<div class='action'>\n";
387      REMOTE_ACTION:
388      for my $act_name (keys %remote_db) {
389         my $act_depend = $config->{'remote-action'}{$act_name}{'depend'} || 'SSH';
390
391         my @action = grep !exists $remote_sshdown{$act_depend}->{$_}, @{$remote_db{$act_name}};
392         if (@action) {
393            my $srv_title = $config->{'remote-action'}{$act_name}{'title'} || "Action: $act_name";
394            $htmlpage .= "<h2>$srv_title</h2>\n";
395            $htmlpage .= "<pre>\n";
396            my $remote_action = $config->{'remote-action'}{$act_name}{'command'};
397            $remote_action = $config->{'remote-action'}{$act_name}{'command-one'}
398               if @action == 1 and exists $config->{'remote-action'}{$act_name}{'command-one'};
399            my @hosts;
400            for my $host (@action) {
401               my $object = $parser->find_object("$host", "Nagios::Host");
402               push @hosts, hostmapping($object->address =~ s/\..*$//r);
403               }
404            my $hosts_list = join ' ', @hosts;
405            $htmlpage .= ' ' . $remote_action =~ s{\%m}{$hosts_list}r;
406            $htmlpage .= "</pre>\n";
407            }
408         }
409      $htmlpage .= "</div>\n";
410      }
411   }
412
413$htmlpage .= <<"ENDH";
414<hr clear="all" />
415<div class="footer">
416 <b><a href="http://servforge.legi.grenoble-inp.fr/projects/soft-trokata/wiki/SoftWare/NagiosVelvice">Velvice</a>
417   - version: $VERSION</b>
418   (<a href="http://servforge.legi.grenoble-inp.fr/pub/soft-trokata/nagios-velvice/velvice.html">online manual</a>)
419   - Written by Gabriel Moreau
420 <ul>
421  <li>Licence GNU GPL version 2 or later and Perl equivalent</li>
422  <li>Copyright (C) 2014-2018, LEGI UMR 5519 / CNRS UGA G-INP, Grenoble, France</li>
423 </ul>
424</div>
425</body>
426ENDH
427
428$htmlpage .= <<"ENDH" if not $cgi_only;
429</html>
430ENDH
431
432print $htmlpage;
433
434# delayed future check
435if (@futurecheck) {
436   sleep 2;
437   my $nagios_cmd;
438   open $nagios_cmd, '>>', $config->{'nagios-server'}{'nagios-cmd'} or die "Can't open file filename: $!";
439   print $nagios_cmd "$_\n" for @futurecheck;
440   close $nagios_cmd;
441   }
442
443__END__
444
445
446=head1 NAME
447
448velvice.cgi - nagios velvice alert panel
449
450=head1 USAGE
451
452 velvice.cgi
453 velvice.cgi?check=XXX
454
455
456=head1 DESCRIPTION
457
458=begin html
459
460<img width="700" alt="Nagios Velvice Alert Panel" title="Nagios Velvice Alert Panel" style="float:right" src="velvice.png" />
461
462=end html
463
464Nagios VELVICE is an acronym for "Nagios leVEL serVICE status".
465
466The Nagios web page is sometimes very graphically charged
467and does not necessarily contain the information you need at a glance.
468For example, it is quite complicated to restart controls on multiple hosts in one click.
469
470For example, a server that is down should take only one line and not one per service...
471Similarly, a service that has been down for 5 minutes or since yesterday
472has more weight than a service that has fallen for 15 days.
473
474With Velvice Panel, a broken down server takes only one line.
475Services that have been falling for a long time gradually lose their color and become pastel colors.
476
477With Velvice Panel, it is possible through a single click
478to redo a check of all services that are in the CRITICAL state.
479Similarly, it is possible to restart a check on all SSH services in breakdowns ...
480In order not to clog the Nagios server, checks are shifted by 2 seconds in time.
481
482There is also a link to the web page of the main Nagios server.
483For each computer, you have a direct link to its dedicated web page on this server.
484
485
486=head1 CONFIGURATION FILE SPECIFICATION
487
488The configuration file must be F</etc/nagios3/velvice.yml>.
489This is not a required file.
490The file is in YAML format because this is a human-readable text file style.
491Other formats could have been Plain XML, RDF, JSON... but they are much less readable.
492
493You can find in the software nagios-velvice an example of configuration:
494L<velvice.sample.yml|http://servforge.legi.grenoble-inp.fr/pub/soft-trokata/nagios-velvice/velvice.sample.yml>.
495This one is in fact the master reference specification!
496
497The main keys C<nagios-server> and C<color-downtime> have good default values.
498No secondary key is required...
499The Velvice script try hard to replace ~ by the good value automatically.
500
501 nagios-server:
502   status-file: /var/cache/nagios3/status.dat
503   nagios-cmd:  /var/lib/nagios3/rw/nagios.cmd
504   portal-url:  ~/nagios3/
505   status-cgi:  ~/cgi-bin/nagios3/status.cgi
506   stylesheets: ~/nagios3/stylesheets
507
508The background color of the faulty service line display remains stable with a bright color for at least 3 days.
509Then, it decreases and becomes pastel after 53 days with an intensity of 70% (100% is white and 0% is black).
510
511 color-downtime:
512   day-min:  3
513   day-max: 50
514   factor:   0.7
515
516With key C<host-mapping>,
517it's good to map C<localhost> to the real name of the computer (hostname).
518
519 host-mapping:
520   localhost:  srv-nagios
521   toto:       titi
522
523The only important key is C<remote-action>.
524You can affiliate as many subkeys as you want.
525Let's take an example:
526
527 remote-action:
528   oom-killer:
529     regex: ^OOM Killer
530     title:  OOM Killer
531     command:     tssh -c 'sudo rm /var/lib/nagios3/nagios_oom_killer.log' %m
532     command-one: ssh %m 'sudo rm /var/lib/nagios3/nagios_oom_killer.log'
533     depend: ^SSH
534     status: ALL
535     style: bold
536
537C<oom-killer> is just a key for your remote action.
538The regex is used to find which service has a problem...
539The title is use in the result web page (not mandatory - otherwise, it will be C<Action: oom-killer>).
540The C<command> is just written on this web page.
541You have the responsibility to copy / cut it on a terminal.
542For security reasons, the nagios server does not have the right to launch the command on the remote host.
543The wildcard C<%m> is replaced by the list of the host (separated by the space).
544Sometime, the command could be different if there is only one computer (just SSH and no parallel SSH).
545If your command is based on SSH,
546you can have an SSH action only if the remote SSH is running.
547So you can make the remote action depend on the SSH service through a regular expression of your choice.
548
549The last two keys.
550The C<status> key is for CRITICAL or WARNING (or ALL).
551The key C<style> is there to mark in bold the service in error on the web page.
552
553=head1 SEE ALSO
554
555yamllint(1), ysh(1), YAML, Nagios::StatusLog, Color::Calc
556
557In Debian GNU/Linux distribution, packages for C<yamllint> and C<ysh> are:
558
559=over
560
561=item * C<yamllint> - Linter for YAML files (Python)
562
563=item * C<libyaml-shell-perl> - YAML test shell (Perl)
564
565=back
566
567
568Own project ressources:
569
570=over
571
572=item * L<Web Site|http://servforge.legi.grenoble-inp.fr/projects/soft-trokata/wiki/SoftWare/NagiosVelvice>
573
574=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/soft-trokata/nagios-velvice/velvice.html>
575
576=item * L<SVN Repository|http://servforge.legi.grenoble-inp.fr/svn/soft-trokata/trunk/nagios-velvice>
577
578=item * L<Debian Package|http://servforge.legi.grenoble-inp.fr/pub/soft-trokata/nagios-velvice/download/>
579
580=back
581
582
583=head1 VERSION
584
585$Id: velvice.cgi 374 2018-11-04 10:18:18Z g7moreau $
586
587
588=head1 AUTHOR
589
590Written by Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>, LEGI UMR 5519, CNRS, Grenoble - France
591
592
593=head1 LICENSE AND COPYRIGHT
594
595Licence GNU GPL version 2 or later and Perl equivalent
596
597Copyright (C) 2014-2018, LEGI UMR 5519 / CNRS UGA G-INP, Grenoble, France
Note: See TracBrowser for help on using the repository browser.