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

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