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

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