]> git.etc.gen.nz Git - spong.git/commitdiff
Initial import
authorStephen L Johnson <sjohnson@monsters.org>
Fri, 22 Oct 1999 01:04:01 +0000 (01:04 +0000)
committerStephen L Johnson <sjohnson@monsters.org>
Fri, 22 Oct 1999 01:04:01 +0000 (01:04 +0000)
src/lib/Spong/Ack.pm [new file with mode: 0755]
src/lib/Spong/AckList.pm [new file with mode: 0755]
src/lib/Spong/History.pm [new file with mode: 0755]
src/lib/Spong/HistoryList.pm [new file with mode: 0755]
src/lib/Spong/HostList.pm [new file with mode: 0755]
src/lib/Spong/Info.pm [new file with mode: 0755]
src/lib/Spong/Service.pm [new file with mode: 0755]
src/lib/Spong/ServiceList.pm [new file with mode: 0755]

diff --git a/src/lib/Spong/Ack.pm b/src/lib/Spong/Ack.pm
new file mode 100755 (executable)
index 0000000..fd38fb5
--- /dev/null
@@ -0,0 +1,152 @@
+#!/usr/local/bin/perl
+#
+# This object represents an acknowledgment.  When a problem happens, you can
+# associate one of these objects with the host to let operators or other staff
+# know that you are aware of the problem, and what you are doing about it.
+#
+#   host          - string FQDN of the machine its associated with
+#   start         - time() that the ack was created
+#   end           - time() that the ack will be over
+#   contact       - arbitrary contact info
+#   services      - arbitrary service names (sep by commas is standard)
+#   message       - arbitrary message
+#
+# + new()         - constructor (sets instance vars to arguments passed in)
+# + gets/sets()   - magical set/get functions (autoloaded based on func name)
+# + display()     - output format and view
+#
+# History:
+# (1) Cleaned up (Ed July 30, 1997);
+
+package Spong::Ack;
+
+# Constructor.  This expects all of its instance vars to be passed in when the
+# object is created.  See above for specific information on the variables.
+# This object is dependent on the AckList object for loading the information
+# about it from the database.
+
+sub new {
+   my( $class, $host, $start, $end, $contact, $services, $message ) = @_;
+   my $self = {};
+
+   $self->{'host'}     = $host;
+   $self->{'start'}    = $start;
+   $self->{'end'}      = $end;
+   $self->{'contact'}  = $contact;
+   $self->{'services'} = $services;
+   $self->{'message'}  = $message;
+
+   bless $self;
+   return $self;
+}
+
+
+# Get/Set methods, nothing fancy here...
+
+sub host { my $var = 'host';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub start { my $var = 'start';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub end { my $var = 'end';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub contact { my $var = 'contact';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub services { my $var = 'services';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub message { my $var = 'message';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+
+
+# Display summary.  Does both text and html, doesn't make any calls to 
+# sub-objects or other helper objects, just spits out the data that it has.
+#
+# brief:      Just shows name of services and time ack is over.
+# standard:   Shows detailed record of service information (contact, message)
+# full:       (same as standard)
+
+sub display {
+   my( $self, $type, $view ) = @_;
+
+   if( $type eq "text" ) { return $self->display_text( $view ); }
+   if( $type eq "html" ) { return $self->display_html( $view ); }
+}
+
+sub display_text {
+   my( $self, $format ) = @_;
+   
+   if( $format eq "brief" ) {
+      print $self->host(), "-", $self->services(), "-", $self->end();
+   } elsif( $format eq "standard" ) {
+      my( $d1, $m1, $y1 ) = (localtime( $self->end()))[3,4,5];
+      my( $d2, $m2, $y2 ) = (localtime())[3,4,5];
+      my $shost = $main::HOSTS{$self->host()}->{'display_name'};
+      my $date;
+      my $message = $self->message();
+
+      $shost = (split( /\./, $self->host() ))[0] unless $shost;
+      print substr( $shost, 0, 12 ), " "x(14-length(substr($shost, 0, 12)));
+
+      print substr( $self->services(), 0, 7 );
+      print " "x(9-length(substr( $self->services(), 0, 7 )));
+
+      if( $d1 == $d2 && $m1 == $m2 && $y1 == $y2 ) {
+        $date = POSIX::strftime( "%H:%M", localtime($self->end()) );
+      } else {
+        $date = POSIX::strftime( "%D", localtime($self->end()) );
+      }
+      print $date, " "x(12-length($date));
+
+      $message =~ s/[\n]*$//;
+      print substr( $message, 0, 45 );
+   } elsif( $format eq "full" ) {
+      $message = $self->message();
+
+      print "Service(s): ", $self->services(), "\n";
+      print "Ack. Until: ", scalar localtime( $self->end() ), "\n";
+      print "Ack. By:    ", $self->contact(), "\n";
+
+      if( $message ne "" ) {
+        $message =~ s/[\n]*$/\n/; print "Message:    $message"; }
+   } elsif( $format eq "special" ) {
+      print $self->host(), ":", $self->services(), ":", $self->end();
+   }
+}
+
+sub display_html {
+   my( $self, $format ) = @_;
+   
+   if( $format eq "brief" ) {
+      print "<table border=0 cellspacing=1 cellpadding=1>\n";
+      print "<tr><td align=center valign=top>";
+      print $self->services();
+      print "</td><td align=left valign=top>";
+      print scalar localtime( $self->end() );
+      print "</td></tr>\n";
+      print "</table>\n";
+
+   } elsif( $format eq "standard" || $format eq "full" ) {
+      my $message = $self->message();
+      my $id = $self->host() . "-" . $self->services() . "-" . $self->end();
+
+      print "<table width=100% border=0 cellspacing=1 cellpadding=1><tr>\n";
+      print "<td colspan=2>";
+      print "<a href=\"" . $main::WWWACK . "/delete/$id\">Delete</a></td>";
+      print "</tr><td nowrap width=15% align=left>Service(s): </td>\n"; 
+      print "<td width=85% align=left>", $self->services(), "</td></tr>\n";
+
+      print "<tr><td nowrap width=15% align=left>Ack. Until: </td>\n";
+      print "<td width=85% align=left>", scalar localtime( $self->end() );
+      print "</td></tr>\n";
+
+      print "<tr><td nowrap width=15% align=left>Ack. By: </td>\n";
+      print "<td width=85% align=left>", $self->contact(), "</td></tr>\n";
+
+      if( $message ne "" ) {
+        print "<tr><td nowrap width=15% align=left>Message: </td>\n";
+        print "<td width=85% align=left>$message</td></tr>\n";
+      }
+      print "</table>\n";
+   }
+}
+
+1;
diff --git a/src/lib/Spong/AckList.pm b/src/lib/Spong/AckList.pm
new file mode 100755 (executable)
index 0000000..786e094
--- /dev/null
@@ -0,0 +1,124 @@
+#!/usr/local/bin/perl
+#
+# This object represents an list of acknowledgments associated with a host.  It
+# knows how to load the Acks associated with a given host (since there can be
+# more then one).  Acks do not know how to load themselves, they must be 
+# brought to life from an AckList object.
+#
+#   host          - string FQDN of the machine its associated with
+#   acks          - list of Ack objects
+#
+# + new()         - constructor (sets instance vars to arguments passed in)
+# + gets/sets()   - magical set/get functions (autoloaded based on func name)
+# + display()     - output format and view
+#
+# History:
+# (1) Cleaned up (Ed July 30, 1997);
+
+use Spong::Ack;
+
+package Spong::AckList;
+
+# Constructor.  This requires the FQDN of the host that the Acks are associated
+# with.  If there are no Acks associated with that host, then undef is returned
+# otherwise the AckList object is returned and the list of Acks are loaded.
+
+sub new {
+   my( $class, $host ) = @_;
+   my( @list, $file, $message );
+   my $self = {};
+
+
+   if( $host ) {
+      # Go through the list of acknowledgments for this host and add Ack
+      # objects for each one.
+      
+      opendir( DIR, "$main::SPONGDB/$host/acks" );
+      while( defined( $file = readdir( DIR ) ) ) {
+        if( $file =~ /^\d+-(\d+)-(\d+)$/ ) {
+           my( $s, $e ) = ($1, $2);
+           if( time() >= $s && time() <= $e ) {
+              $message = "";
+              open( FILE, "$main::SPONGDB/$host/acks/$file" );
+              my $header = <FILE>; chomp $header;
+              my( $user, $serv ) = ( $header =~ /^(\S+) (.*)$/ );
+              while( <FILE> ) { $message .= $_; }
+              close( FILE );
+              
+              push(@list, 
+                   Spong::Ack->new( $host, $s, $e, $user, $serv, $message ));
+           }
+        }
+      }
+      closedir( DIR );
+      
+      # If we found any, then continue setting up and return self, otherwise
+      # return undef.
+      
+      if( $#list >= 0 ) {
+        $self->{'host'} = $host;
+        $self->{'acks'} = \@list;
+        bless $self;
+        return $self;
+      } else { 
+        return undef;
+      }
+   } else {
+      $self->{'host'} = $host;
+      $self->{'acks'} = \@list;
+
+      bless $self;
+      return $self;
+   }
+}
+
+# Get/Set methods, acks() is a little fancy in that it return a list rather
+# then just the list reference (easier for others to deal with).
+
+sub host { my $var = 'host';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub acks { my $var = 'acks';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return @{$_[0]->{$var}}; }
+
+
+# Used to add acknowledgements to the list.  Typically used in the case where
+# you want to show acknowledgements on a variety of hosts and you start with
+# a blank list.
+
+sub add_acks {
+   my( $self, $add ) = @_;
+
+   if( ref( $add ) eq "Spong::AckList" ) {
+      foreach( $add->acks() ) { 
+        push( @{$self->{'acks'}}, $_ );
+      }
+   } elsif( ref( $add ) eq "Spong::Ack" ) {
+      push( @{$self->{'acks'}}, $add );
+   }
+}
+
+
+# Pretty simple display, just iterate over the Acks and tell each of them to
+# display themselves in the way that we were told.
+
+sub display {
+   my( $self, $format, $view ) = @_;
+
+   if( $view eq "standard" && $format eq "text" ) {
+      print "Host          Service  Until       Message\n";
+      print "------------  -------  ----------  ";
+      print "------------------------------------------\n";
+   }
+
+   sub bytime { return $b->end() <=> $a->end(); }
+
+   foreach( sort bytime $self->acks() ) { 
+      $_->display( $format, $view ); 
+      
+      if( $format eq "text" ) { print "\n"; }
+      if( $format eq "html" ) { print "<p>"; }
+   }
+}
+
+
+1;
diff --git a/src/lib/Spong/History.pm b/src/lib/Spong/History.pm
new file mode 100755 (executable)
index 0000000..89deb77
--- /dev/null
@@ -0,0 +1,113 @@
+#!/usr/local/bin/perl
+#
+# This object represents an event that has happened that a record should be
+# kept for.  The current case is when a service changes state (color change).
+# An record representing that change is written to the database so that you
+# can see a record of these events over time.
+#
+#   host          - string FQDN of the machine its associated with
+#   type          - type of history event (status change, etc...)
+#   time          - time (time() format) that the event occurred
+#   service       - service name this history is associated with
+#   color         - color of the service at time history event was created
+#   other         - summary information about the history event
+#
+# + new()         - constructor (sets instance vars to arguments passed in)
+# + gets/sets()   - magical set/get functions (autoloaded based on func name)
+# + display()     - output format and view
+#
+# History:
+# (1) Cleaned up (Ed July 31, 1997);
+
+package Spong::History;
+
+# Constructor.  A simple constructor that just sets all of the instance vars
+# according to what was passed in.  All vars should be set on creation.
+
+sub new {
+   my( $class, $host, $type, $time, $service, $color, $other ) = @_;
+   my $self = {};
+
+   $self->{'host'}    = $host;
+   $self->{'type'}    = $type;
+   $self->{'time'}    = $time;
+   $self->{'service'} = $service;
+   $self->{'color'}   = $color;
+   $self->{'other'}   = $other;
+
+   bless $self;
+   return $self;
+}
+
+# Get/Set methods, nothing fancy here...
+
+sub host { my $var = 'host';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub type { my $var = 'type';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub time { my $var = 'time';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub service { my $var = 'service';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub color { my $var = 'color';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub other { my $var = 'other';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+
+
+# Display summary.  Does both text and html, doesn't make any calls to 
+# sub-objects or other helper objects, just spits out the data that it has.
+# This is a little different in that there are not different views of the
+# data.  The views come into play with the HistoryList in that more days worth
+# of history is shown with the more detailed views.
+
+sub display {
+   my( $self, $type, $view ) = @_;
+
+   if( $type eq "text" ) { return $self->display_text( $view ); }
+   if( $type eq "html" ) { return $self->display_html( $view ); }
+}
+
+sub display_text {
+   my( $self, $view ) = @_;
+   my( $min, $hour ) = (localtime($self->time()))[1,2];
+   my $shost = $main::HOSTS{$self->host()}->{'display_name'};
+
+   $shost = (split( /\./, $self->host() ))[0] unless $shost;
+
+   print $self->color(), " "x(7-length($self->color()));
+   print sprintf( "%2.2d:%2.2d  ", $hour, $min );
+   print substr( $shost, 0, 12 ), " "x(14-length(substr($shost, 0, 12)));
+   print substr( $self->service(), 0, 5 );
+   print " "x(7-length(substr( $self->service(), 0, 5 )));
+   print $self->other(), "\n";
+}
+
+sub display_html {
+   my( $self, $view ) = @_;
+   my( $min, $hour ) = (localtime($self->time()))[1,2];
+   my $shost = $main::HOSTS{$self->host()}->{'display_name'};
+   my $color = $self->color();
+   my $service = $self->service();
+   my $other = $self->other();
+
+   $shost = (split( /\./, $self->host() ))[0] unless $shost;
+
+   print "<tr><td width=7% align=left valign=top>";
+
+   if( $main::WWW_USE_IMAGES == 1 ) {
+      print "<img src=\"!!WWWGIFS!!/$color.gif\" alt=$color border=0>";
+   } else {
+      print "<table width=20 border=0 cellspacing=0 cellpadding=0><tr>";
+      print "<td width=20 bgcolor=\"" . $main::WWW_COLOR{$color} . "\">";
+      print "&nbsp;</td></tr></table>";
+   }
+
+   print "</td><td width=12% align=center valign=top>";
+   printf ( "%2.2d:%2.2d</td>", $hour, $min );
+   print "<td width=15% align=left valign=top>$shost </td>\n";
+   print "<td width=15% align=left valign=top>$service</td>\n";
+   print "<td width=51% align=left valign=top>$other</td></tr>\n";
+}
+
+1;
diff --git a/src/lib/Spong/HistoryList.pm b/src/lib/Spong/HistoryList.pm
new file mode 100755 (executable)
index 0000000..a48a94c
--- /dev/null
@@ -0,0 +1,156 @@
+#!/usr/local/bin/perl
+#
+# This object represents a list of history events that happen on a given host
+# or a set of hosts.  HistoryLists are a little unique in that they don't have
+# to be associated with one specific host, they can contain events from a
+# number of hosts, and they can be populated from an empty container.
+#
+#   host          - string FQDN of the machine its associated with
+#   events        - list of history objects.
+#
+# + new()         - constructor (sets instance vars to arguments passed in)
+# + gets/sets()   - magical set/get functions (autoloaded based on func name)
+# + display()     - output format and view
+#
+# + add_history() - used to add history objects to the list
+#
+# History:
+# (1) Cleaned up (Ed July 31, 1997);
+
+use Spong::History;
+
+package Spong::HistoryList;
+
+# Constructor.  A simple constructor that will load all the history events
+# associated with a particular host (if a hostname is passed in), otherwise
+# it just sets up an empty object that expects to be filled by someone else.
+# If there are no History events associated with a host, then undef is returned
+
+sub new {
+   my( $class, $host ) = @_;
+   my $self = {};
+   my( @list );
+
+   # If we are given a host name, then look up the events for that host, 
+   # otherwise, just return a shell that will be filled later.
+
+   if( $host ) {
+      return undef if ! -d "$main::SPONGDB/$host/history";
+
+      # Go through the current history, and build history objects for each
+      # item in the history.
+
+      open( FILE, "$main::SPONGDB/$host/history/current" );
+      while( <FILE> ) { 
+        chomp;
+        if( /^status (\d+) (\S+) (green|yellow|red) (.*)$/ ) {
+           push( @list, 
+                 Spong::History->new( $host, "status", $1, $2, $3, $4 ) );
+        } elsif( /^ack (\d+) (\S+) (.*)$/ ) {
+           push( @list, 
+                 Spong::History->new( $host, "ack", $1, $2, "blue", $3 ));
+        }
+      }
+      close( FILE );
+   }
+
+   $self->{'host'}   = $host;
+   $self->{'events'} = \@list;
+
+   bless $self;
+   return $self;
+}
+
+
+# Get/Set methods, events() is a little fancy in that it return a list rather
+# then just the list reference (easier for others to deal with).
+
+sub host { my $var = 'host';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub events { my $var = 'events';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return @{$_[0]->{$var}}; }
+
+
+# Used to add history events to the list.  Typically used in the case where
+# you want to show history events on a variety of hosts and you start with
+# a blank list.
+
+sub add_history {
+   my( $self, $add ) = @_;
+
+   if( ref( $add ) eq "Spong::HistoryList" ) {
+      foreach( $add->events() ) { 
+        push( @{$self->{'events'}}, $_ );
+      }
+   } elsif( ref( $add ) eq "Spong::History" ) {
+      push( @{$self->{'events'}}, $add );
+   }
+}
+
+
+# Display summary.  Does both text and html, does rely on the History object
+# to display itself, but this adds to it by showing header information in
+# tables, and breaking output up by days.
+#
+# brief:      Shows events that have happened in the last 24 hours
+# standard:   Shows events that have happened in the last 7 days
+# full:       Shows events that have happened in the last 30 days
+
+sub display {
+   my( $self, $type, $view ) = @_;
+
+   if( $type eq "text" ) { return $self->display_text( $view ); }
+   if( $type eq "html" ) { return $self->display_html( $view ); }
+}
+
+sub display_text {
+   my( $self, $format ) = @_;
+   my( $event, $date );
+   my( $day ) = 24*60*60;
+
+   sub bytime { return $b->time() <=> $a->time(); }
+
+   foreach $event ( sort bytime $self->events() ) {
+      if( ($format eq "brief" && $event->time() > (time() - $day)) ||
+         ($format eq "standard" && $event->time() > (time() - 7*$day)) ||
+         ($format eq "full" && $event->time() > (time() - 31*$day)) ) {
+
+        my $dstr = POSIX::strftime( "%A, %D", localtime($event->time()) );
+        if( $dstr ne $date ) { 
+           if( $date ne "" ) { print "\n"; }
+           print "$dstr\n", "-"x78, "\n";
+           $date = $dstr; 
+        }
+        
+        $event->display_text();
+      }
+   }
+}
+
+sub display_html {
+   my( $self, $format ) = @_;
+   my( $event, $date );
+   my( $day ) = 24*60*60;
+
+   sub bytime { return $b->time() <=> $a->time(); }
+
+   foreach $event ( sort bytime $self->events() ) {
+      if( ($format eq "brief" && $event->time() > (time() - $day)) ||
+         ($format eq "standard" && $event->time() > (time() - 7*$day)) ||
+         ($format eq "full" && $event->time() > (time() - 31*$day)) ) {
+
+        my $dstr = POSIX::strftime( "%A, %D", localtime($event->time()) );
+        if( $dstr ne $date ) { 
+           if( $date ne "" ) { print "</table><br>"; }
+           print "$dstr\n<hr border=1 noshade>\n";
+           print "<table width=100% border=0 cellspacing=1 cellpadding=0>\n";
+           $date = $dstr; 
+        }
+
+        $event->display_html();
+      }
+   }
+   print "</table>\n";
+}
+
+1;
diff --git a/src/lib/Spong/HostList.pm b/src/lib/Spong/HostList.pm
new file mode 100755 (executable)
index 0000000..110afe9
--- /dev/null
@@ -0,0 +1,316 @@
+#!/usr/local/bin/perl
+#
+# This represents a collection of hosts, either a group (including all hosts),
+# a single host, or a list of hosts by FDQN name.
+#
+#   name          - name of the group the HostList represents
+#   hostnames     - a list of string names of the hosts in the list
+#   hosthash      - a hash of Host objects keyed by hostname.
+#
+# + new()         - constructor (sets instance vars to arguments passed in)
+# + gets/sets()   - magical set/get functions (autoloaded based on func name)
+# + display()     - output format and view
+#
+# + display_problems() - shows information only about hosts that have services
+#                        that indicate a problem
+#
+# + host          - used to get at a specific host by name
+# + hosts         - used to get back a generic list of hosts that are stored
+# + add           - adds a specific host to the list.
+#
+# History:
+# (1) Cleaned up (Ed July 31, 1997);
+
+use Spong::Host;
+
+package Spong::HostList;
+
+# This constructor expects one of four different types of options.  Either an
+# empty string specifying that no actual hosts will be loaded, the string
+# "all" to load all the hosts, the string containing a group name that
+# represents a list of hosts, or a reference to an array that contains a list
+# of hostnames in which case each host will be loaded individually.
+
+sub new {
+   my( $class, $group ) = @_;
+   my( @names, %hosts, $name ) = ();
+   my $self = {};
+
+   if( $group eq "" ) {
+      %hosts = ();
+      @names = ();
+      $self->{'name'} = "";
+   } elsif( ref( $group ) eq "ARRAY" ) {
+      foreach $name ( @$group ) {
+        my $object = Spong::Host->new( $name );
+        if( $object ) { $hosts{$name} = $object;  push( @names, $name ); }
+      }
+      $self->{'name'} = "";
+   } else {
+      if( $group eq "all" ) {
+        foreach $name ( @main::HOSTS_LIST ) {
+           my $object = Spong::Host->new( $name );
+           if( $object ) { $hosts{$name} = $object; push( @names, $name ); }
+        } 
+      } else {
+        if( ref( $main::GROUPS{$group}->{'members'} ) eq "ARRAY" ) {
+           foreach $name ( @{$main::GROUPS{$group}->{'members'}} ) {
+              my $object = Spong::Host->new( $name );
+              if( $object ) { $hosts{$name} = $object; push( @names, $name );}
+           }
+        }
+      }
+
+      $self->{'name'} = $group;
+   }
+
+   $self->{'hosthash'}      = \%hosts;
+   $self->{'hostnames'}     = [ @names ];
+
+   bless $self;
+   return $self;
+}
+
+
+# Get/Set methods, hostnames() is a little fancy in that it return a list
+# rather then just the list reference (easier for others to deal with).  The
+# hosthash function returns a reference however.
+
+sub name { my $var = 'name';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub hosthash { my $var = 'hosthash';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub hostnames { my $var = 'hostnames';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return @{$_[0]->{$var}}; }
+
+# Some specific functions that get at and manipulate the data in the instance
+# vars in more convenient ways
+
+sub host { 
+   return $_[0]->{'hosthash'}->{$_[1]}; }
+
+sub hosts { 
+   my $self = shift;
+   my( @tmp );
+
+   foreach( $self->hostnames() ) { push( @tmp, $self->host( $_ ) ); }
+   return @tmp;
+}
+
+sub add {
+   my( $self, $host ) = @_;
+   my $name = $host->name();
+
+   $self->{'hosthash'}->{$name} = $host;
+   push( @{$self->{'hostnames'}}, $name );
+}
+
+# Display summary.  Does both text and html, does rely on both the Host and
+# Service objects for some help.
+#
+# brief       Hostname and "one" color reflecting overall status of the host
+# standard    Table showing hosts vs services and the state of each service
+# full        Records for each host showing detailed info about its state
+
+sub display {
+   my( $self, $type, $view ) = @_;
+
+   $self->display_text( $view ) if $type eq "text";
+   $self->display_html( $view ) if $type eq "html";
+}
+
+# This displays a summary of all the hosts in this list in a text format 
+# suitable for displaying on dumb ascii terminals
+
+sub display_text {
+   my( $self, $format ) = @_;
+
+   if( $format eq "standard" ) {
+      my( %services, $host, $service, @names );
+
+      # Compute the total list of services running on the various hosts, and
+      # sort them alphabetically (except always put ping first).
+
+#      foreach $host ( $self->hosts() ) {
+#       foreach $service ( $host->service_names() ) { $services{$service}++;}}
+
+      if( grep( /^ping$/, keys %main::SERVICES ) ) { push( @names, "ping" ); }
+      foreach $service ( sort keys %main::SERVICES ) {
+        push( @names, $service ) unless $service eq "ping"; }
+     
+      # Print the horizontal axis of the table (names of the services)
+
+      print "-"x30, "-"x(($#names+1)*3), "\n";
+      foreach $cnt ( 0..4 ) {
+        if( $cnt == 0 ) {  print "Key: . = green,  ? = purple   "; }
+        if( $cnt == 1 ) {  print "     Y = yellow, R = red      "; }
+        if( $cnt == 2 ) {  print "     B = blue                 "; }
+        if( $cnt == 3 ) { print " "x30; }
+        if( $cnt == 4 ) { print "Host:", " "x25; }
+        foreach $service ( @names ) { 
+           if( length($service) - 5 + $cnt >= 0 ) {
+              print substr( $service, (length($service) - 5 + $cnt), 1 ),"  ";
+           } else {
+              print "   ";
+           }
+        }
+        print "\n";
+      }
+      print "-"x30, "-"x(($#names+1)*3), "\n";
+
+      # Now go through each host, and fill in the table.
+
+      foreach $host ( $self->hosts() ) {
+        print substr( $host->name(), 0, 29 );
+        print " "x(30-length(substr($host->name(), 0, 29)));
+        foreach $service ( @names ) {
+           my $servobj = $host->service( $service );
+           if( $servobj ) {
+              $servobj->display_text( "really_brief" );
+           } else {
+              print "   ";
+           }
+        }
+        print "\n";
+      }
+      print "\n";
+   } elsif( $format eq "full" ) {
+
+      # This goes through each host, and has each one print a textual record
+      # of the problem it is currently having, this would include a summary
+      # of the problem services, date/time the problem occurred, and contact
+      # information (as well as acknowledgments if they are available).
+
+      foreach $host ( $self->hosts() ) {
+        $host->display_problem( "text" ); print "\n";
+      }
+   }
+}
+
+# This displays a summary of all the hosts in this list in an HTML format
+# suitable for displaying on web clients capable of displaying tables.
+
+sub display_html { 
+   my( $self, $format ) = @_;
+
+   if( $format eq "standard" ) {
+      my( %services, $host, $service, @names );
+
+      # Compute the total list of services running on the various hosts, and
+      # sort them alphabetically (except always put ping first).
+      
+      if( grep( /^ping$/, keys %main::SERVICES ) ) { push( @names, "ping" ); }
+      foreach $service ( sort keys %main::SERVICES ) {
+        push( @names, $service ) unless $service eq "ping"; }
+      
+      # Print the horizontal axis of the table (names of the services)
+      
+      print "<table border=1 cellspacing=0 cellpadding=1><tr>";
+      print "<td align=center width=80 nowrap><b>Host</b></td>\n";
+      foreach $service ( @names ) { 
+        print "<td align=center valign=bottom width=25>\n";
+        print "<font size=-1><a href=\"!!WWWSPONG!!/help/$service\">";
+        print "$service</a></font></td>\n"; 
+      }
+      
+      print "</tr>\n\n";
+      
+      # Now go through each host, and fill in the table.
+      
+      foreach $host ( $self->hosts() ) { 
+        my $hostname = $host->name();
+        my $short = $main::HOSTS{$hostname}->{'display_name'};
+        $short = (split( /\./, $hostname ))[0] unless $short;
+        
+        print "<tr><td align=left nowrap>\n";
+        print "<a href=\"!!WWWSPONG!!/host/$hostname\">$short</a></td>\n";
+        
+        foreach $service ( @names ) {
+           my $servobj = $host->service( $service );
+           
+           if( $servobj ) {
+              my $col = $servobj->color();
+              
+              if( $main::WWW_USE_IMAGES == 1 ) {
+                 print "<td align=center><a href=\"!!WWWSPONG!!/service/";
+                 print "$hostname/$service\">";
+                 print "<img src=\"!!WWWGIFS!!/$col.gif\" alt=$col border=0>";
+                 print "</a>";
+              } else {
+                 print "<td align=center bgcolor=\"";
+                 print $main::WWW_COLOR{$col} . "\" width=25>";
+                 print "<a href=\"!!WWWSPONG!!/service/$hostname/$service\">";
+                 print "<font color=\"" . $main::WWW_COLOR{$col} . "\">";
+                 print "___</font></a>";
+              }
+              print "</td>";
+           } else {
+              if( $main::WWW_USE_IMAGES == 1 ) {
+                 print "<td align=center width=25> - </td>\n"; 
+              } else {
+                 print "<td align=center width=25>&nbsp;</td>\n";
+              }
+           }
+        }
+      }        
+      print "</tr></table>";
+
+   } elsif( $format eq "full" ) {
+
+      # This goes through each host, and has each one print a textual record
+      # of the problem it is currently having, this would include a summary
+      # of the problem services, date/time the problem occurred, and contact
+      # information (as well as acknowledgments if they are available).
+      
+      foreach $host ( $self->hosts() ) {
+        $host->display_problem( "html" ); print "\n";
+      }
+   }
+}
+
+
+# These methods all display summary information about only those machines
+# that are currently registering a problem, or that have services that have
+# not been updated (showing purple) - it also relays information about 
+# services that have been acknowledged.
+#
+# brief       Hostname and "one" color reflecting overall status of the host
+# standard    Table showing hosts vs services and the state of each service
+# full        Records for each host showing detailed info about the problems
+
+sub display_problems {
+   my( $self, $type, $view ) = @_;
+   my( $badhosts ) = Spong::HostList->new( "" );
+   my( $host, $problem, $purple );
+
+   foreach $host ( $self->hosts() ) {
+      if( $host->has_problem() ) {
+         $problem = 1;
+        $badhosts->add( $host );
+      } elsif( $host->has_color( "purple" ) ) {
+        $purple = 1; 
+      }
+   }
+
+   if( $problem ) {
+      $badhosts->display( $type, $view );
+   } else {      
+      if( $view eq "full" ) {
+        if( $purple ) { 
+           print "<p><font color=green><b>"       if( $type eq "html" );
+           print "No known problems.\n";
+           print "</b></font>"                    if( $type eq "html" );
+           print "<p><font size=-1 color=purple>" if( $type eq "html" );
+           print "Although some information is out of date which might ";
+           print "indicate a problem.\n";
+           print "</font>"                        if( $type eq "html" );
+        } else {
+           print "<p><font color=green><b>"       if( $type eq "html" );
+           print "No current problems.\n";
+           print "</b></font>"                    if( $type eq "html" );
+        }
+      }
+   }
+}
+
+1;
diff --git a/src/lib/Spong/Info.pm b/src/lib/Spong/Info.pm
new file mode 100755 (executable)
index 0000000..ca87378
--- /dev/null
@@ -0,0 +1,125 @@
+#!/usr/local/bin/perl
+#
+# This is a simple class which just represents some information that is
+# provided by the spong administrator about the hosts that are being
+# monitored.  The administrator can provide documentation in any number of
+# formats depending on how energetic he/she is.  He/she can also choose not to
+# provide this information at all, and nothing will be displayed.
+#
+# There is only one Info class associated with each Host, so this class knows
+# how to load itself from the database.
+#
+#   host          - string FQDN of the machine its associated with
+#
+# + new()         - constructor (sets instance vars to arguments passed in)
+# + gets/sets()   - magical set/get functions (autoloaded based on func name)
+# + display()     - output format and view
+#
+# History:
+# (1) Cleaned up (Ed July 31, 1997);
+
+package Spong::Info;
+
+# Object creation... Just checks to see if any of the required documentation
+# files have been provided by the spong administrator, and if not it returns
+# undef rather then a real life object...
+
+sub new {
+   my( $class, $host ) = @_;
+   my $self = {};
+   my $ok;
+
+   if( -f "$main::SPONGDB/$host/info/info.txt" )  { $ok = 1; }
+   if( -f "$main::SPONGDB/$host/info/info.html" ) { $ok = 1; }
+
+   if( -f "$main::SPONGDB/$host/info/info.brief.txt" )    { $ok = 1; }
+   if( -f "$main::SPONGDB/$host/info/info.standard.txt" ) { $ok = 1; }
+   if( -f "$main::SPONGDB/$host/info/info.full.txt" )     { $ok = 1; }
+
+   if( -f "$main::SPONGDB/$host/info/info.brief.html" )    { $ok = 1; }
+   if( -f "$main::SPONGDB/$host/info/info.standard.html" ) { $ok = 1; }
+   if( -f "$main::SPONGDB/$host/info/info.full.html" )     { $ok = 1; }
+
+   return undef if ! $ok;
+
+   $self->{'host'} = $host;
+   bless $self;
+   return $self;
+}
+
+
+# Get/Set methods, nothing fancy here...
+
+sub host { my $var = 'host';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+
+
+# Display summary.  Does both text and html, doesn't make any calls to 
+# sub-objects or other helper objects, just spits out the data that it has.
+#
+# This object is a little different in that it shows the best possible
+# documentation based on what is provided.  It checks to see if the
+# administrator has provided a specific view of documentation, but if it
+# doesn't exist then it just keep looking for documentation until it finds
+# some to print.
+
+sub display {
+   my( $self, $type, $view ) = @_;
+
+   if( $type eq "text" ) { return $self->display_text( $view ); }
+   if( $type eq "html" ) { return $self->display_html( $view ); }
+}
+
+sub display_text {
+   my( $self, $format ) = @_;
+   my $host = $self->host();
+   my( @cycle );
+
+   if( $format eq "brief" ) {
+      @cycle = ( "brief.txt", "txt", "standard.txt", "full.txt" );
+   } elsif( $format eq "standard" ) {
+      @cycle = ( "standard.txt", "txt", "full.txt", "brief.txt" );
+   } elsif( $format eq "full" ) {
+      @cycle = ( "full.txt", "txt", "brief.txt", "standard.txt" );
+   }
+   
+   foreach( @cycle ) {
+      if( -f "$main::SPONGDB/$host/info/info.$_" ) {
+        open( FILE, "$main::SPONGDB/$host/info/info.$_" );
+        while( <FILE> ) { print $_; }
+        close( FILE );
+        return;
+      }
+   }
+   
+   print "Only information in HTML is provided.\n";
+}
+
+sub display_html {
+   my( $self, $format ) = @_;
+   my $host = $self->host();
+   my( @cycle );
+
+   if( $format eq "brief" ) {
+      @cycle = ( "brief.html", "html", "brief.txt", "standard.html", 
+                "standard.txt", "full.html", "full.txt" ); 
+   } elsif( $format eq "standard" ) {
+      @cycle = ( "standard.html", "html", "standard.txt", "full.html", 
+                "full.txt", "breif.html", "brief.txt" );
+   } elsif( $format eq "full" ) {
+      @cycle = ( "full.html", "html", "full.txt", "brief.html", "brief.txt", 
+                "standard.html", "standard.txt" );
+   }
+
+   foreach( @cycle ) {
+      if( -f "$main::SPONGDB/$host/info/info.$_" ) {
+        open( FILE, "$main::SPONGDB/$host/info/info.$_" );
+        while( <FILE> ) { print $_; }
+        close( FILE );
+        return;
+      }
+   }
+}
+
+1;
+
diff --git a/src/lib/Spong/Service.pm b/src/lib/Spong/Service.pm
new file mode 100755 (executable)
index 0000000..79b93db
--- /dev/null
@@ -0,0 +1,386 @@
+#!/usr/local/bin/perl
+#
+# This object represents a service that is running on a given machine.  It
+# contains information such as the state of the service (red,green,etc...), the
+# time the service was last updated, summary information about the state, and
+# additional information helpful if there is a problem.
+#
+#   host          - string FQDN of the machine its associated with
+#   name          - string name of the service
+#   color         - computed color of the service (can be purple or blue)
+#   rcolor        - reported color of the service (only red, yellow, green)
+#   rtime         - reported last updated time of the service (time() format)
+#   stime         - reported start of current status (time() format)
+#   summary       - short summary providing additional info about the service
+#   message       - more detailed information about the service
+#
+# + new()         - constructor (sets instance vars to arguments passed in)
+# + gets/sets()   - magical set/get functions (autoloaded based on func name)
+# + display()     - output format and view
+#
+# + has_problem() - 
+#
+# History:
+# (1) Cleaned up (Ed July 31, 1997);
+
+package Spong::Service;
+
+# Constructor.  This is a lazy constructor in that it doesn't get all it's 
+# instance vars set at the time of creation.  Vars are loaded as they are asked
+# for, this gives us a performance gain.  This object is dependent on the 
+# ServiceList object for loading the information about it from the database.
+
+sub new {
+   my( $class, $host, $service ) = @_;
+   my $self = {};
+
+   # A hack to get around a local problem.
+
+   if( $service eq "procs" ) { $service = "jobs"; }
+
+   $self->{'name'}    = $service;
+   $self->{'host'}    = $host;
+   $self->{'color'}   = "";
+   $self->{'rcolor'}  = "";
+   $self->{'rtime'}   = "";
+   $self->{'summary'} = "";
+   $self->{'message'} = "";
+   $self->{'stime'}   = "";
+
+   bless $self;
+   return $self;
+}
+
+
+# Get/Set methods, nothing fancy here...
+
+sub host { my $var = 'host';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+
+sub name { 
+   my $var = 'name';
+
+   # A hack to get around a local problem.
+
+   if( defined $_[1] ) { 
+      if( $_[1] eq "procs" ) {
+        $_[0]->{$var} = "jobs";
+      } else {
+        $_[0]->{$var} = $_[1]; 
+      } 
+   }
+   return $_[0]->{$var}; 
+}
+
+sub color { my $var = 'color';
+   if( defined $_[1] ) { 
+      $_[0]->{$var} = $_[1]; 
+   } else {
+      my $name = $_[0]->host();
+
+      # If there is a problem reported, and we are in this machine's down time
+      # then don't report it, instead mark it as blue.
+
+      foreach( @{$main::HOSTS{$name}->{'down'}} ) {
+        my( $day, $shour, $smin, $ehour, $emin ) = 
+           ( /^(.):(\d+):(\d+)-(\d+):(\d+)$/ );
+        my( $nmin, $nhour, $nday ) = (localtime(time()))[1,2,6];
+        
+        if( $day eq "*" || $nday eq $day ) {
+           my $ntotal = $nhour * 60 + $nmin;
+           my $stotal = $shour * 60 + $smin;
+           my $etotal = $ehour * 60 + $emin;
+
+           if( $ntotal >= $stotal && $ntotal <= $etotal ) {
+              return 'blue';
+           }
+        }
+      }
+   }
+
+   return $_[0]->{$var}; 
+}
+
+sub rcolor { my $var = 'rcolor';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub rtime { my $var = 'rtime';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub stime { my $var = 'stime';
+   if( $_[0]->{$var} eq "" ) { $_[0]->summary(); } return $_[0]->{$var}; }
+
+
+# These are both lazy loading functions.  If we don't have summary and message
+# information already loaded, then go out and grab it from the database and
+# return that information to the user.
+
+sub summary {
+   my $self = shift;
+   my $file;
+
+   # This is a local hack to get around the procs/jobs problems, first look
+   # for the jobs service, if it is not there, look for procs...
+   
+   if( $self->name() eq "jobs" ) {
+      $file = $self->host()."/services/".$self->name()."-".$self->rcolor();
+      if( ! -f "$main::SPONGDB/$file" ) {
+        $file = $self->host()."/services/procs-".$self->rcolor();
+      }
+   } else {
+      $file = $self->host()."/services/".$self->name()."-".$self->rcolor();
+   }
+
+
+   if( ! $self->{'summary'} ) {
+      open( FILE, "$main::SPONGDB/$file" );
+      my $header = <FILE>; chomp $header;
+      # If a timestamp is present, read it and set start time
+      if ($header =~ /^timestamp (\d+) (\d+)/ ) {
+         $self->{'stime'} = $1; 
+         $header = <FILE>; chomp $header;
+      }
+      ($self->{'rtime'}, $self->{'summary'}) = ( $header =~ /^(\d+) (.*)$/ );
+      while( <FILE> ) { $self->{'message'} .= $_; }
+      close( FILE );
+   }
+
+   return $self->{'summary'};
+}
+
+sub message {
+   my $self = shift;
+   my $file;
+
+   # This is a local hack to get around the procs/jobs problems, first look
+   # for the jobs service, if it is not there, look for procs...
+   
+   if( $self->name() eq "jobs" ) {
+      $file = $self->host()."/services/".$self->name()."-".$self->rcolor();
+      if( ! -f $file ) {
+        $file = $self->host()."/services/procs-".$self->rcolor();
+      }
+   } else {
+      $file = $self->host()."/services/".$self->name()."-".$self->rcolor();
+   }
+
+   if( ! $self->{'message'} ) {
+      open( FILE, "$main::SPONGDB/$file" );
+      my $header = <FILE>; chomp $header;
+      # If timestamp header present, read it and set start time
+      if ($header =~ /^timestamp (\d+) (\d+)/ ) {
+         $self->{'stime'} = $1; 
+         $header = <FILE>; chomp $header;
+      }
+      ($self->{'rtime'}, $self->{'summary'}) = ( $header =~ /^(\d+) (.*)$/ );
+      while( <FILE> ) { $self->{'message'} .= $_; }
+      close( FILE );
+   }
+
+   return $self->{'message'};
+}
+
+
+# This returns true or false depending on if there is a problem with the 
+# service.  This allows you to define some smarts into what a problem is.
+
+sub has_problem {
+   my $self = shift;
+
+   if( $self->color() eq "red" ) {
+      return 1; 
+   } else {
+      return 0;
+   }
+}
+
+
+# Display summary.  Does both text and html, doesn't make any calls to 
+# sub-objects or other helper objects, just spits out the data that it has.
+#
+# brief:      Just show the color of the service (or a colored ball in html)
+# standard:   Shows a line stating the name/color/updated/summary information
+# full:       Shows standard information plus any messages the service has
+
+sub display {
+   my( $self, $type, $view ) = @_;
+
+   if( $type eq "text" ) { return $self->display_text( $view ); }
+   if( $type eq "html" ) { return $self->display_html( $view ); }
+}
+
+sub display_text {
+   my( $self, $format ) = @_;
+   
+   if( $format eq "really_brief" ) {
+      if( $self->color() eq "green" )  { print ".  "; }
+      if( $self->color() eq "purple" ) { print "?  "; }
+      if( $self->color() eq "yellow" ) { print "Y  "; }
+      if( $self->color() eq "red" )    { print "R  "; }
+      if( $self->color() eq "blue" )   { print "B  "; }
+   } elsif( $format eq "brief" ) {
+      print $self->color(), "\n";
+   } elsif( $format eq "standard_table" ) {
+      my( $d1, $m1, $y1 ) = (localtime( $self->rtime()))[3,4,5];
+      my( $d2, $m2, $y2 ) = (localtime())[3,4,5];
+      my $name = substr( $self->name(), 0, 7 );
+      my $color = $self->color();
+      my $summary = $self->summary();
+      my $firstline = 1;
+
+      print $name, " "x(8-length($name));
+      print $color, " "x(9-length($color));
+
+      if( $d1 == $d2 && $m1 == $m2 && $y1 == $y2 ) {
+        $date = POSIX::strftime( "%H:%M", localtime($self->rtime()) );
+      } else {
+        $date = POSIX::strftime( "%D", localtime($self->rtime()) );
+      }
+      print $date, " "x(11-length($date));
+
+      do {
+        print " "x30 if ! $firstline++;
+        print substr( $summary, 0, 50 ), "\n";
+        $summary = substr( $summary, 51 );
+      } while( length( $summary > 50 ) );
+   } elsif( $format eq "standard" ) {
+      print "Name    Color    Updated    Summary\n";
+      print "------- -------- ---------- ", "-"x50, "\n";
+      $self->display_text( "standard_table" );
+   } elsif( $format eq "full" ) {
+      $self->display_text( "standard" );
+      print "\nStatus unchanged in " .
+           &format_duration($self->stime(),$self->rtime) . "\n";
+      print "-"x78, "\n";
+      print $self->message(), "\n";
+   }
+}
+
+sub display_html {
+   my( $self, $format ) = @_;
+   my $host  = $self->host;
+   my $name  = $self->name();
+   my $color = $self->color();
+   my( $d1, $m1, $y1 ) = (localtime( $self->rtime()))[3,4,5];
+   my( $d2, $m2, $y2 ) = (localtime())[3,4,5];
+
+   if( $format eq "brief" ) {
+      print "<a href=\"!!WWWSPONG!!/service/$host/$name\">\n";
+
+      if( $main::WWW_USE_IMAGES == 1 ) {
+        print "<img src=\"!!WWWGIFS!!/$color.gif\" alt=$color border=0>";
+      } else {
+        print "<table border=0 cellspacing=0 cellpadding=0><tr>";
+        print "<td width=20 bgcolor=\"" . $main::WWW_COLOR{$color} ;
+        print "\"><font color=\"" . $main::WWW_COLOR{$color} . "\">";
+        print "___</font></td></tr></table>\n";
+      }
+      print "</a>";
+   } elsif( $format eq "standard_table" ) {
+      print "<tr><td align=left valign=top nowrap>\n"; 
+      print "<a href=\"!!WWWSPONG!!/service/$host/$name\">$name</a></td>\n";
+      print "<td align=center valign=top>\n"; 
+
+      if( $main::WWW_USE_IMAGES == 1 ) {
+        print "<a href=\"!!WWWSPONG!!/service/$host/$name\">\n";
+        print "<img src=\"!!WWWGIFS!!/$color.gif\" alt=$color border=0></a>";
+      } else {
+        print "<table border=0 cellspacing=0 cellpadding=0><tr>";
+        print "<td width=20 bgcolor=\"" . $main::WWW_COLOR{$color} . "\">";
+        print "<a href=\"!!WWWSPONG!!/service/$host/$name\">";
+        print "<font color=\"" . $main::WWW_COLOR{$color} . "\">___</font>";
+        print "</a></td></tr></table>\n";
+      }
+
+      print "</td>\n"; 
+      print "<td align=center valign=top nowrap>";
+
+      if( $d1 == $d2 && $m1 == $m2 && $y1 == $y2 ) {
+        print POSIX::strftime( "%H:%M", localtime($self->rtime()) ), "  ";
+      } else {
+        print POSIX::strftime( "%D", localtime($self->rtime()) ), "  ";
+      }
+
+      print "</td>\n";
+      print "<td align=left valign=top>", $self->summary(), "</td></tr>\n";
+   } elsif( $format eq "standard" ) {
+      print "<table width=100% border=1 cellspacing=2 cellpadding=2><tr>\n";
+      print "<td width=60 align=center><b>Service</b></td>\n";
+      print "<td width=1%>&nbsp</td>\n";
+      print "<td width=60 align=center><b>Updated</b></td>\n";
+      print "<td width=100% align=center><b>Summary</b></td></tr>\n";
+
+      $self->display_html( "standard_table" );
+
+      print "</table>\n";
+   } elsif( $format eq "full" ) {
+      print "<font size=+2><b>", $self->host(), "/", $self->name();
+      print "</b></font>\n";
+
+      # This breaks the generic interactive vs non-interactive model
+      $self->add_action_bar();
+
+      print "<table width=100% cellspacing=0 cellpadding=0 border=0>";
+      print "<tr><td bgcolor=\"" . $main::WWW_COLOR{$color} . "\">&nbsp;</td>";
+      print "</tr></table><p>";
+
+      print "<b>Updated:</b> ";
+      print POSIX::strftime( "%H:%M, %D", localtime($self->rtime()) );
+
+      print "<br><b>Duration:</b> ";
+      print &format_duration($self->stime(),$self->rtime());
+
+      print "<br><b>Summary:</b> ", $self->summary(), "<br>\n";
+      print "<hr noshade><pre>", $self->message(), "</pre>\n";
+   }
+}
+
+
+# Returns a message that can be sent to the person who is on call for this
+# machine which indicates the problem with this machine.
+
+sub _problem_message {
+   my $self = shift;
+
+   if( $self->has_problem() ) {
+      return "Problem: " . $self->name() . ", " . $self->summary();
+   } else {
+      return "No Problem";
+   }
+}
+
+
+sub add_action_bar {
+   my( $self ) = shift;
+   my $name = $self->host();
+   my $message = &escape( "Host: $name, " . $self->_problem_message() );
+
+   print "<hr>";
+   print "<a href=\"telnet://$name\">Connect to Host</a> || ";
+   print "<a href=\"$main::WWWACK/$host/$service\">Acknowledge Problem</a> ";
+
+   if( $main::WWWCONTACT ) {
+      print "|| <a href=\"$main::WWWCONTACT?host=$name&message=$message\">";
+      print "Contact Help</a>"; 
+   }
+   
+   print "<hr>\n";
+}
+
+sub escape {
+    my($toencode) = @_;
+    $toencode=~s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg;
+    return $toencode;
+}
+
+sub format_duration {
+    my($stime,$rtime) = @_;
+
+    my $duration = $rtime - $stime;
+    my $units = "seconds";
+    if ($duration > 86400.0) { $duration /= 86400.0; $units = "days"; }
+    elsif ($duration > 3600.0) { $duration /= 3600.0; $units = "hours"; }
+    elsif ($duration > 60.0)   { $duration /= 60.0; $units = "minutes"; }
+
+    return sprintf("%.2f %s", $duration, $units);
+}
+
+1;
diff --git a/src/lib/Spong/ServiceList.pm b/src/lib/Spong/ServiceList.pm
new file mode 100755 (executable)
index 0000000..c2fe484
--- /dev/null
@@ -0,0 +1,194 @@
+#!/usr/local/bin/perl
+#
+# This class represents a list of services that run on a given host, allowing
+# you to query individual services for status information.  This knows how to
+# load the Service information from the database, and has the smarts to change
+# the service colors based on updates times and Acks.
+#
+#   host          - string FQDN of the machine its associated with
+#   service_hash  - a hash containing the services org by service names
+#
+# + new()         - constructor (sets instance vars to arguments passed in)
+# + gets/sets()   - magical set/get functions (autoloaded based on func name)
+# + display()     - output format and view
+#
+# + service()     - returns the specific Service object based on name
+# + services()    - returns a list of all the Service objects
+# + names()       - returns a list of all the Service object names.
+# + color()       - returns the LCD color of all the services in the list
+#
+# History:
+# (1) Cleaned up (Ed July 31, 1997);
+
+use Spong::Service;
+use Spong::AckList;
+use Spong::Ack;
+
+package Spong::ServiceList;
+
+# Constructor.  This requires the FQDN of the host that the Services are
+# associated with.  It goes through and loads the information from the database
+# and also loads any Acks that might be associated with the host.  It then 
+# computes the colors for each host and builds Service objects for each service
+# running on that host.  If there are no services running on the host, then
+# undef is returned.
+
+sub new {
+   my( $class, $host ) = @_;
+   my( %service_hash, @acks, $service, $ack, $service_name, $got_one );
+   my $self = {};
+
+   return undef if ! -d "$main::SPONGDB/$host/services";
+
+   # Go through the database, and grab the name and state of each service 
+   # running on this host.
+   
+   opendir( DIR, "$main::SPONGDB/$host/services" );
+   while( defined( $_ = readdir( DIR ) ) ) {
+      next unless /^(.*)-(green|yellow|red)$/ ;
+      my( $name, $color ) = ($1, $2);
+
+      # A hack to get around a local problem.
+      if( $name eq "procs" ) { $name = "jobs"; }
+      
+      $got_one = 1;
+      my( $rtime ) = (stat( "$main::SPONGDB/$host/services/$_" ))[9];
+      my $service  = Spong::Service->new( $host, $name );
+      
+      $service->rtime( $rtime );
+      if( $rtime < ( time() - (3 * $main::SPONGSLEEP) ) ) { 
+        $service->rcolor( $color ); $service->color( "purple" );
+      } else {
+        $service->rcolor( $color ); $service->color( $color );
+      }
+      $service_hash{$name} = $service;
+   }
+   closedir( DIR );
+
+   return undef unless $got_one;
+
+   # Go through the acknowledgments that we have for this host, and change the
+   # state information on our Services if needed.
+
+   $acklist = Spong::AckList->new( $host );
+   if( $acklist ) {
+      foreach $ack ( $acklist->acks() ) {
+        my $ack_services = $ack->services();
+
+        if( time() >= $ack->start() && time() <= $ack->end() ) {
+           foreach $service_name ( keys %service_hash ) {
+              if( $ack_services eq "all" || 
+                  $ack_services =~ /\b$service_name\b/ ) {
+                 
+                 $service_hash{$service_name}->color( "blue" );
+              }
+           }
+        }
+      }
+   }
+
+   $self->{'host'}          = $host;
+   $self->{'service_hash'}  = \%service_hash;
+
+   bless $self;
+   return $self;
+}
+
+# Get/Set methods, nothing fancy here...
+
+sub host { my $var = 'host';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+sub service_hash { my $var = 'service_hash';
+   if( defined $_[1] ) { $_[0]->{$var} = $_[1]; } return $_[0]->{$var}; }
+
+
+# Some utility functions that return information based on instance vars, and 
+# the state of services that this object holds.
+
+sub service  { return $_[0]->{'service_hash'}->{$_[1]}; }
+sub services { return values %{$_[0]->{'service_hash'}}; }
+sub names    { return keys %{$_[0]->{'service_hash'}}; }
+
+sub color {
+   my $self = shift;
+   my( %state_hash ) = qw( blue 3 red 4 yellow 2 purple 1 green 0 );
+   my $state = "green";
+
+   foreach $service ( $self->services() ) {
+      if( $state_hash{$service->color()} > $state_hash{$state} ) {
+        $state = $service->color(); } }
+
+   return $state;
+}
+
+
+# Display summary.  Does both text and html, it relies on the Service objects
+# to print out information about themselves, but this adds a little to by
+# printing header information in cases.
+#
+# brief:      Just shows name of services and what color they are
+# standard:   Shows a table of services (name,color,updated,summary)
+# full:       Shows standard information about service along with messages
+#
+# ** full is not implemented yet.
+
+sub display {
+   my( $self, $type, $view ) = @_;
+
+   if( $type eq "text" ) { return $self->display_text( $view ); }
+   if( $type eq "html" ) { return $self->display_html( $view ); }
+}
+
+sub display_text {
+   my( $self, $format ) = @_;
+   my( $service );
+
+   if( $format eq "brief" ) {
+      foreach $service ( $self->services() ) {
+        print $service->name, " "x(8-length( $service->name() ));
+        $service->display_text( "brief" );
+      }
+   } elsif( $format eq "standard" ) {
+      print "Name    Color    Updated    Summary\n";
+      print "------- -------- ---------- ", "-"x50, "\n";
+      foreach( $self->services() ) { $_->display_text( "standard_table" ); }
+   } elsif( $format eq "full" ) {
+      foreach $service ( $self->services() ) {
+        $service->display_text( "full" );
+        print "\n\n";
+      }
+   }
+}
+
+sub display_html {
+   my( $self, $format ) = @_;
+   my( $event, $date );
+   
+   if( $format eq "brief" ) {
+      print "<table border=0 cellspacing=1 cellpadding=1>\n";
+      foreach $service ( $self->services() ) {
+        my $host = $service->host();
+        my $name = $service->name();
+
+        print "<tr><td align=center valign=center>";
+        $service->display_html( "brief" );
+        print "</td><td align=left valign=center>";
+        print "&nbsp<a href=\"!!WWWSPONG!!/service/$host/$name\">$name</a>";
+        print "</td></tr>\n";
+      }
+      print "</table>\n";
+
+   } elsif( $format eq "standard" ) {
+      print "<table width=100% border=1 cellspacing=1 cellpadding=1><tr>\n";
+      print "<td width=60 align=center nowrap><b>Service</b></td>\n";
+      print "<td width=1% nowrap>&nbsp</td>\n";
+      print "<td width=60 align=center nowrap><b>Updated</b></td>\n";
+      print "<td width=100% align=center><b>Summary</b></td></tr>\n";
+
+      foreach( $self->services() ) { $_->display_html( "standard_table" ); }
+
+      print "</table>\n";
+   }
+}
+
+1;