#!/usr/bin/perl
#
# monshow - concise, user view-based opstatus summary
#
# Jim Trocki, trockij@transmeta.com
#
# $Id: monshow,v 1.12.2.9 1999/11/16 11:35:07 trockij Exp $
#
#    Copyright (C) 1998, Jim Trocki
#
#    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
#

use strict "vars";

use Getopt::Long;
use English;
use CGI;

use Mon::Client;

#
# forward declarations
#
sub usage;
sub get_auth;
sub read_cf;
sub err_die;
sub secs_to_hms;
sub display_allok;
sub compose_detail;
sub compose_group;
sub compose_header;
sub compose_table;
sub compose_trailer;
sub get_client_status;

my %OPSTAT = %Mon::Client::OPSTAT;
my $WORD = '[a-zA-Z0-9_-]+';
my $OUT_BUF = "";
my $e;
$= = 1000;
$SIG{INT} = \&handle_sig;
$SIG{TERM} = \&handle_sig;

my ($DEP, $GROUP, $SERVICE, $STATUS, $TIME, $NEXT, $ALERTS, $SUMMARY);

my %opt;
GetOptions (\%opt, "showall", "auth", "help", "full", "disabled",
	"rcfile=s", "login=s", "server=s", "port=i", "prot=s", "old");

my $CGI;
my %QUERY_ARGS;
if (defined $ENV{"REQUEST_METHOD"})
{
    $CGI = new CGI;
}

if (!$CGI && $opt{"help"})
{
    usage;
    exit 1;
}

my $CF = {
    full		=> 0,
    show-disabled	=> 0,
    bg_ok		=> "00a080",
    bg_fail		=> "ff4040",
    bg_untested		=> "ffffff",
};

my ($e, $what) = read_cf ($CF);

if ($e ne "")
{
    err_die ("while reading config file, $e");
}

#
# cmdline args override config file
#
if (!$CGI)
{
    $CF->{"all"}		= 1 if ($opt{"showall"});
    $CF->{"show-disabled"}	= 1 if ($opt{"disabled"});
    $CF->{"full"}		= 1 if ($opt{"full"});
    $CF->{"host"}		= $opt{"server"} if ($opt{"server"});
    $CF->{"port"}		= $opt{"port"} if ($opt{"port"});
    $CF->{"prot"}		= $opt{"prot"} if ($opt{"prot"});
}

else
{
    foreach my $e (split (/\?/, $ENV{"QUERY_STRING"}))
    {
    	my ($var, $val) = split (/=/, $e);
	$QUERY_ARGS{$var} = $val;
    }
}

#
# retrieve client status
#
my ($e, $state, $opstatus, $disabled, $deps, $groups) = get_client_status;

if ($e ne "")
{
    err_die ($e);
}

my $rows = select_table ($what, $state, $opstatus, $disabled, $deps);

compose_header ($state);

if ($CGI && !$QUERY_ARGS{"detail"})
{
    compose_table ($rows, $opstatus, $disabled, $deps);
    compose_disabled ($disabled);
}

elsif ($CGI && $QUERY_ARGS{"detail"})
{
    compose_detail ($QUERY_ARGS{"detail"}, $opstatus, $disabled, $deps);
}

elsif ($CGI && $QUERY_ARGS{"group"})
{
    compose_group ($QUERY_ARGS{"group"}, $groups);
}

elsif (!$CGI)
{
    compose_table ($rows, $opstatus, $disabled, $deps);
    compose_disabled ($disabled);
}

compose_trailer;

if ($CGI)
{
    print <<EOF;
Content-type: text/html

$OUT_BUF
EOF
}

else
{
    print "\n";
}

exit;

#-----------------------------------------------------------------------------

format STDOUT_TOP =

  GROUP           SERVICE      STATUS      LAST       NEXT       ALERTS SUMMARY
.


#
# usage
#
sub usage
{
    print <<EOF;

usage: monshow [--auth] [--showall] [--full] [--login user] [--disabled]
               [--server host] [--port num] [--rcfile file]
    --showall      do not read rcfile and show opstatus of all services
    --full         show disabled, failures, successes, and untested
    --auth         authenticate to mon server
    --disabled     show disabled
    --prot ver     set protocol version, must match 1.2.3 format
    --old          use old protocol (0.37.0) and old port (32777)
    --login user   use "login" as username while authenticating
    --server host  use "host" as monhost, instead of MONHOST
    --port num     use "num" as port number instead of default
    --rcfile file  use "file" as config file instead of \$HOME/.monshowrc

EOF
}


#
# signal handler
#
sub handle_sig
{
    system "stty echo" unless ($CGI);
    exit;
}


#
# get auth info
#
# returns a list "error", "login", "password"
#
sub get_auth
{
    my ($login, $pass);

    if ($CGI)
    {
    	$login = $CGI->query ("login");
    	$pass  = $CGI->query ("password");
    }

    else
    {
	if ($opt{"login"})
	{
	    $login = $opt{"login"};
	}

	else
	{
	    return "could not determine username"
	    	if (!defined ($login = getpwuid($EUID)));
	}

	if (-t STDIN)
	{
	    system "stty -echo";
	    print "Password: ";
	    chop ($pass = <STDIN>);
	    print "\n";
	    system "stty echo";
	    return "invalid password" if ($pass =~ /^\s*$/);
	}

	else
	{
	    my $cmd;

	    while (defined ($cmd = <>))
	    {
		chomp $cmd;
		if ($cmd =~ /^user=(\S+)$/i)
		{
		    $login = $1;
		}

		elsif ($cmd =~ /^pass=(\S+)$/i)
		{
		    $pass = $1;
		}

		last if (defined ($login) && defined ($pass));
	    }
	}
    }

    return "inadequate authentication information supplied"
	if ($login eq "" || $pass eq "");
    
    return ("", $login, $pass);
}

#
# config file
#
sub read_cf
{
    my $CF = shift;

    my ($group, $service);
    my @RC;

    my $RC = "/etc/mon/monshowrc";

    if ($CGI)
    {
	if (-f ".monshowrc")
	{
	    $RC = ".monshowrc";
	}
    }

    else
    {
	if ($opt{"rcfile"})
	{
	    $RC = $opt{"rcfile"};
	}

	elsif (-f "$ENV{HOME}/.monshowrc")
	{
	    $RC = "$ENV{HOME}/.monshowrc";
	}
    }

    if ($opt{"old"})
    {
	$CF->{"prot"} = "0.37.0";
	$CF->{"port"} = 32777;
    }

    if (-f $RC)
    {
	open (IN, $RC) || return "could not read $RC: $!";

	while (<IN>)
	{
	    next if (/^\s*#/ || /^\s*$/);
	    chomp;
	    s/^\s*//;
	    s/\s*$//;
	    if (/^set \s+ (\S+) \s* (\S+)?/ix)
	    {
		my $cmd = $1;
		my $arg = $2;
		if ($cmd eq "show-disabled") { }
		elsif ($cmd eq "host") { }
		elsif ($cmd eq "prot") { }
		elsif ($cmd eq "port") { }
		elsif ($cmd eq "full") { }
		elsif ($cmd eq "bg_ok") { }
		elsif ($cmd eq "bg_fail") { }
		elsif ($cmd eq "bg_untested") { }
		else
		{
		    print STDERR "unknown set, line $.\n";
		    next;
		}
		if ($arg ne "")
		{
		    $CF->{$cmd} = $arg;
		}

		else
		{
		    $CF->{$cmd} = 1;
		}
	    }

	    else
	    {
		($group, $service) = split;
		if ($group eq "" || $service eq "")
		{
		    print STDERR "unknown group or service, line $.\n";
		    next;
		}
		push (@RC, [$group, $service]);
	    }
	}
	close (IN);
    }

    return ("", \@RC);
}


sub secs_to_hms
{
    my ($s) = @_;
    my ($dd, $hh, $mm, $ss);

    $dd = int ($s / 86400);
    $s -= $dd * 86400;

    $hh = int ($s / 3600);
    $s -= $hh * 3600;

    $mm = int ($s / 60);
    $s -= $mm * 60;

    $ss = $s;

    if ($dd == 0)
    {
        sprintf("%02d:%02d", $hh, $mm);
    }

    else
    {
        sprintf("%d days, %02d:%02d", $dd, $hh, $mm);
    }
}


#
# exit displaying error in appropriate output format
#
sub err_die
{
    my $msg = shift;

    if ($CGI)
    {
print <<EOF;
Content-type: text/html

<html>
<head>
<title> Error </title>
</head>
<body>
<h2> Error </h2>
<pre>
$msg
</pre>
</body>
</html>
EOF
    }

    else
    {
print <<EOF;
Error: $msg

EOF
    }

    exit 1;
}


#
# everything is cool
#
sub display_allok
{
    if ($CGI)
    {
    	$OUT_BUF .= <<EOF;
<h2> All systems OK </h2>
<p>

EOF
    }

    else
    {
	print "\nAll systems OK\n";
    }
}


#
# client status
#
# return ("", $state, \%opstatus, \%disabled, \%deps);
#
sub get_client_status {
    my $cl;
    if (!defined ($cl = Mon::Client->new))
    {
	return "could not create client object: $@";
    }

    my ($username, $pass);
    if ($opt{"auth"} && !$CGI)
    {
	my $e;
	($e, $username, $pass) = get_auth;
	if ($e eq "")
	{
	    return "$e";
	}
	$cl->username ($username);
	$cl->password ($pass);
    }

    $cl->host ($CF->{"host"}) if (defined $CF->{"host"});
    $cl->port ($CF->{"port"}) if (defined $CF->{"port"});
    $cl->port ($CF->{"prot"}) if (defined $CF->{"prot"});

    $cl->connect;
    if ($cl->error) {
	return "Could not connect to server: " . $cl->error;
    }

    #
    # authenticate self to the server if necessary
    #
    if ($opt{"auth"} && !defined ($cl->login)) {
	my $e = $cl->error;
	$cl->disconnect;
	return "Could not log in: $e";
    }

    #
    # get disabled things
    #
    my %disabled = $cl->list_disabled;
    if ($cl->error) {
	my $e = $cl->error;
	$cl->disconnect;
	return "could not get disabled: $e";
    }

    #
    # get stats
    #
    my ($running, $t) = $cl->list_state;
    if ($cl->error) {
	my $e = $cl->error;
	$cl->disconnect;
	return "could not get state: $e";
    }

    my $state;
    if ($running) {
	$state = $t;
    }

    else
    {
	$state = "scheduler stopped since " . localtime ($t);
    }

    #
    # get opstatus
    #
    my %opstatus = $cl->list_opstatus;
    if ($cl->error) {
	my $e = $cl->error;
	$cl->disconnect;
	return "could not get opstatus: $e";
    }

    #
    # dependencies
    #
    my %deps;
    if ($opt{"deps"}) {
	%deps = $cl->list_deps;
	if ($cl->error) {
	    my $e = $cl->error;
	    $cl->disconnect;
	    return "could not list deps: $e";
	}
    }

    #
    # log out
    #
    if (!defined $cl->disconnect) {
	return "error while disconnecting: " . $cl->error;
    }

    my %groups;

    return ("", $state, \%opstatus, \%disabled, \%deps, \%groups);
}


sub compose_header {
    my $state = shift;

    my $t = localtime;

    #
    # HTML stuff
    #
    if ($CGI)
    {
	$OUT_BUF = <<EOF;
<html>
<head>
<title> Mon Operational Status </title>
</head>
<h1> Operational Status </h1>
<body>

<table>
    <tr>
    	<td align=right> Server: </td>
	<td> $CF->{host} </td>
    </tr>
    <tr>
	<td align=right> Time: </td>
	<td> $t </td>
    </tr>
    <tr>
    	<td align=right> State: </td>
	<td> $state </td>
    </tr>
</table>
<p>
EOF
    }

    else
    {
    	print <<EOF;

     server: $CF->{host}
       time: $t
      state: $state
EOF
    }
}


sub select_table {
    my ($what, $state, $opstatus, $disabled, $deps) = @_;

    my @rows;

    #
    # display everything real nice
    #
    if ($CF->{"all"} || @{$what} == 0)
    {
	foreach my $group (keys %{$opstatus})
	{
	    foreach my $service (keys %{$opstatus->{$group}})
	    {
		push (@rows, [$group, $service]);
	    }
	}
    }

    else
    {
	@rows = @{$what};
    }

    my (%DEP, %DEPROOT);

    foreach my $l (@rows)
    {
	my ($group, $service) = @{$l};

	my $sref = \%{$opstatus->{$group}->{$service}};

	next if (!defined $sref->{"opstatus"});

	#
	# disabled things
	#
	if (defined $disabled->{"watches"}->{$group})
	{
	    next;
	}

	elsif (defined $disabled->{"services"}->{$group}->{$service})
	{
	    next;
	}

	#
	# potential root dependencies
	#
	elsif ($sref->{"depend"} eq "")
	{
	    push (@{$DEPROOT{$sref->{"opstatus"}}}, ["R", $group, $service]);
	}

	#
	# things which have dependencies
	#
	else
	{
	    push (@{$DEP{$sref->{"opstatus"}}}, ["D", $group, $service]);
	}
    }

    if ($CF->{"full"})
    {
	[
	    @{$DEPROOT{$OPSTAT{"fail"}}},
	    @{$DEPROOT{$OPSTAT{"linkdown"}}},
	    @{$DEPROOT{$OPSTAT{"timeout"}}},
	    @{$DEPROOT{$OPSTAT{"coldstart"}}},
	    @{$DEPROOT{$OPSTAT{"warmstart"}}},
	    @{$DEPROOT{$OPSTAT{"untested"}}},
	    @{$DEPROOT{$OPSTAT{"unknown"}}},

	    @{$DEP{$OPSTAT{"fail"}}},
	    @{$DEP{$OPSTAT{"linkdown"}}},
	    @{$DEP{$OPSTAT{"timeout"}}},
	    @{$DEP{$OPSTAT{"coldstart"}}},
	    @{$DEP{$OPSTAT{"warmstart"}}},

	    @{$DEPROOT{$OPSTAT{"ok"}}},
	    @{$DEP{$OPSTAT{"ok"}}},
	    @{$DEP{$OPSTAT{"untested"}}},
	    @{$DEP{$OPSTAT{"unknown"}}},
	];
    }

    else
    {
	[
	    @{$DEPROOT{$OPSTAT{"fail"}}},
	    @{$DEPROOT{$OPSTAT{"linkdown"}}},
	    @{$DEPROOT{$OPSTAT{"timeout"}}},
	    @{$DEPROOT{$OPSTAT{"coldstart"}}},
	    @{$DEPROOT{$OPSTAT{"warmstart"}}},

	    @{$DEP{$OPSTAT{"fail"}}},
	    @{$DEP{$OPSTAT{"linkdown"}}},
	    @{$DEP{$OPSTAT{"timeout"}}},
	    @{$DEP{$OPSTAT{"coldstart"}}},
	    @{$DEP{$OPSTAT{"warmstart"}}},
	];
    }
}


#
# build the table
#
sub compose_table {
    my ($rows, $opstatus, $disabled, $deps) = @_;

    if (@{$rows} == 0)
    {
	display_allok;
	return;
    }

    #
    # display the failure table
    #
    if ($CGI)
    {
	$OUT_BUF .= <<EOF;

<table cellpadding=2 cellspacing=1 bgcolor="#CCCCCC">
<th> Dep <th>Group <th>Service <th> Last check <th> Next check <th> Alerts <th> Status <th> Summary

EOF
    }

    foreach my $l (@{$rows})
    {
	my ($depstate, $group, $service) = @{$l};

	my $sref = \%{$opstatus->{$group}->{$service}};

	$STATUS = "unknown";
	$TIME = "";
	$DEP = $depstate;
	my $last = "";
	my $bgcolor = "";

	if ($sref->{"opstatus"} == $OPSTAT{"untested"})
	{
	    $STATUS = "untested";
	    $TIME = "untested";
	    $last = "last_success";
	    $bgcolor = $CF->{"bg_untested"};
	}

	elsif ($sref->{"opstatus"} == $OPSTAT{"ok"})
	{
	    $STATUS = "-";
	    $last = "last_success";
	    $bgcolor = $CF->{"bg_ok"};
	}

	elsif ($sref->{"opstatus"} == $OPSTAT{"fail"})
	{
	    $STATUS = "FAIL";
	    $last = "last_failure";
	    $bgcolor = $CF->{"bg_fail"};
	}

	if ($depstate eq "")
	{
	    $DEP = "-";
	}

	$GROUP = $group;
	$SERVICE = $service;

	if ($TIME eq "")
	{
	    if (time - $sref->{$last} < 60)
	    {
	    	$TIME = "$sref->{$last} secs";
	    }

	    else
	    {
		$TIME = secs_to_hms (time - $sref->{$last});
	    }
	}

	if ($sref->{"timer"} < 60)
	{
	    $NEXT = "$sref->{timer} secs";
	}

	else
	{
	    $NEXT = secs_to_hms ($sref->{"timer"});
	}

	$SUMMARY = $sref->{"last_summary"};

	$ALERTS = $sref->{"alerts_sent"} || "none";

	my $fmt;
	if (!$CGI)
	{
	    $fmt = <<EOF;
format STDOUT =
@ @<<<<<<<<<<<<<< @<<<<<<<<<<< @<<<<<<<<<  @<<<<<<<   @<<<<<<<<< @<<<   @
EOF
	    chomp $fmt;
	    $fmt .= "<" x length($SUMMARY) . "\n";
	    $fmt .= <<'EOF';
$DEP, $GROUP, $SERVICE, $STATUS, $TIME, $NEXT, $ALERTS, $SUMMARY
.
EOF
	    eval $fmt;
	    write;
	}

	else
	{
	    if ($SUMMARY =~ /^\s*$/)
	    {
		$SUMMARY = "<pre> </pre>";
	    }
	    if ($bgcolor ne "")
	    {
	    	$bgcolor = "bgcolor='#$bgcolor'";
	    }
	    $OUT_BUF .= <<EOF;

<tr $bgcolor>
   <td> $DEP
   <td> $GROUP
   <td> <a href="$ENV{SCRIPT_NAME}/?detail=$group,$service">$SERVICE</a>
   <td> $TIME
   <td> $NEXT
   <td> $ALERTS
   <td> $STATUS
   <td> $SUMMARY
EOF

	}
    }

    if ($CGI)
    {
	$OUT_BUF .= <<EOF;
</table>

EOF
    }
}


sub compose_disabled {
    my $disabled = shift;

    return if (!$CF->{"show-disabled"});

    if (keys %{$disabled->{"watches"}})
    {
	if ($CGI)
	{
	    $OUT_BUF .= <<EOF;

<h2> Disabled Watches </h2>

EOF
	}

	else
	{
	    print "\nDISABLED WATCHES:\n";
	}

	foreach my $watch (keys %{$disabled->{"watches"}})
	{
	    if ($CGI)
	    {
	    	$OUT_BUF .= "$watch <br>\n";
	    }

	    else
	    {
		print "$watch\n";
	    }
	}
    }

    my @disabled_services;
    foreach my $watch (keys %{$disabled->{"services"}})
    {
	foreach my $service (keys %{$disabled->{"services"}{$watch}})
	{
	    push (@disabled_services, "$watch $service");;
	}
    }

    if (@disabled_services)
    {
	if ($CGI)
	{
	    $OUT_BUF .= <<EOF;

<h2> Disabled Services </h2>

EOF
	}

	else
	{
	    print "\nDISABLED SERVICES\n";
	}
	for (@disabled_services)
	{
	    if ($CGI)
	    {
	    	$OUT_BUF .= "$_ <br>\n";
	    }

	    else
	    {
		print "$_\n";
	    }
	}
    }

    if (keys %{$disabled->{"hosts"}})
    {
	if ($CGI)
	{
	    $OUT_BUF .= <<EOF;

<h2> Disabled Hosts </h2>

EOF
	}

	else
	{
	    print "\nDISABLED HOSTS:\n";
	}
	foreach my $group (keys %{$disabled->{"hosts"}})
	{
	    my @HOSTS = ();
	    foreach my $host (keys %{$disabled->{"hosts"}{$group}})
	    {
		push (@HOSTS, $host);
	    }
	    if ($CGI)
	    {
		$OUT_BUF .= sprintf ("%-15s %s <br>\n", $group, "@HOSTS");
	    }

	    else
	    {
		printf ("%-15s %s\n", $group, "@HOSTS");
	    }
	}
    }
}


sub compose_trailer {
    if ($CGI)
    {
    	$OUT_BUF .= <<EOF;
</body>
</html>
EOF
    }
}


sub compose_detail {
    my ($args, $opstatus, $disabled, $deps) = @_;

    my ($group, $service) = split (/,/, $args, 2);

    if (!defined ($opstatus->{$group}->{$service}))
    {
    	err_die ("$group/$service not a valid service");
    }

    my $sref = \%{$opstatus->{$group}->{$service}};

    $OUT_BUF .= <<EOF;

<h2> Detail for $group/$service </h2>

<b> Summary: </b> $sref->{last_summary}
<p>
<b> Detail: </b>
<pre>
$sref->{last_detail}
</pre>

<table cellpadding=2 cellspacing=1 bgcolor="#CCCCCC">
    <tr>
    	<td> opstatus
	<td> $sref->{opstatus}
    <tr>
    	<td> exitval
	<td> $sref->{exitval}
    <tr>
    	<td> depend
	<td> $sref->{depend}
    <tr>
    	<td> monitor
	<td> $sref->{monitor}
    <tr>
    	<td> last_check
	<td> $sref->{last_check}
    <tr>
    	<td> next_check
	<td> $sref->{timer}
</table>

<p>
<a href="$ENV{SCRIPT_NAME}">Back to main page</a>
<p>

EOF

}


sub compose_group {
    my $arg = shift;
}
