#!/usr/bin/perl -T 
#
# all-in-one, no frills, just the information you need, web page for mon
#
# If you want an interactive version with buttons and stuff, you want
# to use Art Chan's package!
#
# To configure, adjust $conf{monserver}, $conf{monport}, and $REFRESH below
# 
# Jim Trocki, trockij@transmeta.com
#
#
#    Copyright (C) 1998, Jim Trocki, Gilles Lamiral
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

# $Id: my-mon.cgi,v 1.1 1998/11/30 01:07:30 trockij Exp $

# Here is what will be done :
#


use Socket;
use CGI::Carp qw(fatalsToBrowser);
use CGI qw(:standard :html3);
use English;
use Getopt::Long;

# Define here where is the local libraries
use lib ('../../lib');
use Translation ('load', 'translate', 'language');

# analyse the command options

GetOptions("configfile=s" => \$configurationFile,
	   "debug"        => \$debug);

# sets some default values
$configurationFile = 
  ($configurationFile) ? $configurationFile :"./mon-cgi.conf";

%conf = (
	messagesFile	  => './messages.conf',
	monserver	  => 'localhost',
	monport 	  => '32777',
	refresh 	  => 'none',
	scrollGroupLength => '3',
	scrollHostLength  => '10',
	blue		  => '#8888ff',
	green		  => '#88e088',
	blue		  => '#8888ff',
	red		  => '#ff8888',
	yellow  	  => '#ffff44',
	historicLength    => '20',
	historic 	  => 'no',
	cookieExpiration  => '+10y'
	);

$query = new CGI;

# Read the configuration file, if any
# So, some default value are erased.
# If the file can not be open then go on.

readTheConfigurationFile($configurationFile, \%conf);

$WORD = '[a-zA-Z0-9_-]+';
$SIG{'PIPE'} = 'IGNORE';

# recover the "preferences" cookie.

$cookieName = "mon-" . $conf{'monserver'} . $conf{'monport'} ;

%cookie = cookie($cookieName);
%preferences = %cookie;

# Simple chechboxes need special traitment
foreach ('historic') {
	if (param){
		param(-Name=>$_, -Value=>'no') unless param(-Name=>$_);
	}else{
		param(-Name=>$_,
			-Value=>$preferences{$_});
	
	}
	$preferences{$_} = param(-Name=>$_);
}


# update the preferences with the user choice, or the cookie, or the conf
foreach ('refresh',
         'language',
	 'scrollGroupLength',
	 'scrollHostLength',
	 'historicLength',
	 'cookieExpiration',
	) {
  $preferences{$_} = (defined(param($_))) 
    ? param($_)
      : (defined($preferences{$_})) ? $preferences{$_} : $conf{$_};
}

# I do not like systematic cookies so:
# Test if we need to refresh the cookie
$diff = diff(\%cookie, \%preferences);

$time = time;
$localtime = localtime(time);

$| = 1;

$the_cookie = cookie(-name=>$cookieName,
		     -value=>\%preferences,
		     -expires=>$preferences{'cookieExpiration'}
		    );

$corpus = load($conf{'messagesFile'});
$corpus->language($preferences{'language'});

# do we have to send the refresh time ?
if ($preferences{'refresh'} eq "none"){
	# do we need to refresh the cookie
	if ($diff){
		print header(
		-expires=>'now',
		-cookie=>$the_cookie);
	}else{
		print header();
	}
  }else{
	if ($diff){
		print
		  header(-expires=>'now',
			 -Refresh=>$preferences{'refresh'},
			 -cookie=>$the_cookie);
	}else{
	       print
	         header(-expires=>'now',
	        	-Refresh=>$preferences{'refresh'});
	}
  };

#CGI::delete_all();

print
  start_html(-Title=>$corpus->translate('The Minotaur is watching'),
	     -Author=>'lamiral@mail.dotcom.fr',
	     -Meta=>{'keywords'=>$corpus->translate('monitoring, HTTP, FTP, NNTP, LDAP, POP3, SMTP, IMAP4'),
		     'copyright'=>
		     "Gnu Public Licence, copyleft September 1998 Jim Trocky, Gilles Lamiral"},
	     -BGCOLOR=>'#c0c0c0',
	    ),"\n";

#foreach $key (keys(%preferences)) {
#print 
#       "diff	 : $diff<BR>\n",
#       "cookie: ","[",$key,"]",$cookie{$key}, "<BR>\n",
#       "prefer: ","[",$key,"]",$preferences{$key},"<BR>\n";
#}


#
# set up TCP socket
#

# byebye if we can not resolve the hostname

$iaddr = inet_aton ($conf{monserver}) or  
  html_die ("could not resolve hostname : $conf{monserver}\n");

if ($conf{monport} =~ /\D/) { $conf{monport} = getservbyname ($conf{monport}, 'tcp') }
$paddr = sockaddr_in ($conf{monport}, $iaddr);
$proto = getprotobyname ('tcp');

# byebye if 

if (!defined(socket (MON, PF_INET, SOCK_STREAM, $proto))) {
    html_die ("could not create socket: $!\n");
}

# byebye if 

if (!defined(connect (MON, $paddr))) {
    &html_die ("could not connect: $!\n");
}

select (MON); $| = 1; select (STDOUT);


#
# get what is disabled
#
($ret, @disabled) = &do_cmd(MON, "list disabled");
if (!defined($ret)) {
  html_die ("oops--unexpected error: $!\n");
} elsif ($ret !~ /^220/) {
  html_die ("oops--list disabled returned:\n    $ret\n");
}

foreach $l (@disabled) {
  my(@list);
    if ($l =~ /^group ($WORD): (.*)$/) {
      $group = $1;
      $hostDisable{$group}=$2;
    }elsif ($l =~ /^watch ($WORD)$/) {
      $group = $1;
      $watch{$group} = 1;
      $Watch{$group}{'function'} = "disabled";
      next;
    } elsif ($l =~ /^watch ($WORD) service ($WORD)$/) {
      $group = $1;
      $service = $2;
      $service{$group}{$2} = 1;
      $Watch{$group}{'service'}{$service}{'function'}="disabled";
      next;
    }
  }


#
# get the current operational status
#
($ret, @opstatus) = &do_cmd(MON, "list opstatus");
if (!defined($ret)) {
    &html_die ("oops--unexpected error: $!\n");
} elsif ($ret !~ /^220/) {
    &html_die ("oops--list opstatus returned:\n    $ret\n");
}

foreach $l (@opstatus) {
    if ($l !~ /^($WORD)\s+($WORD)\s+(\d+)\s+(\d+)\s+(.*)$/) {
    	next;
    }

    ($group, $service, $t, $next, $rest) = split (/\s+/, $l, 5);
    $Watch{$group}{'service'}{$service}{'last'}=$t;
    $Watch{$group}{'service'}{$service}{'next'}=$next;
    $Watch{$group}{'service'}{$service}{'status'}=$rest;
    
    
    if ($watch{$group} == 1) {
    	push (@dis_watch, $l);
	next;
    } elsif ($service{$group}{$service} == 1) {
    	push (@dis_service, $l);
	next;
    } elsif ($rest =~ /\Afailed/) {
    	push (@failures, $l);
	next;
    } elsif ($rest =~ /\Auntested/) {
    	push (@untested, $l);
	next;
    } else {
    	push (@op, $l);
	next;
    }
}


#
# get the group members
#

foreach $i (@failures, @dis_watch, @dis_service, @untested, @op) {
  
  ($group, $service, $rest) = split (/\s+/, $i);
  
  next if (defined($groups{$group}));
  
  ($ret, @l) = &do_cmd(MON, "list group $group");
  if (!defined($ret)) {
    &html_die ("oops--unexpected error: $!\n");
  } elsif ($ret !~ /^220/) {
    &html_die ("oops--list group returned:\n    $ret\n");
  }
  
  ($x, $group, $hosts) = split (/\s+/, $l[0], 3);
  @hosts =  split (/\s+/,$hosts);
  $Watch{$group}{'hosts'}=[@hosts];
  
  foreach $host (@hosts) {
    if ($host =~ /\A\*(.*)/) {
      push ( @{$Watch{$group}{'disable'}}, $1);
      
    }else{
      push ( @{$Watch{$group}{'enable'}}, $host);
    }
    $maxLengthHost = max($maxLengthHost, length($host));
  };
  $groups{$group} = $hosts;
}

# get the alert historic if needed
if ($preferences{'historic'} eq 'yes') {
  ($ret, @alertHistoric) = &do_cmd(MON, "list alerthist");
  if (!defined($ret)) {
    &html_die ("oops--unexpected error: $!\n");
  } elsif ($ret !~ /^220/) {
    &html_die ("oops--list group returned:\n    $ret\n");
  }
	
}


# all done
#
&do_cmd(MON, "quit");
if (!defined($ret)) {
    &html_die ("oops--unexpected error: $!\n");
} elsif ($ret !~ /^220/) {
    &html_die ("oops--quit returned:\n    $ret\n");
}
close (MON);


# Try to make all things clear...

# First  : What are the groups ?
@group = keys(%Watch);
#print scalar(@group), " groups : ", "@group", "<BR>\n";

# Second : What are the services for each group
foreach $group (@group) {
  @services = keys(%{$Watch{$group}{'service'}});

#  print 
#    "Group $group : ", 
#    scalar(@services) , 
#    " service(s): ", 
#    "@services<BR>\n";


# Third : What are the priorities to show ?
# a) Failures
# b) Disable
# c) Other

# The rules : 
# A) If a failure-disable is on a service the entire group is upped
# B) 

  
  foreach $service (@services) {
    if($Watch{$group}{'service'}{$service}{'function'} eq 'disabled'){
      $Watch{$group}{'service'}{$service}{'order'}{'disabled'}+=1;
      $Watch{$group}{'order'}{'disabled'}+=1;
    }elsif ($Watch{$group}{'service'}{$service}{'status'} =~ /\Afailed.*\Z/){
      $Watch{$group}{'service'}{$service}{'order'}{'failed'}+=1;
      unless($Watch{$group}{'function'} eq 'disabled'){
	$Watch{$group}{'order'}{'failed'}+=1;
      }
    }
    $totalNumberOfServices++;
  }
  #  print 
  #    "$group Order : ", 
  #    $Watch{$group}{'order'}{'failed'},
  #    ":", 
  #    $Watch{$group}{'order'}{'disabled'}, 
  #    "<BR>\n";
}

# Now we make a list of disable hosts
#print dump();

foreach $groupList (values(%groups)) {
  foreach $host (split(/\s+/, $groupList)){
    $host =~ /\A\*?(.*)\Z/;
    $hostWithoutStar = $1;
    next if (defined ( $hostList{$hostWithoutStar}));
    # Here the star "*" is eliminated.
    if ($host =~ /\A\*(.*)\Z/) {
      $hostList{$hostWithoutStar}='';
      push (@hostListDisable, $hostWithoutStar);
    }else{
      $hostList{$host}='enable';
    }
  }
}
# Now the table

print 
  startform,
  "<CENTER>\n",
  "<table border=3>\n",
  
  # The first line
  "<th>", $corpus->translate('Host') ,
  "<th>", $corpus->translate('Group') ,
  "<th>", $corpus->translate('Members') ,
  "<th>", $corpus->translate('Service') ,
  "<th>", $corpus->translate('Last at') ,
  "<th>", $corpus->translate('Next in') ,
  "<th>", $corpus->translate('Status');

print 
  "<TR>",
  "<TD rowspan=$totalNumberOfServices>",
  scrolling_list(-name=>"Enable_Host_CompleteListe",
		 -Values=>[sort { 
		   $hostList{$a} cmp $hostList{$b} 
		   or
		     $a cmp $b
		 }(keys(%hostList))],
		 -Default=>[@hostListDisable],
		 -multiple=>'true',
		 -size=>min($preferences{'scrollHostLength'}, 
			    scalar(keys(%hostList))),
		),	
  ;

foreach $group (sort { 
  # first a failure
  $Watch{$b}{'order'}{'failed'} <=> $Watch{$a}{'order'}{'failed'}
  or
    # a disabled watch
  $Watch{$b}{'function'} cmp $Watch{$a}{'function'}
  or
    # disabled services
  $Watch{$b}{'order'}{'disabled'} <=> $Watch{$a}{'order'}{'disabled'}
  or 
    # Alphabetic
    $a cmp $b
} 
		keys(%Watch)  ) {
  $numberOfServices = scalar(keys(%{$Watch{$group}{'service'}}));
  if ($Watch{$group}{'function'}){
    $groupColor = "bgcolor=".$conf{'blue'};
  }elsif($Watch{$group}{'order'}{'failed'}){
    $groupColor = "bgcolor=".$conf{'red'};
  }else{
    $groupColor = "";
  };
  
  print 
    "<TD rowspan=$numberOfServices align=center $groupColor>", $group,
#    " : ", 
#    $Watch{$group}{'order'}{'failed'}*2**8 + $Watch{$group}{'order'}{'disabled'},
    "<TD rowspan=$numberOfServices  $groupColor>",
    scrolling_list(-name=>"Enable_Host_In_Group_$group",
		   -Values=>[@{$Watch{$group}{'disable'}}, @{$Watch{$group}{'enable'}}],
		   -Default=>[@{$Watch{$group}{'disable'}}],
		   -multiple=>'true',
		   -size=>min($preferences{'scrollGroupLength'}, 
			      scalar(@{$Watch{$group}{'hosts'}})),
		  ),
    ;
  foreach $service ( sort { 
    $Watch{$group}{'service'}{$b}{'order'}{'failed'}
    <=>  
      $Watch{$group}{'service'}{$a}{'order'}{'failed'}
    or 
      $Watch{$group}{'service'}{$b}{'order'}{'disabled'}
    <=>  
      $Watch{$group}{'service'}{$a}{'order'}{'disabled'}
    or 
      $a cmp $b
    
  }keys(%{$Watch{$group}{'service'}})) {
    
    my(	$statusService,
    	$timeStringLast,
    	$timeStringNext,
	$ServiceColor
	);
    
    $statusService = $Watch{$group}{'service'}{$service}{'status'};
    
    if($Watch{$group}{'function'} eq 'disabled'){
      $ServiceColor = "bgcolor=".$conf{'blue'};
    }elsif($Watch{$group}{'service'}{$service}{'function'} eq 'disabled'){
      $ServiceColor = "bgcolor=".$conf{'blue'};
    }elsif($Watch{$group}{'service'}{$service}{'order'}{'failed'}){
      $ServiceColor =   "bgcolor=".$conf{'red'};
    }elsif($statusService =~ /\Auntested\Z/){
	$ServiceColor = ($groupColor) ? "bgcolor=".$conf{'yellow'} :  "";
	$ServiceColor = "bgcolor=".$conf{'yellow'};
    }else{
      $ServiceColor = ($groupColor) ? "bgcolor=".$conf{'green'} :  "";
    };
      if ($statusService =~ /succeeded/) {
      	$statusService=$corpus->translate("succeeded");
      };
	if ($statusService =~ /untested/) {    
    		$statusService=$corpus->translate("untested");
	};
	
	$timeLast = $Watch{$group}{'service'}{$service}{'last'};
	($ls,$lm,$lh) = (localtime ($timeLast))[0,1,2];
	($ns,$nm,$nh,$nyear) = (gmtime ($Watch{$group}{'service'}{$service}{'next'}))[0,1,2,7];
	$nyear = ($nyear == 0) ? "" : $nyear . "d";
	
	unless ($timeLast) {
		$timeStringLast = $corpus->translate("none");
	}else{
		$timeStringLast = sprintf("%02d:%02d:%02d", $lh, $lm, $ls);
	}
	$timeStringNext = sprintf("%s %02d:%02d:%02d", $nyear, $nh, $nm, $ns);
	
    print  
      "<TD $ServiceColor>", 
      $service;
	print "<TD $ServiceColor>$timeStringLast",
		"<TD $ServiceColor>$timeStringNext",
		"<TD $ServiceColor>", $statusService,
		"<TR>";
  }
};

print
  "<th>", 
  $corpus->translate("Length "),
  textfield(-name=>'scrollHostLength',
            -default=>$preferences{'scrollHostLength'},
            -override=>1,
            -size=>length($preferences{'scrollHostLength'})+1,
),

  "<th>", $corpus->translate('Group'),
  "<th>",
  $corpus->translate("Length "),
  textfield(-name=>'scrollGroupLength',
            -default=>$preferences{'scrollGroupLength'},
            -override=>1,
            -size=>length($preferences{'scrollGroupLength'})+1,
	),
  
  "<th>", $corpus->translate('Service') ,
  "<th>", $corpus->translate('(H:M:S)') ,
  "<th>", $corpus->translate('(H:M:S)') ,
  "<th>", $corpus->translate('Status'),
  "\n</table>",

  "<BR>\n",
	"</CENTER>\n",
  "<P><CENTER>\n",

  "<TABLE border=2>",
  
  "<CAPTION>", $corpus->translate('Preferences'), "</CAPTION>\n",
  "<TR>",
  "<TH>", $corpus->translate('Refresh'), 
  "<TH>", $corpus->translate('Language'),
  "<TH>",checkbox(-name=>'historic',
		-value=>'yes',
		-label=>$corpus->translate(' Historic')),
  "<TR>",
  "<TD>", 
  popup_menu(-name=>'refresh',
		-Values=>['none',
		       '60',
		       '120',
		       '240',
		       '480',
		       '960',
		      ],
		      
		-Labels=>{'none'=>$corpus->translate('none'),
			 '60' =>'1 min',
			 '120'=>'2 min',
			 '240'=>'4 min',
			 '480'=>'8 min',
			 '960'=>'16 min',
			 },
	     -default=>$preferences{'refresh'}
	    ),
  "<TD>", 
  popup_menu(-name=>'language',
	       -Values=>['Francais',
		       'English',
		      ],
	       -default=>$preferences{'language'}
	      ),
  "<TD>", 
  textfield(-name=>'historicLength',
            -default=>$preferences{'historicLength'},
            -override=>1,
            -size=>length($preferences{'historicLength'})+1,
	),
	      
  "</TABLE>\n",
  submit(-Name=>'Go',
	 -Value=>$corpus->translate("Submit")
	),
  reset(-Value=>$corpus->translate("Cancel")),
  "</CENTER>\n",
  
  
  endform,
  "\n";

	historique() if ($preferences{'historic'} eq 'yes');

 #print dump(),
 end_html();

exit;


#
# do a command, returning an array full of the output,
# where the first element is the status of the command,
# or undef on error
#
sub do_cmd {
    my ($fd, $cmd) = @_;
    my ($l, @out);

    @out = ();
    print $fd "$cmd\n";
    while (defined($l = <$fd>)) {
	chomp $l;
    	if ($l =~ /^(\d+)/) {
	    last;
	}
	push (@out, $l);
    }

    ($l, @out);
}


#
# die with some HTML output
#
sub html_die {
    my ($str) = @_;
    print <<EOF;
Content-Type: text/html

<html>
<head>
<title> opstatus failure </title>
</head>
<pre>

opstatus failed with the following diagnostics:

$str

</pre>
</html>
EOF
    exit(1);
}
  
sub max {
  my $max = shift(@_);
  foreach $foo (@_) {
    $max = $foo if $max < $foo;
  }
  return $max;
}

###############################################################################
sub min {
  my $min = shift(@_);
  foreach $foo (@_) {
    $min = $foo if $min > $foo;
  }
  return $min;
}
###############################################################################
sub readTheConfigurationFile {
  local($configurationFile, *paramConf) = @_;

  $debug and print "entering readTheConfigurationFile...\n";

  open(CONF,$configurationFile) or return "Can not open $configurationFile : $!";
  while (<CONF>) {
    next if /^#/;
    next if /^\s*$/;
    if (/^[ \t]*(\w+)[ \t]+:(.*)$/){
      $paramConf{$1}=$2;
      $debug and print "  $1:[$2]\n";
    } else 
      {
	warn "Syntax error in the configuration File : $configurationFile",
	" line $INPUT_LINE_NUMBER\n",
	"You have to use only alphanumeric terms\n",
	"A correct syntax example :\n",
	" ResultLengthMax   500  \n";
      }
  }
  close(CONF);
  $debug and print "leaving readTheConfigurationFile\n";
  return("");
}

###############################################################################
sub diff {
	local (*listA, *listB) = @_;
		
	return('1') unless (scalar(%listA) == scalar(%listB));
	foreach $key (%listA) {
		return('1') unless ($listA{$key} eq $listB{$key});
	};
	return(0);
};



# List alert history --------------------------------------------------
sub historique {
	print 
		hr,
		"<p><table border=1  align=center>\n",
		"<CAPTION><BIG>", $corpus->translate(' Historic'), "</BIG></CAPTION>\n",
		"<tr>",
                "<th>",$corpus->translate('Service'),
                "<th>", $corpus->translate('Group'),
                "<th>", $corpus->translate('Date'),
                "<th>", $corpus->translate('Alert'),
                "<th>", $corpus->translate('Status'),
                "\n";
		
	@alertHistoric = reverse(@alertHistoric);
	# I do not understand why splice(reverse(@alertHistoric)) does not work.
    foreach $ligne  (splice(@alertHistoric, 0, $preferences{'historicLength'})) {
      my($groupName, 
         $serviceName, 
         $timeNumeric, 
         $alertFile, 
         $argList, 
         $hostList,
         $timeString);
	
	chomp($ligne);
      
      $ligne =~ /\A(\S+)\s+(\S+)\s+(\d+)\s+(\S+)\s+\((.*?)\)\s+(.*)\Z/;
      ($groupName, $serviceName, $timeNumeric, $alertFile, $argList, $hostList) = 
        ($1, $2, $3, $4, $5, $6);
      @hostList = split(/\s+/,$hostList);
      
      $timeString = localtime($timeNumeric);
      print("<tr>");
      if ($hostList eq "(NO SUMMARY)") {
        print("<td><font color=green>$serviceName</font></td>\n");
      }else{
        print("<td><font color=red>$serviceName</font></td>\n");
      }
      
	print("<td>$groupName</a>",
		"<td nowrap>$timeString",
		"<td>$alertFile\n");

       if ($hostList eq "(NO SUMMARY)") {
         print("<td align=center>OK");
       }else{
         print("<td align=center>");
         foreach $hostElement (@hostList){
           print("$hostElement <BR>");
         }
       }
 
    }
	print 
		"</table>\n",
		"<p><hr>";    
}
 
