#!/usr/bin/perl -w
#
# faxhist.cgi -- show fax history (reading 'acct.log')
# 
# Copyright 1998 Gert Doering, gert@greenie.net
#
# CVS: $Id: faxhist.in,v 1.7 2006/01/19 13:41:17 gert Exp $
#
# $Log: faxhist.in,v $
# Revision 1.7  2006/01/19 13:41:17  gert
# adapt to understand "new" acct.log format (faxrunqd 1.70->1.71)
# change sort rules for "give me one day" output
#    (first: number of failures, then: number of good faxes, then: phone number)
#
# Revision 1.6  2001/01/30 11:39:03  gert
# make "can't parse" error message easier to understand
#
# Revision 1.5  1998/12/04 15:50:02  gert
# use $indexpage
#
# Revision 1.4  1998/10/13 15:53:37  gert
# logo/<h1> header adjusted
#
# Revision 1.3  1998/10/07 13:06:48  gert
# added (preliminary) config file
#
# Revision 1.2  1998/09/04 15:20:09  gert
# add 'detail' page, and restructure
#
# Revision 1.1  1998/09/01 15:09:32  gert
# faxhist.in : show fax history (from 'acct.log')
#

#
print "Content-type: text/html\n\n";

# read configuration file
require "@CONFDIR@/wwwgui.conf";

$fax_acct='@FAX_SPOOL@/acct.log';
$faxrunq_cf='@CONFDIR@/faxrunq.config';

#
# read and parse CGI arguments
#
if ($ENV{'REQUEST_METHOD'} eq "GET")
{
  $query_string=$ENV{'QUERY_STRING'};
}
elsif (($ENV{'REQUEST_METHOD'} eq "POST") &&
       ($ENV{'CONTENT_TYPE'} eq "application/x-www-form-urlencoded"))
{
  read(STDIN,$query_string,$ENV{'CONTENT_LENGTH'});
}
else
{
  $query_string='';
};

#
# parse CGI arguments
#
%args=();
foreach (split(/\&/,$query_string))
{
  if ( /^(\w+)=/ && ($key=$1) && ($value=$') )
  {
    $value =~ s/\+/ /go;
    $value =~ s/\%([0-9a-f]{2})/pack(C,hex($1))/eig;

   $args{"$key"}=$args{"$key"} . " " . $value if ($value!~/^\s*$/);
   $args{"$key"} =~ s/^\s*//o;
  }
}

#
# read 'faxrunq.config' - the path to $faxrunq_cf might have changed
#
if ( open( IN, "<$faxrunq_cf" ) )
{
    while( <IN> )
    {
	chomp;
	if ( /^\s*acct-log\s+(\S+)/ )
			{ $fax_acct = "$1"; }
    }
    close IN;
}

#
# now we know which file to read...
#

#
# open HTML page
#

print <<EOF;
<html><head>
<title>Fax History</title>
</head></html>
<body bgcolor="#ffffff">

$www_logo
<h1 align="center">Faxmanager - &Uuml;bersicht gesendeter Faxe</h1><br clear="all">
<hr size="5">

<!-- acct_log: $fax_acct -->
EOF

unless( open( A, "<$fax_acct" ) )
{
    print "<b>can't open '$fax_acct': $!</b><p></body></html>\n";
    exit 0;
}

if ( defined( $args{'d'} ))		# show one day only
{
    &do_one_day( $args{'d'} );
}
else					# show all, one line for each day
{
    &do_summary;
}

# close page
print <<EOF;
<p>
<hr size="5">
<a href="$indexpage">^Hauptmenue</a>
</body>
EOF

exit 0;


#
# print overall summary table
#
sub do_summary
{
    print <<EOF;
<div align="center">
<table border="1" cellpadding="4">
<TR><TH>Datum<TH>Faxe<TH>OK
<TH>Fehler<TH>NoDev<TH>BUSY<TH>NO CAR.<TH>Sendef.</TR>
EOF

#
# process 'acct.log'
# (store data day-by-day in an array, output in reverse order)
#

    $lday='';
    $success_d=$fail_d=0; @fail_d=(0,0,0,0,0,0,0,0,0,0,0,0,0);
    $success_t=$fail_t=0; @fail_t=(0,0,0,0,0,0,0,0,0,0,0,0,0);

    @days=();

    while( <A> )
    {
	chomp;
	@_ = split( /\|/, $_ );

	my ( $job, $user, $tty, $phone, $date, $acct_h, $status );
	my ( $day, $time, $yy );

	# labor F262958 |13032548 |acct|Thu Dec  1 05:05:13 2005| success
	if ( $_[0] =~ /^(\S+)\s+(F\d+)/ )		# old format
	{
	    ( $job, $user, $tty, $phone, $date, $acct_h, $status ) = 
		( $2, $1, '', $_[1], $_[3], $_[2], $_[4] );

	    unless ( $date =~ /(.*) (\d\d:\d\d:\d\d) (.*)/ )
	    {
		print "</table><p><b>internal error: can't parse '$date' in the following acct.log input line:<br>$_</b><p></body></html>\n";
		exit 0;
	    }
	    ( $day, $time, $yy ) = ( $1, $2, $3 );
	}
	else						# new format
	# F325827|labor|tty210| 93948740 |0|20060119 12:35:32|acct|success
	{
	    ( $job, $user, $tty, $phone, $date, $acct_h, $status ) = 
		( $_[0], $_[1], $_[2], $_[3], $_[5], $_[6], $_[7] );

	    unless ( $date =~ /(\d{4})(\d\d)(\d\d) (\d\d:\d\d:\d\d)/ )
	    {
		print "</table><p><b>internal error: can't parse '$date' in the following acct.log input line:<br>$_</b><p></body></html>\n";
		exit 0;
	    }
	    ( $day, $time, $yy ) = ( "$3.$2.", $4, $1 );
	}

	# remove "eye candy" whitespace
	$phone =~ s/\s//go;

	if ( $day ne $lday )
	{
	    if ( $lday ne '' )
	    {
		unshift @days, &emit_row( 1, $lday, $success_d, $fail_d, \@fail_d );
	    }
	    $lday=$day;
	    $success_d=$fail_d=0;
	    @fail_d=(0,0,0,0,0,0,0,0,0,0,0,0,0);
	}
	#print "$_<br><b>phone=$phone, day=$day, $status</b><br>\n";

	if ( $status =~ /fail (\d+):/ )
	{
	    $fail_d++; $fail_d[$1]++;
	    $fail_t++; $fail_t[$1]++;
	}
	else
	{
	    $success_d++;
	    $success_t++;
	}
    }
    close A;

    unshift @days, &emit_row( 1, $lday, $success_d, $fail_d, \@fail_d );
    unshift @days, &emit_row( 0, "TOTAL", $success_t, $fail_t, \@fail_t );

    print join('', @days);

    print <<EOF;
</table>
</div>

EOF
}

#
# auxiliary function to generate one table row for do_summary
#
sub emit_row
{
my ( $do_href, $title, $s, $f, $det ) = @_;

my $total = $s+$f;

my $str;

    if ( $do_href )
    {
	my $t = $title;
	$t =~ s/ /_/g;
	$title="<a href=\"faxhist.cgi?d=$t\">$title</a>";
    }

    $str = sprintf "<TR><TD>%s<TD align=\"right\">%d", $title, $total;
    
    $str .= sprintf "<TD align=\"right\">$s &nbsp;(%02d%%)", 100*$s/$total;

my $bad = 100*$f/$total;
my $bg;

    $bg = &getcolor( 100*$f/$total );

    $str .= sprintf "<TD align=\"right\"$bg>$f &nbsp;(%02d%%)\n", 100*$f/$total;

my $case;

    foreach $case (2, 4, 10, 12)
    {
	$bg = &getcolor( 100*($det->[$case])/$total );

	$str .= sprintf "  <TD align=\"right\"$bg>%d\n", $det->[$case];
    }
    return $str;
}

#
# determine color of table cell, according to the percentage of errors
# > 30% means "yellow", > 50% means "red"
#
sub getcolor
{
    my $percent = shift;

    if ( $percent > 50 ) { return ' bgcolor="ff7720"'; }
    if ( $percent > 30 ) { return ' bgcolor="ffff00"'; }
    return '';
}

#
# show statistics for a specific day
#
sub do_one_day
{
    my $doday = shift;

    $doday =~ s/_/ /g;

    print <<EOF;
<h2 align="center">Detail&uuml;bersicht f&uuml;r '$doday'</h2>
EOF

    my ($success, $fail, @fail, %by_phone);

    $success = $fail = 0;
    @fail = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);

    # read acct.log
    while( <A> )
    {
	chomp;
	@_ = split( /\|/, $_ );

	my ( $job, $user, $tty, $phone, $date, $acct_h, $status );
	my ( $day, $time, $yy );

	# labor F262958 |13032548 |acct|Thu Dec  1 05:05:13 2005| success
	if ( $_[0] =~ /^(\S+)\s+(F\d+)/ )		# old format
	{
	    ( $job, $user, $tty, $phone, $date, $acct_h, $status ) = 
		( $2, $1, '', $_[1], $_[3], $_[2], $_[4] );

	    unless ( $date =~ /(.*) (\d\d:\d\d:\d\d) (.*)/ )
	    {
		print "</table><p><b>internal error (2a): can't parse '$date' in the following acct.log input line:<br>$_</b><p></body></html>\n";
		exit 0;
	    }
	    ( $day, $time, $yy ) = ( $1, $2, $3 );
	}
	else						# new format
	# F325827|labor|tty210| 93948740 |0|20060119 12:35:32|acct|success
	{
	    ( $job, $user, $tty, $phone, $date, $acct_h, $status ) = 
		( $_[0], $_[1], $_[2], $_[3], $_[5], $_[6], $_[7] );

	    unless ( $date =~ /(\d{4})(\d\d)(\d\d) (\d\d:\d\d:\d\d)/ )
	    {
		print "</table><p><b>internal error (2b): can't parse '$date' in the following acct.log input line:<br>$_</b><p></body></html>\n";
		exit 0;
	    }
	    ( $day, $time, $yy ) = ( "$3.$2.", $4, $1 );
	}

	# remove "eye candy" whitespace
	$phone =~ s/\s//go;

	next unless $day eq $doday;

	if ( ! defined( $by_phone{$phone} ))
		{ $by_phone{$phone} = { 'ok' => 0, 'fail' => 0 }; }

	if ( $status =~ /fail (\d+):/ )
	{
	    $fail++; $fail[$1]++;
	    if ( !defined( $by_phone{$phone}{$1} ))
		{ $by_phone{$phone}{$1} = 0;}

	    $by_phone{$phone}{$1}++;
	    $by_phone{$phone}{'fail'}++;
	}
	else
	{
	    $success++;
	    $by_phone{$phone}{'ok'}++;
	}

	#print "$_<br>\n";
    }
    close A;

    my $total = $success+$fail;

    print <<EOF;
<div align="center">
Sendeversuche insgesamt: <b>$total</b>&nbsp;&nbsp;&nbsp;
Erfolgreiche &Uuml;bertragungen: <b>$success</b>&nbsp;&nbsp;&nbsp;
Fehlerversuche gesamt: <b>$fail</b>
<hr>
Jobs nach Telefonnummern aufgeschl&uuml;sselt, sortiert nach Zahl der
Fehlversuche<p>
<table border="1">
<TR><TH>Telefonnr.<TH>Status<TH>Anz.<TH>Beschreibung</TR>
EOF

    my %reason = 
	( 'fail' => "Fehlversuche insgesamt",
	  '1'    => "Syntax-Fehler beim Aufruf",
	  '2'    => "lokales Modem nicht verf&uuml;gbar",
	  '3'    => "Fehler bei der Modeminitialisierung",
	  '4'	 => "Empf&auml;nger besetzt (BUSY)",
	  '5'    => "Kein W&auml;hlton (NO DIALTONE)",
	  '10'   => "Fehler beim Verbindungsaufbau (NO CARRIER)",
	  '11'   => "Fehler am Seitenanfang (XON)",
	  '12'   => "Fehler bei der Seiten&uuml;bertragung",
	  '15'   => "Sendfax durch Signal abgeschossen" );

    foreach $p ( sort { $by_phone{$b}{'fail'} <=> $by_phone{$a}{'fail'} ||
                        $by_phone{$b}{'ok'} <=> $by_phone{$a}{'ok'} ||
			$a cmp $b }
			 ( keys( %by_phone ) ) )
    {
	printf "<TR><TD>$p<TD>OK<TD>%d<TD>erfolgreich</TR>",
			$by_phone{$p}{'ok'};

	next if ( $by_phone{$p}{'fail'} == 0 );

	my $total = $by_phone{$p}{'ok'} + $by_phone{$p}{'fail'};

	foreach $c ( keys %reason )
	{
	    next unless defined( $by_phone{$p}{$c} );
	    $bg = &getcolor( 100*$by_phone{$p}{$c} / $total );

	    printf "<TR><TD>&nbsp;<TD$bg>$c<TD$bg>%d<TD$bg>$reason{$c}</TR>",
			$by_phone{$p}{$c};
	}
    }

print <<EOF;
</table></div>
EOF

}

# jobs sortieren: die mit den meisten Fehlern ganz nach vorne
sub by_error
{
    return $by_phone{$a}{'fail'} <=> $by_phone{$b}{'fail'};
}

