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

Last change on this file since 369 was 369, checked in by g7moreau, 6 years ago
  • Initial AJAX implementation
  • Property svn:keywords set to Id
File size: 21.6 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.0');
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++;
167#my $date = nosbreak(sprintf '%04i-%02i-%02i %02i:%02i:%02i', $year, $mon, $mday, $hour, $min, $sec);
168my $date = 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 first_loop  = 0;
195   var refresh_sec = 900; // 15 min = 900 s
196   function refresh() {
197      if (first_loop == 0) {
198         first_loop++;
199         return;
200         }
201      var req = new XMLHttpRequest();
202      console.log("Grabbing Value");
203      // req.onreadystatechange = function () {
204      //    if (req.readyState == 4 && req.status == 200) {
205      //       document.getElementById('master-body').innerText = req.responseXML.getElementById('master-body').innerText;
206      //       }
207      //    }
208      // req.overrideMimeType('text/xml');
209      req.open("GET", '$cgi_script_name?only=body', true); // Grabs whatever you've written in this file
210      req.onload = function () {
211         if (req.status == 200) {
212            // document.getElementById('master-body').innerText = req.responseXML.getElementById('master-body').innerText;
213            document.getElementById('master-body').outerHTML = req.responseXML.getElementById('master-body').outerHTML;
214            console.log("Update Value");
215            }
216         }
217      req.send(null);
218      }
219
220   function init() { // This is the function the browser first runs when it's loaded.
221      // refresh() // Then runs the refresh function for the first time.
222      var int = self.setInterval(refresh, refresh_sec * 1000); // Set the refresh() function to run every 900 seconds. 1 second would be 1000
223      }
224</script>
225</head>
226ENDH
227
228$htmlpage .= <<"ENDH";
229<body id="master-body" onload="init()" refresh="$config->{'refresh'}">
230<div class="header">
231 <h1>
232  <ul>
233    <li>Nagios Velvice Alert Panel : <a href="$config->{'nagios-server'}{'portal-url'}">Core Server</a></li>
234    <li><small><a id="refresh" href="$cgi_script_name">$date</a></small></li>
235  </ul>
236 </h1>
237</div>
238ENDH
239
240my %service_name   = ();
241my %service_status = ();
242for my $srv (@serviceproblems) {
243   $service_name{$srv->service_description}++;
244   $service_status{$srv->status}++;
245   }
246
247if (scalar @serviceproblems == 0) {
248   $htmlpage .= "<p>No alert to recheck.</p>\n";
249   }
250else {
251
252   $htmlpage .= "<p>Alert to recheck - Level:\n";
253   $htmlpage .= join ",\n",
254      " <span class='button'><a href='$cgi_script_name?check=all'>ALL</a><small>" . scalar(@serviceproblems) . '</small></span>',
255      map(" <span class='button'><a href='$cgi_script_name?check=" . lc(uri_encode($_)) . "'>$_</a><small>$service_status{$_}</small></span>",
256         sort keys %service_status);
257   $htmlpage .= ".\n";
258   $htmlpage .= " <br />\n";
259   $htmlpage .= " Service:\n";
260   $htmlpage .= join ",\n",
261      map(" <span class='button'><a href='$cgi_script_name?check=" . lc(uri_encode($_)) . "'>" . nosbreak($_) . "</a><small>$service_name{$_}</small></span>",
262         sort keys %service_name);
263   $htmlpage .= ".\n";
264   $htmlpage .= "</p>\n";
265
266   my $nagios_cmd;
267   open $nagios_cmd, '>>', $config->{'nagios-server'}{'nagios-cmd'} or die "Can't open file filename: $!";
268
269   my %remote_sshdown = ();
270   my %remote_db      = ();
271   my $remote_flag;
272
273   my $current_host  = '';
274   $htmlpage .= "<table border=\"1\">\n";
275   SERVICE_PROBLEMS:
276   for my $srv (@serviceproblems) {
277      my $hostname = $srv->host_name;
278      my $service  = $srv->service_description;
279      my $status   = $srv->status;
280      my $downtime = downtime($srv->last_state_change);
281      my $output   = HTML::Entities::encode($srv->plugin_output) =~ s/^[A-Z_\s]+?[:-]//r;
282
283      my $color = alertcolor($status, $downtime);
284      my $stylecolor = "style='background:$color;'";
285      $htmlpage .= " <tr>\n";
286      if ($hostname ne $current_host) {
287         $current_host  = $hostname;
288         my $rowspan    = $hostcount{$hostname}->{'count'};
289         my $rowcolor   = "style='background:" . $hostcount{$hostname}->{'color'} . ";'";
290         $htmlpage .= "  <td $rowcolor rowspan='$rowspan'>"
291            . "<a href=\"$cgi_script_name?check=" . uri_encode($hostname) . '">&#8623;</a></td>' . "\n";
292         $htmlpage .= "  <td $rowcolor class='hoop' rowspan='$rowspan'>"
293            . "<a href=\"$config->{'nagios-server'}{'status-cgi'}?host=" . uri_encode($hostname) . "\">$hostname</a></td>\n";
294         }
295
296      my $bold;
297      ACTION_STYLE:
298      for my $act_name (keys %{$config->{'remote-action'}}) {
299         my $act_regex = $config->{'remote-action'}{$act_name}{'regex'};
300         $bold++ if $service =~ m/$act_regex/ and $config->{'remote-action'}{$act_name}{'style'} eq 'bold';
301         }
302      $htmlpage .= $bold ? "  <td $stylecolor class='hoop bold'>" : "  <td $stylecolor class='hoop'>";
303      $htmlpage .= "$service</td>\n";
304
305      $htmlpage .= "  <td $stylecolor class='hoop'>$status</td>\n";
306      $htmlpage .= "  <td $stylecolor class='comment'>$output</td>\n";
307      $htmlpage .= "  <td $stylecolor class='days'>$downtime days</td>\n";
308
309      if (($cgi_check =~ m/all/i)
310            or ($cgi_check =~ m/^$service$/i)
311            or ($cgi_check =~ m/critical/i and $status eq 'CRITICAL')
312            or ($cgi_check =~ m/warning/i  and $status eq 'WARNING')
313            or ($cgi_check =~ m/pending/i  and $status eq 'PENDING')
314            or ($cgi_check eq $hostname    and $status =~ m/^(CRITICAL|WARNING|PENDING)$/)
315            ) {
316         $now++;
317         my $interval = $srv->next_check() - $srv->last_check() || 300; # 5 * 60 = 300
318         $interval =  240 if $interval <  240;
319         $interval = 3000 if $interval > 3000;
320         my $future = $now + 20 + int(rand($interval - 20));
321
322         $htmlpage .= "  <td class='checking'>" . ($future - $now) . "</td>\n";
323         #$htmlpage .= " -- <b>CHECK</b> [$now/" . ($future - $now) . "]";
324         printf $nagios_cmd "[%lu] SCHEDULE_FORCED_SVC_CHECK;%s;%s;%lu\n", $now, $hostname, $service, $now;
325         # delay future command
326         push @futurecheck, sprintf "[%lu] SCHEDULE_FORCED_SVC_CHECK;%s;%s;%lu", $future, $hostname, $service, $future;
327         }
328
329      ACTION_PUSH_AND_DEPEND:
330      for my $act_name (keys %{$config->{'remote-action'}}) {
331         my $act_regex  = $config->{'remote-action'}{$act_name}{'regex'};
332         my $act_status = $config->{'remote-action'}{$act_name}{'status'} || 'ALL';
333         my $act_depend = $config->{'remote-action'}{$act_name}{'depend'} || 'SSH';
334
335         if ($service =~ m/$act_regex/ and ($act_status eq 'ALL' or $status =~ m/$act_status/)) {
336            $remote_db{$act_name} ||= [];
337            push @{$remote_db{$act_name}}, $hostname;
338            $remote_flag++;
339            }
340
341         # check depend service otherwise
342         $remote_sshdown{$act_depend} ||= {};
343         $remote_sshdown{$act_depend}->{$hostname}++ if $service =~ m/$act_depend/;
344         }
345
346      $htmlpage .= " </tr>\n";
347      }
348
349   $htmlpage .= "</table>\n";
350   close $nagios_cmd;
351
352   # host down
353   if (%hostdown) {
354      $htmlpage .= "<br />\n";
355      $htmlpage .= "<table border='1'>\n";
356      HOST_DOWN:
357      for my $host (sort keys %hostdown) {
358         my $host_stat = $hostdown{$host};
359         my $hostname = $host_stat->host_name;
360         my $downtime = downtime($host_stat->last_state_change);
361         my $color = alertcolor('CRITICAL', $downtime);
362         $htmlpage .= " <tr style='background:$color'>\n";
363         $htmlpage .= "  <td class='hoop'><a href=\"$config->{'nagios-server'}{'status-cgi'}?host=" . uri_encode($hostname) . "\">$hostname</a></td>\n";
364         my @host_service;
365         for my $srv ($log->list_services_on_host($host)) {
366            push @host_service, $log->service($host, $srv)->service_description;
367            }
368         $htmlpage .= "  <td><small>" . join(', ', @host_service) . "</small></td>\n";
369         $htmlpage .= "  <td style='text-align:right;'>$downtime days</td>\n";
370         $htmlpage .= " </tr>\n";
371         }
372      $htmlpage .= "</table>\n";
373      }
374
375   # remote action
376   if ($remote_flag) {
377      require Nagios::Object::Config;
378      my $parser = Nagios::Object::Config->new();
379      $parser->parse("/var/cache/nagios3/objects.cache");
380
381      $htmlpage .= "<div class='action'>\n";
382      REMOTE_ACTION:
383      for my $act_name (keys %remote_db) {
384         my $act_depend = $config->{'remote-action'}{$act_name}{'depend'} || 'SSH';
385
386         my @action = grep !exists $remote_sshdown{$act_depend}->{$_}, @{$remote_db{$act_name}};
387         if (@action) {
388            my $srv_title = $config->{'remote-action'}{$act_name}{'title'} || "Action: $act_name";
389            $htmlpage .= "<h2>$srv_title</h2>\n";
390            $htmlpage .= "<pre>\n";
391            my $remote_action = $config->{'remote-action'}{$act_name}{'command'};
392            $remote_action = $config->{'remote-action'}{$act_name}{'command-one'}
393               if @action == 1 and exists $config->{'remote-action'}{$act_name}{'command-one'};
394            my @hosts;
395            for my $host (@action) {
396               my $object = $parser->find_object("$host", "Nagios::Host");
397               push @hosts, hostmapping($object->address =~ s/\..*$//r);
398               }
399            my $hosts_list = join ' ', @hosts;
400            $htmlpage .= ' ' . $remote_action =~ s{\%m}{$hosts_list}r;
401            $htmlpage .= "</pre>\n";
402            }
403         }
404      $htmlpage .= "</div>\n";
405      }
406   }
407
408$htmlpage .= <<"ENDH";
409<hr clear="all" />
410<div class="footer">
411 <b><a href="http://servforge.legi.grenoble-inp.fr/projects/soft-trokata/wiki/SoftWare/NagiosVelvice">Velvice</a>
412   - version: $VERSION</b>
413   (<a href="http://servforge.legi.grenoble-inp.fr/pub/soft-trokata/nagios-velvice/velvice.html">online manual</a>)
414   - Written by Gabriel Moreau
415 <ul>
416  <li>Licence GNU GPL version 2 or later and Perl equivalent</li>
417  <li>Copyright (C) 2014-2018, LEGI UMR 5519 / CNRS UGA G-INP, Grenoble, France</li>
418 </ul>
419</div>
420</body>
421ENDH
422
423$htmlpage .= <<"ENDH" if not $cgi_only;
424</html>
425ENDH
426
427print $htmlpage;
428
429# delayed future check
430if (@futurecheck) {
431   sleep 2;
432   my $nagios_cmd;
433   open $nagios_cmd, '>>', $config->{'nagios-server'}{'nagios-cmd'} or die "Can't open file filename: $!";
434   print $nagios_cmd "$_\n" for @futurecheck;
435   close $nagios_cmd;
436   }
437
438__END__
439
440
441=head1 NAME
442
443velvice.cgi - nagios velvice alert panel
444
445=head1 USAGE
446
447 velvice.cgi
448 velvice.cgi?check=XXX
449
450
451=head1 DESCRIPTION
452
453=begin html
454
455<img width="700" alt="Nagios Velvice Alert Panel" title="Nagios Velvice Alert Panel" style="float:right" src="velvice.png" />
456
457=end html
458
459Nagios VELVICE is an acronym for "Nagios leVEL serVICE status".
460
461The Nagios web page is sometimes very graphically charged
462and does not necessarily contain the information you need at a glance.
463For example, it is quite complicated to restart controls on multiple hosts in one click.
464
465For example, a server that is down should take only one line and not one per service...
466Similarly, a service that has been down for 5 minutes or since yesterday
467has more weight than a service that has fallen for 15 days.
468
469With Velvice Panel, a broken down server takes only one line.
470Services that have been falling for a long time gradually lose their color and become pastel colors.
471
472With Velvice Panel, it is possible through a single click
473to redo a check of all services that are in the CRITICAL state.
474Similarly, it is possible to restart a check on all SSH services in breakdowns ...
475In order not to clog the Nagios server, checks are shifted by 2 seconds in time.
476
477There is also a link to the web page of the main Nagios server.
478For each computer, you have a direct link to its dedicated web page on this server.
479
480
481=head1 CONFIGURATION FILE SPECIFICATION
482
483The configuration file must be F</etc/nagios3/velvice.yml>.
484This is not a required file.
485The file is in YAML format because this is a human-readable text file style.
486Other formats could have been Plain XML, RDF, JSON... but they are much less readable.
487
488You can find in the software nagios-velvice an example of configuration:
489L<velvice.sample.yml|http://servforge.legi.grenoble-inp.fr/pub/soft-trokata/nagios-velvice/velvice.sample.yml>.
490This one is in fact the master reference specification!
491
492The main keys C<nagios-server> and C<color-downtime> have good default values.
493No secondary key is required...
494The Velvice script try hard to replace ~ by the good value automatically.
495
496 nagios-server:
497   status-file: /var/cache/nagios3/status.dat
498   nagios-cmd:  /var/lib/nagios3/rw/nagios.cmd
499   portal-url:  ~/nagios3/
500   status-cgi:  ~/cgi-bin/nagios3/status.cgi
501   stylesheets: ~/nagios3/stylesheets
502
503The background color of the faulty service line display remains stable with a bright color for at least 3 days.
504Then, it decreases and becomes pastel after 53 days with an intensity of 70% (100% is white and 0% is black).
505
506 color-downtime:
507   day-min:  3
508   day-max: 50
509   factor:   0.7
510
511With key C<host-mapping>,
512it's good to map C<localhost> to the real name of the computer (hostname).
513
514 host-mapping:
515   localhost:  srv-nagios
516   toto:       titi
517
518The only important key is C<remote-action>.
519You can affiliate as many subkeys as you want.
520Let's take an example:
521
522 remote-action:
523   oom-killer:
524     regex: ^OOM Killer
525     title:  OOM Killer
526     command:     tssh -c 'sudo rm /var/lib/nagios3/nagios_oom_killer.log' %m
527     command-one: ssh %m 'sudo rm /var/lib/nagios3/nagios_oom_killer.log'
528     depend: ^SSH
529     status: ALL
530     style: bold
531
532C<oom-killer> is just a key for your remote action.
533The regex is used to find which service has a problem...
534The title is use in the result web page (not mandatory - otherwise, it will be C<Action: oom-killer>).
535The C<command> is just written on this web page.
536You have the responsibility to copy / cut it on a terminal.
537For security reasons, the nagios server does not have the right to launch the command on the remote host.
538The wildcard C<%m> is replaced by the list of the host (separated by the space).
539Sometime, the command could be different if there is only one computer (just SSH and no parallel SSH).
540If your command is based on SSH,
541you can have an SSH action only if the remote SSH is running.
542So you can make the remote action depend on the SSH service through a regular expression of your choice.
543
544The last two keys.
545The C<status> key is for CRITICAL or WARNING (or ALL).
546The key C<style> is there to mark in bold the service in error on the web page.
547
548=head1 SEE ALSO
549
550yamllint(1), ysh(1), YAML, Nagios::StatusLog, Color::Calc
551
552In Debian GNU/Linux distribution, packages for C<yamllint> and C<ysh> are:
553
554=over
555
556=item * C<yamllint> - Linter for YAML files (Python)
557
558=item * C<libyaml-shell-perl> - YAML test shell (Perl)
559
560=back
561
562
563Own project ressources:
564
565=over
566
567=item * L<Web Site|http://servforge.legi.grenoble-inp.fr/projects/soft-trokata/wiki/SoftWare/NagiosVelvice>
568
569=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/soft-trokata/nagios-velvice/velvice.html>
570
571=item * L<SVN Repository|http://servforge.legi.grenoble-inp.fr/svn/soft-trokata/trunk/nagios-velvice>
572
573=item * L<Debian Package|http://servforge.legi.grenoble-inp.fr/pub/soft-trokata/nagios-velvice/download/>
574
575=back
576
577
578=head1 VERSION
579
580$Id: velvice.cgi 369 2018-11-03 19:07:36Z g7moreau $
581
582
583=head1 AUTHOR
584
585Written by Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>, LEGI UMR 5519, CNRS, Grenoble - France
586
587
588=head1 LICENSE AND COPYRIGHT
589
590Licence GNU GPL version 2 or later and Perl equivalent
591
592Copyright (C) 2014-2018, LEGI UMR 5519 / CNRS UGA G-INP, Grenoble, France
Note: See TracBrowser for help on using the repository browser.