#!/usr/bin/perl
# -*- perl -*-
#
# Copyright (C) 2002-2008 Jimmy Olsen, Audun Ytterdal
#
# 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; version 2 dated June,
# 1991.
#
# 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: munin-html.in 1548 2008-03-05 21:37:25Z jo $
#
$|=1;

use strict;
use HTML::Template;
use Getopt::Long;
use Time::HiRes;
use Munin;
use POSIX qw(strftime);

my @times = ( "day", "week", "month", "year" );

my @limit_hosts;
my $DEBUG=0;
my $VERSION = "1.3.4";
my $conffile = "/etc/munin/munin.conf";
my $force_root = 0;
my $do_usage = 0;
my $do_version = 0;
my $stdout = 0;
my $log = new IO::Handle;

# Get options
$do_usage=1  unless
GetOptions ( "host=s"       => \(),
             "force-root!"  => \$force_root,
	     "service=s"    => \(),
	     "config=s"     => \$conffile,
	     "debug!"       => \$DEBUG,
	     "stdout!"      => \$stdout,
	     "help"         => \$do_usage, 
	     "version!"     => \$do_version );

if ($do_usage)
{
    print "Usage: $0 [options]

Options:
    --[no]force-root    Force running, even as root. [--noforce-root]
    --help		View this message.
    --debug		View debug messages.
    --version		View version information.
    --service <service>	Compatability. No effect.
    --host <host>	Compatability. No effect.
    --config <file>	Use <file> as configuration file. 
			[/etc/munin/munin.conf]

";
    exit 0;
}

if ($do_version)
{
    print <<"EOT";
munin-html version $VERSION.
Written by Knut Haugen, Audun Ytterdal, Jimmy Olsen, Tore Anderson / Linpro AS

Copyright (C) 2002-2006

This is free software released under the GNU General Public License. There
is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. For details, please refer to the file COPYING that is included
with this software or refer to
  http://www.fsf.org/licensing/licenses/gpl.txt
EOT
    exit 0;
}

if ($> == 0 and !$force_root)
{
    print "You are running this program as root, which is dumb and will
cause many problems later.  Please run it as user 'munin'.
If you really want to run it as root, use the --force-root option.
Aborting.\n\n";
    exit (1);
}

my $update_time = Time::HiRes::time;

my $config;
my $limits;
$config = &munin_config ($conffile, $config);
$limits = &munin_readconfig ($config->{dbdir}."/limits", 1, 1);
if (!defined $config->{'cgiurl_graph'})
{
    if (defined $config->{'cgiurl'})
    {
	$config->{'cgiurl_graph'} = $config->{'cgiurl'} . "/munin-cgi-graph";
    }
    else
    {
	$config->{'cgiurl_graph'} = "/cgi-bin/munin-cgi-graph";
    }
}

logger("Starting munin-html, checking lock");

munin_runlock("$config->{rundir}/munin-html.lock");

my %comparisontemplates = (  day => HTML::Template->new(filename => "$config->{tmpldir}/munin-comparison-day.tmpl", die_on_bad_params => 0, loop_context_vars => 1),
	                    week => HTML::Template->new(filename => "$config->{tmpldir}/munin-comparison-week.tmpl", die_on_bad_params => 0, loop_context_vars => 1),
	                   month => HTML::Template->new(filename => "$config->{tmpldir}/munin-comparison-month.tmpl", die_on_bad_params => 0, loop_context_vars => 1),
	                    year => HTML::Template->new(filename => "$config->{tmpldir}/munin-comparison-year.tmpl", die_on_bad_params => 0, loop_context_vars => 1)
			  );

my @domains;

my @domainorder;
if ($config->{domain_order}) {
    @domainorder = split /\s+/, $config->{domain_order};
}

#Make sure the logo and the stylesheet file is in the html dir
my @files = ("style.css", "logo.png", "definitions.html");
foreach my $file( (@files) ) {
    if ((! -e "$config->{htmldir}/$file") or
	 (-e "$config->{tmpldir}/$file") and 
	 ((stat ("$config->{tmpldir}/$file"))[9] > (stat("$config->{htmldir}/$file"))[9])) {
        unless (system("cp", "$config->{tmpldir}/$file", "$config->{htmldir}/")){
            logger("copied $file into htmldir");
        } else {
            logger("could not copy $file into htmldir");
        }
    }
}

# For timestamping graphs
my $timestamp = strftime("%Y-%m-%d T %T", localtime);

# Preparing the group tree...
my $groups = get_group_tree ($config);
if (defined $groups->{"name"} and $groups->{"name"} eq "root") {
    $groups = $groups->{"groups"}; # root->groups
}

# Draw main index
my $template = HTML::Template->new(filename => "$config->{tmpldir}/munin-overview.tmpl",
				   die_on_bad_params => 0,
				   loop_context_vars => 1);

$template->param(GROUPS    => $groups,
		 TIMESTAMP => $timestamp);
my $filename = munin_get_html_filename ($config);
open (FILE, ">$filename") or
    die "Cannot open $filename for writing: $!";
print FILE $template->output;
close FILE;

generate_group_templates ($groups);

munin_removelock("$config->{rundir}/munin-html.lock");

sub logger_open {
    my $dirname = shift;

    if (!$log->opened)
    {
	  unless (open ($log, ">>$dirname/munin-html.log"))
	  {
		  print STDERR "Warning: Could not open log file \"$dirname/munin-html.log\" for writing: $!";
	  }
    }
}

sub logger {
  my ($comment) = @_;
  my $now = strftime "%b %d %H:%M:%S", localtime;

  print "$now - $comment\n" if $stdout;
  if ($log->opened)
  {
	  print $log "$now - $comment\n";
  }
  else
  {
	  if (defined $config->{logdir})
	  {
		  if (open ($log, ">>$config->{logdir}/munin-html.log"))
		  {
			  print $log "$now - $comment\n";
			  $log->flush;
			  close (STDERR);
			  open (STDERR, ">&", $log);
		  }
		  else
		  {
			  print STDERR "Warning: Could not open log file \"$config->{logdir}/munin-html.log\" for writing: $!";
			  print STDERR "$now - $comment\n";
		  }
	  }
	  else
	  {
		  print STDERR "$now - $comment\n";
	  }
    }
}

sub calculate_png_size
{
    	my $config = shift;
	my $domain = shift;
	my $node   = shift;
	my $serv   = shift;

        # base size of graph rectangle + space outside graph rectangle
	my $height = munin_get ($config, "graph_height", 100, $domain, $node, $serv) + 156;
	my $width  = munin_get ($config, "graph_width" , 400, $domain, $node, $serv) + 93;

	# In addition, the height increases by 15 pixels for each label underneath
	foreach my $field (keys %{$config->{domain}->{$domain}->{node}->{$node}->{client}->{$serv}})
	{
	    if ($field =~ /^([^\.]+)\.label/)
	    {
		if (munin_draw_field ($config->{domain}->{$domain}->{node}->{$node}, $serv, $1))
		{
		    $height += 15;
		    my $tmpline = munin_get ($config, "line", undef, $domain, $node, $serv, $1);
		    if ($tmpline)
		    {
			my @tmparr = ($tmpline =~ /:/g);
			if (scalar (@tmparr) > 2)
			{ # We've got line definitions with labels...
			    $height += 15;
			}
		    }
		}
	    }
	}
	# ...and +15 if there's a graph total
	$height += 15 if (munin_get ($config, "graph_total", undef, $domain, $node, $serv));
	# ...and +15 if there's min/max-headers above the labels
	$height += 15 if (munin_graph_column_headers ($config, $domain, $node, $serv));

	return ($width, $height);
}

sub get_png_size
{
	my $filename = shift;
	my $width = undef;
	my $height = undef;

	if (open (PNG, $filename))
	{
		my $incoming;
		binmode (PNG);
		if (read (PNG, $incoming, 4))
		{
			if ($incoming =~ /PNG$/)
			{
				if (read (PNG, $incoming, 12))
				{
					if (read (PNG, $incoming, 4))
					{
						$width = unpack ("N", $incoming);
						read (PNG, $incoming, 4);
						$height = unpack ("N", $incoming);
					}
				}
			}
		}
		close (PNG);
	}

	return ($width, $height);
}

sub get_peer_nodes {
    my $hash      = shift || return undef;
    my $category  = shift;
    my $ret       = [];
    my $link      = "index.html";
    my $parent    = munin_get_parent ($hash) || return undef;
    my $me        = munin_get_node_name ($hash);
    my $pchildren = munin_get_children ($parent);

    foreach my $peer (sort {munin_get_node_name($a) cmp munin_get_node_name($b)} @$pchildren) {
	next unless defined $peer and ref ($peer) eq "HASH";
	next if defined $category and lc (munin_get ($peer, "graph_category", "other")) ne $category;
	next if (!defined $peer->{'graph_title'} and (!defined $peer->{'#%#visible'} or !$peer->{'#%#visible'}));
	next if (defined $peer->{'graph_title'} and !munin_get_bool ($peer, "graph", 1));
	my $peername = munin_get_node_name ($peer);
	next if $peername eq "contact" and munin_get_node_name ($parent) eq "root";
	if ($peername eq $me) {
	    unshift @$ret, { "name" => $peername, "link" => undef };
	} else {
	    if (defined $peer->{'graph_title'}) {
		unshift @$ret, { "name" => $peername, "link" => "$peername.html" };
	    } else {
		unshift @$ret, { "name" => $peername, "link" => "../$peername/index.html" };
	    }
	}
    }
    return $ret;
}

sub get_group_tree {
    my $hash    = shift;
    my $base    = shift || "";
    my $graphs  = [];
    my $groups  = [];
    my $cattrav = {};
    my $cats    = [];
    my $path    = [];
    my $rpath   = undef;
    my $ret     = {};
    my $visible = 0;
    my $csspath;

    foreach my $child (@{munin_get_children ($hash)}) {
	next unless defined $child and ref ($child) eq "HASH" and keys %$child;
	if (defined $child->{"graph_title"} and munin_get_bool ($child, "graph", 1)) {
	    my $childname = munin_get_node_name ($child);
	    my $childnode = generate_service_templates ($child);
	    $visible = 1;
	    push @$graphs, { "name" => $childname };
	    $childnode->{'name'} = $child->{"graph_title"};
	    $childnode->{'url'} = $base . $childname.".html";
	    for (my $shrinkpath = $childnode->{'url'}, my $counter = 0; $shrinkpath; $shrinkpath =~ s/^[^\/]+\/?//, $counter++) {
		$childnode->{'url'.$counter} = $shrinkpath;
	    }
	    push @{$cattrav->{ lc munin_get ($child, "graph_category", "other") }}, $childnode;
	} elsif (ref ($child) eq "HASH" and !defined $child->{"graph_title"}) {
	    push @$groups, grep  { defined $_ } get_group_tree ($child, $base . munin_get_node_name ($child) . "/");
	    if (scalar @$groups) {
		$visible = 1;
	    }
	}
    }

    return undef unless $visible;
    $hash->{'#%#visible'} = 1;

    # We need the categories in another format.
    foreach my $cat (sort keys %$cattrav) {
	my $obj = {};
	$obj->{'name'} = $cat;
	$obj->{'url'} = $base."index.html#".$cat;
	$obj->{'services'} = [ sort { lc ($a->{'name'}) cmp lc ($b->{'name'}) } @{$cattrav->{$cat}} ];
	$obj->{'state_'.lc munin_category_status ($hash, $limits, $cat, 1)} = 1;
	for (my $shrinkpath = $obj->{'url'}, my $counter = 0; $shrinkpath =~ /\//; $shrinkpath =~ s/^[^\/]+\/*//, $counter++) {
	    $obj->{'url'.$counter} = $shrinkpath;
	}
    	push @$cats, $obj;
    }

    # ...and we need a couple of paths available.
    @$path = reverse map { { "name" => $_, "path" => (defined $rpath?($rpath.="../")."index.html":($rpath="")) } } reverse ( undef , split ('\/', $base) );
    ($csspath = $path->[0]->{'path'}) =~ s/index.html$/style.css/;

    # We need a bit more info for the comparison templates
    my $compare = munin_get_bool ($hash, "compare", 1);
    my $comparecats = [];
    my $comparecatshash = {};
    my $comparegroups = [];
    if ($compare) {
	foreach my $tmpgroup (@$groups) {
	    # First we gather a bit of data into comparecatshash...
	    if ($tmpgroup->{'ngraphs'} > 0) {
		push @$comparegroups, $tmpgroup;
		foreach my $tmpcat (@{$tmpgroup->{'categories'}}) {
		    $comparecatshash->{$tmpcat->{'name'}}->{'groupname'} = $tmpcat->{'name'};
		    foreach my $tmpserv (@{$tmpcat->{'services'}}) {
			$comparecatshash->{$tmpcat->{'name'}}->{'services'}->{$tmpserv->{'name'}}->{'nodes'}->{$tmpgroup->{'name'}} = $tmpserv;
			$comparecatshash->{$tmpcat->{'name'}}->{'services'}->{$tmpserv->{'name'}}->{'nodes'}->{$tmpgroup->{'name'}}->{'nodename'} = $tmpgroup->{'name'};
		    }
		}	
	    }
	}
	if (scalar @$comparegroups > 1) {
            # ...then we restructure it, comparecats need to end up looking like: ->[i]->{'service'}->[i]->{'nodes'}->[i]->{*}
	    $compare = 1;
	    foreach my $tmpcat (sort keys %$comparecatshash) {
		foreach my $tmpserv (sort keys %{$comparecatshash->{$tmpcat}->{'services'}}) {
		    my @nodelist = map { $comparecatshash->{$tmpcat}->{'services'}->{$tmpserv}->{'nodes'}->{$_} } 
		    	sort keys %{$comparecatshash->{$tmpcat}->{'services'}->{$tmpserv}->{'nodes'}};
		    delete $comparecatshash->{$tmpcat}->{'services'}->{$tmpserv}->{'nodes'};
		    $comparecatshash->{$tmpcat}->{'services'}->{$tmpserv}->{'nodes'} = \@nodelist;
		}
		my @servlist = map { $comparecatshash->{$tmpcat}->{'services'}->{$_} } 
		    sort keys %{$comparecatshash->{$tmpcat}->{'services'}};
		delete $comparecatshash->{$tmpcat}->{'services'};
		$comparecatshash->{$tmpcat}->{'services'} = \@servlist;
	    }
	    @$comparecats = map { $comparecatshash->{$_} } sort keys %$comparecatshash;
	} else {
	    $compare = 0;
	}
    }


    $ret = { 
	"name"               => munin_get_node_name ($hash), 
	"hashnode"           => $hash,
	"url"                => $base . "index.html", 
	"path"               => $path,
	"depth"              => scalar(split("/",$base."index.html"))-1,
	"filename"           => munin_get_html_filename ($hash),
	"csspath"            => $csspath,
	"groups"             => $groups, 
	"graphs"             => $graphs,
	"categories"         => $cats,
	"ngroups"            => scalar (@$groups),
	"ngraphs"            => scalar (@$graphs),
	"ncategories"        => scalar (@$cats),
	"compare"            => $compare,
	"comparegroups"      => $comparegroups,
	"ncomparegroups"     => scalar (@$comparegroups),
	"comparecategories"  => $comparecats,
	"ncomparecategories" => scalar (@$comparecats),
    };
    if ($ret->{'url'} ne "/index.html") {
	for (my $shrinkpath = $ret->{'url'}, my $counter = 0; $shrinkpath =~ /\//; $shrinkpath =~ s/^[^\/]+\/*//, $counter++) {
	    $ret->{'url'.$counter} = $shrinkpath;
	}
    }

    return $ret;
}

sub generate_group_templates {
    my $arr = shift || return undef;
    return undef unless ref ($arr) eq "ARRAY";

    foreach my $key (@$arr) {
	if (defined $key and ref ($key) eq "HASH") {
	    $key->{'peers'} = get_peer_nodes ($key->{'hashnode'});
	    delete $key->{'hashnode'}; # This was only kept there for getting the peers
	    if (defined $key->{'ngroups'} and $key->{'ngroups'}) {
		generate_group_templates ($key->{'groups'});

		my $grouptemplate = HTML::Template->new(
		    filename => munin_get ($config, "tmpldir", "")."/munin-domainview.tmpl",
		    die_on_bad_params => 0,
		    loop_context_vars => 1,
		    filter => sub { my $ref=shift; $$ref =~ s/URLX/URL$key->{'depth'}/g; }
		);

		$grouptemplate->param (
		    GROUPS    => $key->{'groups'},
		    PATH      => $key->{'path'},
		    CSSPATH   => $key->{'csspath'},
		    PEERS     => $key->{'peers'},
		    PARENT    => $key->{'path'}->[-2]->{'name'} || "Overview",
		    COMPARE   => $key->{'compare'},
		    TIMESTAMP => $timestamp,
		);
		my $filename = $key->{'filename'};
		open (FILE, ">$filename") or
		    die "Cannot open $filename for writing: $!";
		print FILE $grouptemplate->output;
		close FILE;

		if ($key->{'compare'}) { # Create comparison templates as well
		    foreach my $t (@times) {
			(my $file = $key->{'filename'}) =~ s/index.html$//;
			$file .= "comparison-$t.html";
			$comparisontemplates{$t}->param (
			    NAME        => $key->{'name'},
			    GROUPS      => $key->{'comparegroups'},
			    PATH        => $key->{'path'},
			    CSSPATH     => $key->{'csspath'},
			    PEERS       => $key->{'peers'},
			    PARENT      => $key->{'path'}->[-2]->{'name'},
			    CATEGORIES  => $key->{'comparecategories'},
			    NCATEGORIES => $key->{'ncomparecategories'},
			    TIMESTAMP   => $timestamp,
			);
			open (FILE, ">$file") or die "Cannot open $file for writing: $!";
			print FILE $comparisontemplates{$t}->output;
			close FILE;
		    }
		}
	    } 

	    if (defined $key->{'ngraphs'} and $key->{'ngraphs'}) {
		my $graphtemplate = HTML::Template->new(
		    filename => munin_get ($config, "tmpldir", "")."/munin-nodeview.tmpl",
		    die_on_bad_params => 0,
		    loop_context_vars => 1,
		    filter => sub { my $ref=shift; $$ref =~ s/URLX/URL$key->{'depth'}/g; }
		);

		$graphtemplate->param (
		    GROUPS      => $key->{'groups'},
		    PATH        => $key->{'path'},
		    CSSPATH     => $key->{'csspath'},
		    PEERS       => $key->{'peers'},
		    PARENT      => $key->{'path'}->[-2]->{'name'},
		    NAME        => $key->{'name'},
		    CATEGORIES  => $key->{'categories'},
		    NCATEGORIES => $key->{'ncategories'},
		    TIMESTAMP   => $timestamp,
		);
		my $filename = $key->{'filename'};
		open (FILE, ">$filename") or
		    die "Cannot open $filename for writing: $!";
		print FILE $graphtemplate->output;
		close FILE;
	    } 
	}
    }
}

sub generate_service_templates { 

    my $service = shift || return undef;
    return undef unless munin_get_bool ($service, "graph", 1);

    my %srv;
    my $fieldnum = 0;
    my @graph_info;
    my @field_info;
    my @loc       = munin_get_node_loc ($service);
    my $pathnodes = get_path_nodes ($service);
    my $peers     = get_peer_nodes ($service, lc munin_get ($service, "graph_category", "other"));
    my $parent    = munin_get_parent_name ($service);
    (my $csspath  = $pathnodes->[0]->{'link'}) =~ s/index.html$/style.css/;

    $srv{'node'}     = munin_get_node_name ($service);
    logger("processing service: $srv{node}");
    $srv{'service'}  = $service;
    $srv{'label'}    = munin_get ($service, "graph_title");
    $srv{'category'} = lc( munin_get ($service, "graph_category", "other") );

    my $method = munin_get ($service, "graph_strategy", "cron");

    $srv{'url'}      = "$srv{node}.html";

    my $path = join ('/', @loc);

    if ($method eq "cgi") {
	$srv{'imgday'}  =$config->{'cgiurl_graph'}."/$path-day.png";
	$srv{'imgweek'} =$config->{'cgiurl_graph'}."/$path-week.png";
	$srv{'imgmonth'}=$config->{'cgiurl_graph'}."/$path-month.png";
	$srv{'imgyear'} =$config->{'cgiurl_graph'}."/$path-year.png";

	if (munin_get_bool ($service, "graph_sums", 0)) {
	    $srv{'imgweeksum'} = $config->{'cgiurl_graph'}."/$path-week-sum.png";
	    $srv{'imgyearsum'} = $config->{'cgiurl_graph'}."/$path-year-sum.png";
	}
    } else {
	# graph strategy cron
	 
	# Image locations for regular pages
	$srv{'imgday'}  ="$srv{node}-day.png";
	$srv{'imgweek'} ="$srv{node}-week.png";
	$srv{'imgmonth'}="$srv{node}-month.png";
	$srv{'imgyear'} ="$srv{node}-year.png";

	# Image locations for comparison pages
	$srv{'cimgday'}  ="$parent/$srv{node}-day.png";
	$srv{'cimgweek'} ="$parent/$srv{node}-week.png";
	$srv{'cimgmonth'}="$parent/$srv{node}-month.png";
	$srv{'cimgyear'} ="$parent/$srv{node}-year.png";

	for my $scale (@times) {
	    if (my ($w, $h) = get_png_size(munin_get_picture_filename($service, $scale))) {
		$srv{"img".$scale."width"} = $w;
		$srv{"img".$scale."height"} = $h;
	    }
	}

	if (munin_get_bool ($service, "graph_sums", 0)) {
	    $srv{imgweeksum} = "$srv{node}-week-sum.png";
	    $srv{imgyearsum} = "$srv{node}-year-sum.png";
	    for my $scale (["week", "year"]) {
		if (my ($w, $h) = get_png_size (munin_get_picture_filename($service, $scale, 1))) {
		    $srv{"img".$scale."sumwidth"} = $w;
		    $srv{"img".$scale."sumheight"} = $h;
		}
	    }
	}
    }

    # Do "help" section
    if (my $info = munin_get ($service, "graph_info")) {
	my %graph_info;
	$graph_info{info} = $info;
	push @{$srv{graphinfo}}, \%graph_info;
    }

    $srv{fieldlist} .= "<tr><th align='left' valign='top'>Field</th><th align='left' valign='top'>Type</th><th align='left' valign='top'>Warn</th><th align='left' valign='top'>Crit</th><th></tr>";
    foreach my $f (@{munin_get_field_order ($service)}) {
	$f =~ s/=(.*)$//;
	my $path = $1;
	next if (!defined $service->{$f});
	my $fieldobj = $service->{$f};
	next if (ref ($fieldobj) != "HASH" or !defined $fieldobj->{'label'});
	next if (!munin_draw_field ($fieldobj));

	logger ("DEBUG: single_value: Checking field \"$f\" ($path).")
	    if $DEBUG;

	if (defined $path) {
	    # This call is to make sure field settings are copied
            # for aliases, .stack, et al. Todo: put that part of
            # munin_get_rrd_filename into its own functino.
	    munin_get_rrd_filename ($f, $path);
	}

	my %field_info;
	$fieldnum++;

	$field_info{'hr'}    = 1 unless ($fieldnum % 3);
	$field_info{'field'} = $f;
	$field_info{'label'} = munin_get ($fieldobj, "label", $f);
	$field_info{'type'}  = lc( munin_get ($fieldobj, "type", "GAUGE") );
	$field_info{'warn'}  = munin_get ($fieldobj, "warning");
	$field_info{'crit'}  = munin_get ($fieldobj, "critical");
	$field_info{'info'}  = munin_get ($fieldobj, "info");

	my $state = munin_field_status ($fieldobj, $limits, 1);

	if (defined $state) {
	    $field_info{'state_warning'}  = 1 if $state eq "warning";
	    $field_info{'state_critical'} = 1 if $state eq "critical";
	    $field_info{'state_unknown'}  = 1 if $state eq "unknown";
	}
	push @{$srv{'fieldinfo'}}, \%field_info;
    }

    my $state = munin_service_status ($service, $limits, 1);
    if (defined $state) {
	$srv{'state_warning'}  = 1 if $state eq "warning";
	$srv{'state_critical'} = 1 if $state eq "critical";
	$srv{'state_unknown'}  = 1 if $state eq "unknown";
    }

    my $servicetemplate = HTML::Template->new(filename => "$config->{tmpldir}/munin-serviceview.tmpl",
				       die_on_bad_params => 0,
				       loop_context_vars => 1);
    $servicetemplate->param(SERVICES  => [\%srv],
			    PATH      => $pathnodes,
			    PEERS     => $peers,
			    CSS       => $csspath,
			    CSSPATH   => $csspath,
			    CATEGORY  => ucfirst $srv{'category'},
			    TIMESTAMP => $timestamp);
    my $filename = munin_get_html_filename ($service);
    open (FILE, ">$filename") or
	die "Cannot open $filename for writing: $!";
    print FILE $servicetemplate->output;
    close FILE;

    return \%srv;
}

sub get_path_nodes {
    my $hash = shift || return undef;
    my $ret  = [];
    my $link = "index.html";

    unshift @$ret, { "name" => munin_get_node_name ($hash), "link" => undef };
    while ($hash = munin_get_parent ($hash)) {
	unshift @$ret, { "name" => munin_get_node_name ($hash), "link" => $link };
	$link = "../" . $link;
    }
    $ret->[0]->{'name'} = undef;
    return $ret;
}

$update_time = sprintf("%.2f",(Time::HiRes::time - $update_time));

logger("munin-html finished ($update_time sec)");
close $log;

=head1 NAME

munin-html - A program to draw html-pages on an Munin installation

=head1 SYNOPSIS

munin-html [options]

=head1 OPTIONS

=over 5

=item B<< --[no]force-root >>

Force running as root (stupid and unnecessary). [--noforce-root]

=item B<< --service <service> >>

Limit services to those of E<lt>serviceE<gt>. Multiple --service options may be supplied. [unset]

=item B<< --host <host> >>

Limit hosts to those of E<lt>host<gt>. Multiple --host options may be supplied. [unset]

=item B<< --config <file> >>

Use E<lt>fileE<gt> as configuration file. [/etc/munin/munin.conf]

=item B<< --help >>

View help message.

=item B<< --[no]debug >>

If set, view debug messages. [--nodebug]

=back

=head1 DESCRIPTION

Munin-html is a part of the package Munin, which is used in combination
with Munin's node.  Munin is a group of programs to gather data from
Munin's nodes, graph them, create html-pages, and optionally warn Nagios
about any off-limit values.

If munin.conf sets "graph_strategy cgi" then munin-html generates URLs
referencing the graph CGI instead of referencing pre-generated
graphs (made by munin-graph).

Munin-html creates the html pages.

=head1 FILES

	/etc/munin/munin.conf
	/var/lib/munin/datafile
	/var/log/munin/munin-html
	/var/www/munin/*
	/var/run/munin/*

=head1 VERSION

This is munin-html version 1.3.4

=head1 AUTHORS

Knut Haugen, Audun Ytterdal and Jimmy Olsen.

=head1 BUGS

munin-html does, as of now, not check the syntax of the configuration file.

Please report other bugs in the bug tracker at L<http://munin.sf.net/>.

=head1 COPYRIGHT

Copyright (C) 2002-2006 Knut Haugen, Audun Ytterdal, and Jimmy Olsen /
Linpro AS.

This is free software; see the source for copying conditions. There is
NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.

This program is released under the GNU General Public License

=head1 SEE ALSO

For information on configuration options, please refer to the man page for
F<munin.conf>.

=cut

# vim:syntax=perl:ts=8
