#!/usr/bin/perl -w

# Copyright 1999, 2000, 2001 (c) Thomas Erskine <thomas.erskine@sourceworks.com>
# See the COPYRIGHT file with the distribution.

# graph-writer - the remstats web-page maker
# $Id: graph-writer.pl,v 1.15 2001/08/28 15:22:24 remstats Exp $

# - - -   Configuration   - - -

use strict;

# What is this program called, for error-messages and file-names
$main::prog = 'graph-writer';
# Where is the configuration dir
$main::config_dir = '/etc/remstats/config';

# - - -   Version History   - - -

(undef, $main::version) = split(' ', '$Revision: 1.15 $');

# - - -   Setup   - - -

use lib '.', '/usr/lib/remstats/lib', '/usr/lib/perl5/';
require 5.005;
require "remstats.pl";
require "htmlstuff.pl";
use RRDs;
use Getopt::Std;

# Parse the command-line
my %opt = ();
getopts('d:f:hI:u', \%opt);

if (defined $opt{'h'}) { &usage; } # no return
if (defined $opt{'d'}) { $main::debug = $opt{'d'}; } else { $main::debug = 0; }
if (defined $opt{'f'}) { $main::config_dir = $opt{'f'}; }
if (defined $opt{'I'}) { unshift @INC, split('\s*,\s*', $opt{'I'}); }

&read_config_dir($main::config_dir, 'general', 'links', 'colors', 'tools', 'times', 
	'html', 'oids', 'rrds', 'customgraphs', 'groups', 'host-templates', 'hosts');

if (-f $main::config{RRDCGI}) {
	$main::shebang = '#!' . $main::config{RRDCGI} . "\n";
}
else { &abort("rrdcgi ($main::config{RRDCGI}) isn't there"); }

# collect the hosts in group order
my %hosts = ();
my @hosts = ();
my ($group, $host, $status);

foreach $group (@{$main::config{GROUPS}}) {
# In case there is a group with no hosts
	next unless (defined @{$main::config{GROUP}{$group}});
	push @hosts, @{$main::config{GROUP}{$group}};
}
undef %hosts;

# Get the indices
%main::indices = &init_indices;
%main::upindices = %main::indices;
# foreach (keys %main::indices) { $main::upindices{$_} = '../' . $main::indices{$_}; }

# Open for business
my $quick_index = $main::config{HTMLDIR} .'/'. $main::indices{$main::config{HTML}{QUICKINDEX}};
open (QUICK, ">$quick_index.new") or &abort("can't open $quick_index.new: $!");
my $quick_html = $main::shebang . &html_header($main::config{HTML}{QUICKINDEX}, 
	$main::config{HTML}{QUICKINDEX}, %main::indices);

# my $overall_index = $main::config{HTMLDIR} .'/'. $main::indices{$main::config{HTML}{OVERALLINDEX}};
# open (OVERALL, ">$overall_index.new") or 
# 	&abort("can't open $overall_index.new: $!");
# my $overall_html = $main::shebang . &html_header($main::config{HTML}{OVERALLINDEX}, 
# 	$main::config{HTML}{OVERALLINDEX}, %main::indices);

my $ping_index = $main::config{HTMLDIR} .'/'. $main::indices{$main::config{HTML}{PINGINDEX}};
open (PINGFULL, ">$ping_index.new") 
	or &abort("can't open $ping_index.new: $!");
my $ping_html = $main::shebang . &html_header($main::config{HTML}{PINGINDEX}, 
	$main::config{HTML}{PINGINDEX}, %main::indices);

# - - -   Mainline   - - -

# Do the web pages
my ($realrrd, $ip, $wildrrd, $wildpart, $extra, $update, $alias, $last_group,
	$realgraph, $lastuptime, $stale, $uptime, $desc, $host_index, $host_html,
	$alertflag, $alerts, $thumb_img, $fixedrrd, $rrdfile, $graph, $fixedgraph,
	$graphstatus, $day_img, $graph_index, $graph_html, $graphtime, $graph_img,
	$custom_index, $custom_html, $customgraph, $customgraph_img, 
	$customgraph_index, $customgraph_url, $customgraph_html, $header_bar, $new);
$last_group = '';
foreach $host (@hosts) {
	$ip = &get_ip($host);

# Do group headers
	$group = $main::config{HOST}{$host}{GROUP};
	if ($group ne $last_group) {
		$last_group = $group;
		my $fixedgroup =  &to_filename($group) . '.html';
		if (-f ($main::config{HTMLDIR} .'/'. $fixedgroup)) {
			$group = "<NOBR><A HREF=\"$fixedgroup\">$group</A></NOBR>";
			$main::upindices{$main::config{HTML}{GROUPINDEX}} = '../'. $fixedgroup;
		}
		else {
			undef $main::upindices{$main::config{HTML}{GROUPINDEX}};
		}

		$header_bar = &header_bar($group);
		$quick_html .= $header_bar;
#		$overall_html .= $header_bar;
		$ping_html .= $header_bar;
	}

# Now host headers
	$main::upindices{$main::config{HTML}{HOSTINDEX}} = $host .'/index.cgi';
	$main::upindices{$main::config{HTML}{ALERTREPORT}} = 
		$main::indices{$main::config{HTML}{ALERTREPORT}} .  '?host='. $host;
	if (defined $main::config{HOST}{$host}{STATUSFILE}) {
		$status = '<RRD::INCLUDE '. $main::config{DATADIR} .'/'. $host .'/'.
			$main::config{HOST}{$host}{STATUSFILE} .'>';
	}
	else {
		$status = '<RRD::INCLUDE '. $main::config{DATADIR} .'/'.  $host .'/STATUS.html>';
	}

# Make a recently up flag, if appropriate
	($lastuptime, $stale) = &get_status($host, 'UPTIME');
	if (!$stale and $lastuptime ne 'MISSING') {
		$uptime = '<RRD::INCLUDE '. $main::config{DATADIR} .'/'. 
			$host .'/UPTIMEFLAG.html>';
	}
	else { $uptime = ''; }

# Make a flag for alerts (separate for easy removal)
	($alertflag,$stale) = &get_status( $host, 'ALERTFLAG.html');
	if (!$stale and $alertflag ne 'MISSING') {
		$alerts = '<A HREF="'. $main::config{HTMLURL} .'/alert.cgi?host='.
			$host .'"><RRD::INCLUDE '. $main::config{DATADIR} .'/'.
			$host .'/ALERTFLAG.html></A>';
	}
	else { $alerts = ''; }

	$quick_html .= <<"EOD_QUICK_HOST";
<NOBR>$main::config{HTML}{HOSTPREFIX}<A HREF="$host/index.cgi">$host</A>&nbsp;$status$uptime$alerts$main::config{HTML}{HOSTSUFFIX}</NOBR>
EOD_QUICK_HOST

# 	$overall_html .= <<"EOD_OVERALL_HOST";
# <NOBR><H3><A HREF="$host/index.cgi">$host</A>&nbsp;$status$uptime$alerts</H3></NOBR>
# EOD_OVERALL_HOST

	$thumb_img = &make_rrdcgi_graph( $host, $host, $host, 'ping', 'ping', 'thumb', $host);
	$ping_html .= '<A HREF="'. $host .'/index.cgi">'. $thumb_img .'</A>';

	if (defined $main::config{HOST}{$host}{DESC}) {
		$desc = ' - '. $main::config{HOST}{$host}{DESC};
	}
	else { $desc = ''; }

	&debug("doing html for $host") if ($main::debug);
	$host_index = $main::config{HTMLDIR} . "/$host/index.cgi";
	open (HOST, ">$host_index.new") or 
		(&error("can't open $host_index.new: $!") and next);
	$host_html = $main::shebang . &html_header($host, $main::config{HTML}{HOSTINDEX}, %main::upindices);
	$host_html .= &toolbar($host, $ip);
	$host_html .= &status_header($host, $ip);
	$host_html .= "<HR>\n";

	my $statusfile;
	foreach $realrrd (@{$main::config{HOST}{$host}{RRDS}}) {
		($wildrrd, $wildpart, $extra, $fixedrrd) = 
			&get_rrd($realrrd);
		next unless (defined $wildrrd);
		&debug("  for rrd $realrrd($wildrrd)") if ($main::debug);

		$rrdfile = $main::config{DATADIR}.'/'.$host.'/'.$fixedrrd.'.rrd';
		unless (-f $rrdfile) {
			&error("$rrdfile is missing; graphs skipped");
			next;
		}

		foreach $graph (@{$main::config{RRD}{$wildrrd}{GRAPHS}}) {
			next if ($graph =~ /^thumb/i);

			$realgraph = $graph;
			if (defined $wildpart) { $realgraph =~ s/\*/$wildpart/; }
			$fixedgraph = &to_filename($fixedrrd .'-'. $realgraph);
			if (defined $main::config{HOST}{$host}{NOGRAPH}{$realrrd}{$graph}) {
				&debug("    nograph for $realgraph; skipped") if ($main::debug);
				next;
			}
			&debug("    doing graph $realgraph: wild=$graph, fixed=$fixedgraph")
				if ($main::debug);

			$statusfile = $main::config{DATADIR} .'/'. $host .'/STATUS-'. $fixedgraph;
			if ( -f $statusfile) {
				$graphstatus = '<RRD::INCLUDE '. $statusfile .'>';
			}
			else {
				$graphstatus = '';
				&debug("skipping missing status file") if ($main::debug>1);
			}
# 			$overall_html .= <<"EOD_OVERALL_GRAPH";
# <NOBR>[<A HREF="$host/$fixedgraph-index.cgi">$realgraph$graphstatus</A>]</NOBR>
# EOD_OVERALL_GRAPH
			$day_img = &make_rrdcgi_graph( undef, undef, $host, $realrrd, 
				$graph, 'day');
			$host_html .= '<A HREF="'. $fixedgraph .'-index.cgi">'. 
				$day_img ."</A>\n";

			$graph_index = $main::config{HTMLDIR} 
				. "/$host/$fixedgraph-index.cgi";
			open (GRAPH, ">$graph_index.new") or 
				(&error("can't open $graph_index.new: $!") and next);
			$graph_html = $main::shebang . 
				&html_header("$realgraph for $host", "", %main::upindices);
			$graph_html .= 
				&rrd_status_header($host, $realrrd, $wildrrd, $fixedrrd, $graph);
			$graph_html .= "<HR>\n";

			foreach $graphtime (@{$main::config{RRD}{$wildrrd}{TIMES}}) {
				next if ($graphtime =~ /^thumb/);
				$graph_img = &make_rrdcgi_graph( undef, undef, $host, $realrrd, 
					$graph, $graphtime);
				$graph_html .= $graph_img ."\n";
			}
			$graph_html .= &html_footer;
			print GRAPH $graph_html;
			close (GRAPH);
			chmod 0755, $graph_index .'.new' or do {
				&error("can't chmod $graph_index.new: $!");
				next;
			};
			rename $graph_index.'.new', $graph_index or do {
				&error("can't rename $graph_index.new to $graph_index: $!");
				next;
			};
		}
	}

# Now any customgraphs associated with this host
	my ($customgraph_img, $customgraph_index, $customgraph_html);
	foreach my $customgraph (@{$main::config{HOST}{$host}{CUSTOMGRAPHS}}) {
		&debug("  doing customgraph $customgraph") if ($main::debug);
		$fixedgraph = &to_filename($customgraph);
		$customgraph_img = &make_custom_graph( undef, undef, 
			$customgraph, 'day');
		$customgraph_index = $main::config{HTMLURL} .'/CUSTOM/'.
			$fixedgraph .'-index.cgi';
		&debug("    link is to $customgraph_index") if ($main::debug);
		$host_html .= '<A HREF="'. $customgraph_index .'">'. 
				$customgraph_img ."</A>\n";
	}

	$host_html .= &html_footer;
	print HOST $host_html;
	close (HOST);
	chmod 0755, $host_index .'.new' or do {
		&error("can't chmod $host_index.new: $!");
		next;
	};
	rename $host_index.'.new', $host_index or do {
		&error("can't rename $host_index.new $host_index: $!");
		next;
	};
}

# End, close and rename all the indices
$quick_html .= &html_footer;
print QUICK $quick_html;
close (QUICK);
chmod 0755, $quick_index .'.new' or
	&error("can't chmod $quick_index.new: $!");
rename $quick_index.'.new', $quick_index or
	&error("can't rename $quick_index.new $quick_index: $!");
&debug("wrote $quick_index") if ($main::debug);

# $overall_html .= &html_footer;
# print OVERALL $overall_html;
# close (OVERALL);
# chmod 0755, $overall_index .'.new' or
# 	&error("can't chmod $overall_index.new: $!");
# rename $overall_index.'.new', $overall_index or
# 	&error("can't rename $overall_index.new $overall_index: $!");
# 
# &debug("wrote $overall_index") if ($main::debug);

$ping_html .= &html_footer;
print PINGFULL $ping_html;
close (PINGFULL);
chmod 0755, $ping_index .'.new' or
	&error("can't chmod $ping_index.new: $!");
rename $ping_index.'.new', $ping_index or
	&error("can't rename $ping_index.new $ping_index: $!");
&debug("wrote $ping_index") if ($main::debug);

# Now do the custom graphs
$custom_index = $main::config{HTMLDIR} .'/'. $main::indices{$main::config{HTML}{CUSTOMINDEX}};
open (CUSTOM, ">$custom_index.new") 
	or &abort("can't open $custom_index.new: $!");
$custom_html = $main::shebang . 
	&html_header($main::config{HTML}{CUSTOMINDEX}, $main::config{HTML}{CUSTOMINDEX}, %main::indices) ."<hr>\n";

# So that the rrdgraph rrd names can be a bit shorter
chdir $main::config{DATADIR} || 
	&abort("can't chdir to $main::config{DATADIR} for custom graphs");
&debug("chdir to $main::config{DATADIR} for custom graphs") if ($main::debug);

# So that they stay in user-specified order
foreach $customgraph (@{$main::config{CUSTOMGRAPHS}}) {
	$customgraph_img = &make_custom_graph( $main::config{HTMLDIR} .'/CUSTOM',
		undef, $customgraph, 'day');
	$customgraph_index = $main::config{HTMLDIR} .'/CUSTOM/'. 
		&to_filename($customgraph .  '-index.cgi');
	$customgraph_url = $main::config{HTMLURL} .'/CUSTOM/'. 
		&to_filename($customgraph .'-index.cgi');
	$custom_html .= "<A HREF=\"$customgraph_url\">$customgraph_img</A>\n";
	&debug("  doing $customgraph: html=$customgraph_url, file=$customgraph_index") 
		if ($main::debug);

	open (CUSTOMGRAPH, ">$customgraph_index.new") or
		(&error("can't open $customgraph_index.new: $!") and next);
	$customgraph_html = $main::shebang . 
		&html_header($customgraph, '', %main::upindices) ."<HR>\n";

	foreach $graphtime (@{$main::config{CUSTOMGRAPH}{$customgraph}{TIMES}}) {
		$customgraph_html .= &make_custom_graph( $main::config{HTMLDIR} .'/CUSTOM',
			undef, $customgraph, $graphtime);
		&debug("    $graphtime") if ($main::debug);
	}

	$customgraph_html .= &html_footer;
	print CUSTOMGRAPH $customgraph_html;
	close (CUSTOMGRAPH);

	chmod 0755, $customgraph_index.'.new' or do {
		&error("can't chmod $customgraph_index.new: $!");
		next;
	};
	rename $customgraph_index.'.new', $customgraph_index or do {
		&error("can't rename $customgraph_index.new: $!");
		next;
	};
	&debug("wrote $customgraph_index") if ($main::debug);

}
$custom_html .= &html_footer;
print CUSTOM $custom_html;
close (CUSTOM);

chmod 0755, $custom_index.'.new' or do {
	&error("can't chmod $custom_index.new: $!");
	next;
};
rename $custom_index.'.new', $custom_index or do {
	&error("can't rename $custom_index.new: $!");
	next;
};
&debug("wrote $custom_index") if ($main::debug);

# Now do the RRD Index (a list of RRDs with each showing all of the graphs for
# that type of rrd)

my $rrd_index = $main::config{HTMLDIR} .'/'. $main::indices{$main::config{HTML}{RRDINDEX}};
open (RRD, ">$rrd_index.new") 
	or &abort("can't open $rrd_index.new: $!");
my $rrd_html = $main::shebang . &html_header($main::config{HTML}{RRDINDEX}, 
	$main::config{HTML}{RRDINDEX}, %main::indices) .
	"<P><TABLE ALIGN=\"CENTER\"BORDER=\"0\">\n" .
	"<TR><TH>RRD</TH><TH>graphs</TH></TR>\n";

my ($hostrrd, $host_wildrrd, $host_wildpart, $host_fixedrrd, $shown_rrd,
	$fixed_graph, $shown_graph, $file, $fixed_host_graph);

foreach $wildrrd (sort keys %{$main::config{RRD}}) {
	&debug("doing rrd $wildrrd") if ($main::debug);
	$shown_rrd = 0;
	foreach $graph (@{$main::config{RRD}{$wildrrd}{GRAPHS}}) {
		&debug("  doing graph $graph") if ($main::debug);
		$shown_graph = 0;
		$fixed_graph = &to_filename($wildrrd .'-'. $graph);
		$graph_html = $main::shebang . &html_header( "graphs of $graph for $wildrrd",
			"graphs of $graph", %main::indices);

		foreach $host (sort keys %{$main::config{HOST}}) {
			&debug("    doing host $host") if ($main::debug>1);
			foreach $hostrrd (sort @{$main::config{HOST}{$host}{RRDS}}) {
				($host_wildrrd, $host_wildpart, undef, $host_fixedrrd) = 
					&get_rrd($hostrrd);
				next unless ($host_wildrrd eq $wildrrd);
				&debug("    found rrd $hostrrd for $host") if ($main::debug);

# Show which RRD this graph belongs to
				unless ($shown_rrd) {
					$shown_rrd = 1; 
					$rrd_html .= "<TR><TD VALIGN=\"TOP\">$wildrrd</TD>\n" .
						"<TD VALIGN=\"TOP\">";
				}

# Show a link to the page of graphs
				unless ($shown_graph) {
					$rrd_html .= <<"EOD_RRD_GRAPH";
<A HREF="$main::config{HTMLURL}/RRDS/$fixed_graph.cgi">$graph</A>
EOD_RRD_GRAPH
					$shown_graph = 1;
				}

# Add this host/rrd/graph combination to the graphs page
				$graph_img = &make_rrdcgi_graph( undef, $host, 
					$host, $hostrrd, $graph, 'day');
				$fixed_host_graph = $host_fixedrrd . '-' . $graph;
				$fixed_host_graph =~ s/\*/$host_wildpart/;
				$graph_html .= <<"EOD_HOST_GRAPH";
<A HREF="$main::config{HTMLURL}/$host/$fixed_host_graph-index.cgi">$graph_img</A>
EOD_HOST_GRAPH
			}
		}
# Write the new graph file
		$file = $main::config{HTMLDIR} .'/RRDS/'. $fixed_graph .'.cgi';
		open (GRAPH, ">$file.new") or do {
			&error("can't open $file.new: $!");
			next;
		};
		print GRAPH $graph_html, &html_footer;
		close (GRAPH);
		chmod 0755, $file.'.new' or do {
			&error("can't chmod $file.new: $!");
			next;
		};
		rename $file.'.new', $file or do {
			&error("can't rename $file.new: $!");
			next;
		};
		&debug("wrote $file") if ($main::debug);
	}
	if ($shown_rrd) { $rrd_html .= "</TD></TR>\n"; }

}
$rrd_html .= "</TABLE></P>\n" . &html_footer;
print RRD $rrd_html;
close (RRD);

chmod 0755, $rrd_index.'.new' or do {
	&error("can't chmod $rrd_index.new: $!");
	return;
};
rename $rrd_index.'.new', $rrd_index or do {
	&error("can't rename $rrd_index.new: $!");
	return;
};

exit 0;

#----------------------------------------------------------------- usage ---
sub usage {
	print STDERR <<"EOD_USAGE";
$main::prog version $main::version
usage: $0 [options] collector
where options are:
	-d nnn	enable debugging output at level 'nnn'
	-D	enable configuration debugging output
	-f fff	use 'fff' for config-dir [$main::config_dir]
	-h	show this help
EOD_USAGE
	exit 0;
}

#----------------------------------------------------------------- debug ---
sub debug {
	my ($msg) = @_;

	if ($main::debug) { print STDERR "DEBUG: $msg\n"; }
0;
}

#----------------------------------------------------------------- error ---
sub error {
	my ($msg) = @_;
	print STDERR "$main::prog: ERROR: $msg\n";
0;
}

#----------------------------------------------------------------- abort ---
sub abort {
	my ($msg) = @_;
	print STDERR "$main::prog: ABORT: $msg\n";
	exit 1;
}

#--------------------------------------------------------------- put_meta ---
sub put_meta {
	my ($file, $expire_time) = @_;

	return unless (defined $main::config{METADIR} and
		defined $main::config{METASUFFIX});

# Format an expiry date
	my ($sec, $min, $hour, $mday, $mon, $year, $wday) = 
		gmtime(time + $expire_time);
	my $string = sprintf("%3s, %d %3s %4d %02d:%02d:%02d -000",
		('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')[$wday],
		$mday,
		('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
			'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')[$mon],
		$year+1900,
		$hour,
		$min,
		$sec);

# Make sure the directory is there
	my @temp = split('/',$file);
	my $name = pop @temp;
	my $dir = join('/', @temp);
	$dir .= '/'. $main::config{METADIR};
	unless (-d $dir) {
		return unless &make_a_dir($dir);
	}

# Write the stuff to the meta file
	my $metafile = $dir . '/' . $name . $main::config{METASUFFIX};
	open (META, ">$metafile") or
		(&error("can't open $metafile: $!") and return);
	print META <<"EOD_META";
Expires: $string
EOD_META
	close(META);
}
