#
# Copyright 2001-2003 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#ident   "@(#)Cgi.pm 1.50     03/07/08 SMI"
#
# CGI.pm wrapper with extra functions for clustering
#

package Cluster::Cgi;
use strict;
use CGI;
use POSIX;
use Socket;
use Net::SSLeay qw(get_https make_headers);
use Sun::Solaris::Utils qw(textdomain bindtextdomain gettext);
use vars qw(@ISA $VERSION);
$VERSION = '1.00';
@ISA = qw(CGI);
use constant TRUE  => 0;
use constant FALSE => 1;

# Set the locale info for this file
my $lang    = "en";
my $locale  = "C";	# or e.g. en_US
my $country = "US";
my $help_lang    = "en";
my $help_locale  = "C";	# or e.g. en_US
my $help_country = "US";

# Clear the locale environment variables, so commands will get run as
# expected
$ENV{"LANG"} = "C";
$ENV{"LC_ALL"} = "C";
$ENV{"LC_MESSAGES"} = "C";

#
# Class constructor
#

sub new {
	my $proto = shift;
	my $class = ref($proto) || $proto;
	my $self  = new CGI;
	bless ($self, $class);

	my $clusterlocaledir = "/usr/cluster/lib/locale";

	# Set the messages (gettext) locale
	($locale, $lang, $country) = &locale($clusterlocaledir, 1);

	# Determine the locale for the online help files
	($help_locale, $help_lang, $help_country) =
	    &locale("/opt/SUNWscvw/htdocs", 0);

	$self->clean_params();
	return $self;
}

#
# Add extra tags to the call to the parent CGI::start_html()
#

sub start_html {
	my ($self, $args) = @_;
	$args->{"DTD"} = 1;
	return $self->SUPER::start_html($args);
}

# Generate page header
# Title must be internationalized, key and script are optional.
sub content_header {
	my ($self, $title, $key, $script) = @_;
	my $this_title; # Title of this page
	if (ref($title)) {
	    $this_title = $title->[$#{@{$title}}]; # Last component of crumbs
	} else {
	    $this_title = $title;
	}
	$title = $self->join_links($title);

	# Print the html header
	print $self->header;

	# Add the yoking to the key
	if (defined($key)) {
	    if (! defined($script)) {
		$script = "";
	    }
	    $script .= $self->yoke_jscript($key);
	}

	# Start the html
	print $self->start_html({ TITLE => $this_title,
			       CLASS => "default-body",
			       STYLE => { SRC => '/css/clustmgr-style.css' },
			       SCRIPT => $script
		       });

	# Main table begin    
	print $self->start_table({ WIDTH       => "100%",
				BORDER      => 0,
				CELLSPACING => 0,
				CELLPADDING => 0
				});

	print $self->start_Tr();
	print $self->start_td({ CLASS => "breadcrumb-row" });
	$self->start_table_text("breadcrumb-text");
	print $title;
	$self->end_table_text();
	print $self->end_td();
	print $self->end_Tr();
	print $self->end_table();
}

#
# Remove "bad" characters from query strings
#

sub clean_params {
	my ($self) = @_;

	foreach my $param (@{$self->{'.parameters'}}) {
		for (my $i = 0; $i <= $#{$self->{$param}}; $i++) {
			$self->{$param}[$i] =~ tr/ ()+,-.\/0-9:=@A-Z^_a-z{|}~//cd;
		}
	}
}

sub alert {
	my ($self, $alert) = @_;
	return $self->script(), " alert(\"$alert\"); ", $self->end_script();
}

# Redefinition of redirect, adding the character set
sub redirect {
	my ($self, $location) = @_;

	# Get the character set, else use the default
	;# /* TRANSLATION_NOTE The localized value of "charset" is sent to the browser in the Content-Type field to control the charset used to display the page.  This value must be defined appropriately in the message catalog.  For example, in the ja locale the localized value of "charset" should be "EUC-JP", and in the zh locale it should be "gb2312".  The default value is "iso-8859-1". */
	my $charset = gettext("charset");
	if ($charset eq "charset") {
		$charset = "iso-8859-1";
	}

	return CGI::redirect({ URL     => $location,
			       CHARSET => $charset
			       });
}

#
# Print "&nbsp;" the specified number of times 
#

sub indent {
	my ($self, $level) = @_;
	my $indent_string;

	for (my $i=0; $i<$level; $i++) {
		$indent_string .= "&nbsp;"
		}

    return $indent_string;
}

#
# Print "&nbsp;" the specified number of times 
#

sub nbsp {
    my ($self, $num) = @_;
    my $space_string;
    
    for (my $i=0; $i<$num; $i++) {
	$space_string .= "&nbsp;"
    }

    return $space_string;
}

#
# Pass along the installation arguments
#

sub get_cluster_query_string {
    my ($self) = @_;

    # Start with the '?'
    my $params = "?";

    # Cluster name
    if (defined $self->param('cluster_name')) {
	$params .= "cluster_name=".$self->param('cluster_name');
    }

    # Cluster size
    if (defined $self->param('cluster_size')) {
	$params .= "&cluster_size=".$self->param('cluster_size');
    }

    # Node names and transports
    if (defined $self->param('cluster_size')) {
	for (my $i=1; $i<=$self->param('cluster_size'); $i++) {
	    if (defined $self->param("node_$i.name")) {
		$params .= "&node_$i.name=".$self->param("node_$i.name");
	    }

	    if (defined $self->param("node_$i.transport_1")) {
		$params .= "&node_$i.transport_1=";
		$params .= $self->param("node_$i.transport_1");
	    }

	    if (defined $self->param("node_$i.transport_2")) {
		$params .= "&node_$i.transport_2=";
		$params .= $self->param("node_$i.transport_2");
	    }
	}
    }

    # Sun Cluster CDROM path
    if (defined $self->param('cdrom_path')) {
	$params .= "&cdrom_path=".$self->param('cdrom_path');
    }

    # Sun Cluster DataServices CDROM path
    if (defined $self->param('ds_cdrom_path')) {
	$params .= "&ds_cdrom_path=".$self->param('ds_cdrom_path');
    }

    # Solaris CDROM path
    if (defined $self->param('solaris_cdrom_path')) {
	$params .= "&solaris_cdrom_path=".$self->param('solaris_cdrom_path');
    }
    
    # SDS Installation
    if (defined $self->param('install_sds')) {
	$params .= "&install_sds=".$self->param('install_sds');
    }

    # SDS Metadb Path
    if (defined $self->param('sds_ctd')) {
	$params .= "&sds_ctd=".$self->param('sds_ctd');
    }

    # Patch Directory Path
    if (defined $self->param('patch_path')) {
	$params .= "&patch_path=".$self->param('patch_path');
    }

    # NFS Installation
    if (defined $self->param('install_nfs')) {
	$params .= "&install_nfs=".$self->param('install_nfs');
    }

    # NFS Failover Address
    if (defined $self->param('nfs_address')) {
	$params .= "&nfs_address=".$self->param('nfs_address');
    }

    # Apache Installation
    if (defined $self->param('install_apache')) {
	$params .= "&install_apache=".$self->param('install_apache');
    }

    # Apache Scalable Address
    if (defined $self->param('apache_address')) {
	$params .= "&apache_address=".$self->param('apache_address');
    }

    # IPMP Test Addresses
    if (defined $self->param('cluster_size')) {
	for (my $i=1; $i<=$self->param('cluster_size'); $i++) {
	    if (defined $self->param("node_$i.testaddr")) {
		$params .= "&node_$i.testaddr=".
			$self->param("node_$i.testaddr");
	    }
	}
    }    

    # Return the string
    return $params;
}

#
# Pass along hidden installation property fields
#

sub print_hidden_cluster_fields {
    my ($self) = @_;

    print "\n";

    # Print hidden info for the cluster name
    if (defined $self->param('cluster_name')) {
	print $self->hidden({ name    => 'cluster_name',
			      default => $self->param('cluster_name')
			      });
	print "\n";
    }	

    # Cluster size
    if (defined $self->param('cluster_size')) {
	print $self->hidden({ name    => 'cluster_size',
			      default => $self->param('cluster_size')
			      });
	print "\n";
    }

    # Print hidden info for the node names and the transports
    if (defined $self->param('cluster_size')) {
	for (my $i=1; $i<=$self->param('cluster_size'); $i++) {
	    if (defined $self->param("node_$i.name")) {
		print $self->hidden({ name    => "node_$i.name",
				      default => $self->param("node_$i.name")	
				      });
		print "\n";
	    }		
	    
	    if (defined $self->param("node_$i.transport_1")) {
		print $self->hidden({ name    => "node_$i.transport_1",
				      default => $self->param("node_$i.transport_1")
				      });
		print "\n";
	    }		
	    
	    if (defined $self->param("node_$i.transport_2")) {
		print $self->hidden({ name    => "node_$i.transport_2",
				      default => $self->param("node_$i.transport_2")
				      });
		print "\n";
	    }
	}
    }
    
    # Sun Cluster CDROM path
    if (defined $self->param('cdrom_path')) {
	print $self->hidden({ name    => 'cdrom_path',
			      default => $self->param('cdrom_path')
			      });
	print "\n";
    }

    # Sun Cluster Data Services CDROM path
    if (defined $self->param('ds_cdrom_path')) {
	print $self->hidden({ name    => 'ds_cdrom_path',
			      default => $self->param('ds_cdrom_path')
			      });
	print "\n";
    }

    # Solaris CDROM Path
    if (defined $self->param('solaris_cdrom_path')) {
	print $self->hidden({ name    => 'solaris_cdrom_path',
			      default => $self->param('solaris_cdrom_path')
			      });
	print "\n";
    }

    # SDS Installation
    if (defined $self->param('install_sds')) {
	print $self->hidden({ name    => 'install_sds',
			      default => $self->param('install_sds')
			      });
	print "\n";
    }

    # SDS metadb slice
    if (defined $self->param('sds_ctd')) {
	print $self->hidden({ name    => 'sds_ctd',
			      default => $self->param('sds_ctd')
			      });
	print "\n";
    }

    # Patch directory path
    if (defined $self->param('patch_path')) {
	print $self->hidden({ name    => 'patch_path',
			      default => $self->param('patch_path')
			      });
	print "\n";
    }

    # NFS Installation
    if (defined $self->param('install_nfs')) {
	print $self->hidden({ name    => 'install_nfs',
			      default => $self->param('install_nfs')
			      });
	print "\n";
    }

    # NFS Failover Address
    if (defined $self->param('nfs_address')) {
	print $self->hidden({ name    => 'nfs_address',
			      default => $self->param('nfs_address')
			      });
	print "\n";
    }

    # Apache Installation
    if (defined $self->param('install_apache')) {
	print $self->hidden({ name    => 'install_apache',
			      default => $self->param('install_apache')
			      });
	print "\n";
    }

    # Apache Failover Address
    if (defined $self->param('apache_address')) {
	print $self->hidden({ name    => 'apache_address',
			      default => $self->param('apache_address')
			      });
	print "\n";
    }

    # IPMP Test Addresses
    if (defined $self->param('cluster_size')) {
	    for (my $i=1; $i<=$self->param('cluster_size'); $i++) {
		    if (defined $self->param("node_$i.testaddr")) {
			    print $self->hidden({ name => "node_$i.testaddr",
				 value => $self->param("node_$i.testaddr")});
			    print "\n";
		    }
	    }
    }
}

#
# Return the values we're using for locale, lang, country, and help
# file locale
#

sub getlocale {
	return ($locale, $lang, $country,
	    $help_locale, $help_lang, $help_country);
}

#
# Figure out the locale from HTTP_ACCEPT_LANGUAGE and the installed languages
#
# Pass in the directory that holds the locale (if installed)
# Pass in a flag to indicate if the locale should be set
# Return locale, lang, country

sub locale {
	my ($localedir, $setlocale) = @_;
	my $LC_MESSAGES = 5; # From /usr/include/iso/locale_iso.h
	my $localename = "SUNW_SC_GUI";
	my $accept = $ENV{"HTTP_ACCEPT_LANGUAGE"};

	# Default
	my ($locale, $lang, $country);
	$locale = "C";
	$lang = "";
	$country = "";

	if (defined $accept) {

		# The browser will send a list of accepted languages
		# in order of preference:
		# "zh-cn,zh-hk;q=0.9,zh-tw;q=0.7,zh;q=0.6,fr;q=0.4,en;q=0.3,en-us;q=0.1".
		# The "q=0.X" weights are floating point values which
		# further represent the desired order. However, since
		# the list is already ordered, we can filter them out
		# before looping to see which language is installed on
		# the node.  For a definition of the format of this
		# http header, see RFC 2616 section 14 at
		# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

		$accept =~ s/;q=\d\.*\d*//g;
		
		foreach my $trylocale (split("[ ,]+", $accept)) {

			# Get the values of lang and country to try
			my ($trylang, $trycountry) = split(/-/, $trylocale);
			if (defined $trycountry) {
				$trycountry =~ tr/a-z/A-Z/;
				$trylocale = $trylang."_".$trycountry;
			} else {
				$trycountry = "";

				# If no country/region was sent from
				# the browser, use just the language
				# as the entire locale.
				$trylocale = $trylang;
			}

			# If the locale's directory exists, use it
			# In order to setlocale to the locale,
			# /usr/lib/locale/xx must also exist.
			if (-e "$localedir/$trylocale" &&
				($setlocale == 0 ||
				    -e "/usr/lib/locale/$trylocale")) {
				$locale = $trylocale;
				$lang = $trylang;
				$country = $trycountry;
				last;
			} elsif ($trylang eq "en") {
				# No locale installed for en, use default
				$locale = "C";
				$lang = $trylang;
				$country = (defined $trycountry) ? $trycountry: "";
				last;
			} elsif ($trylocale eq "zh_HK" ||
				 $trylocale eq "zh_TW") {

				# Use $localedir/zh_TW for the zh_HK & zh_TW locales
				if (-e "$localedir/zh_TW" &&
				    ($setlocale == 0 ||
				     -e "/usr/lib/locale/zh_TW")) {
					$locale = "zh_TW";
					$lang = "zh";
					$country = "TW";
					last;
				}
			} else {

				# If the locale ("language_region")
				# does not exist, fallback to just the
				# language for every locale except
				# zh_TW, zh_HK, en, and en_*, which
				# are handled above.
				if (-e "$localedir/$trylang" &&
				    ($setlocale == 0 ||
				     -e "/usr/lib/locale/$trylang")) {
					$locale = $trylang;
					$lang = $trylang;
					$country = "";
					last;
				}
			}
		}
	}

	if ($setlocale) {
	    # Set the locale and domain
	    bindtextdomain($localename, $localedir);
	    my ($ret) = setlocale($LC_MESSAGES, $locale);

	    if (! (defined $ret)) {
		    print STDERR "Warning: Set of locale to $locale failed\n";
		    $locale = "C";
		    $lang = "";
		    $country = "";
		    setlocale($LC_MESSAGES, $locale);
	    }

	    textdomain($localename);
	}

	return ($locale, $lang, $country);
}

#
# Print an applet tag with lang/locale parameters
#

sub applet {
    my $self = shift;
    return $self->SUPER::applet(@_)."\n".
    $self->Param({ name => 'lang', value => "$lang"})."\n".
    $self->Param({ name => 'country', value => "$country"});
}

# 
# Print a header with charset info
#

sub header {
    my ($self, @headers) = @_;
    my $charset = gettext("charset");
    if ($charset eq "charset") {
	$charset = "iso-8859-1"; # Default charset
    }

    # Disable caching if desired
    # If we have expires=now, move expires to last year and add pragma
    for (my $i = 0 ;$i <=$#headers; $i+= 2) {
	if ($headers[$i] eq "-expires" && $headers[$i+1] eq "now") {
	    $headers[$i+1] = "-1y";
	    push @headers, ("pragma" => "no-cache", "cache-control" => "no-cache");
	}
    }

    # Add the charset info to the header. The expiration header must
    # explicitly be passed in by pages which require it.
    push @headers, (-type    => "text/html; charset=$charset",
		    -charset => "$charset");
    return $self->SUPER::header(@headers);
}

#
# Act like sprintf, but replace %1 by arg 1, %2 by arg 2, etc.
# This is used to fill in variables in internationalized strings.
# 

sub sprintfn {
    my $self = shift;
    my ($str) = $_[0];
    for (my ($i) = $#_; $i >= 1; $i--) {
	$str =~ s/%$i/$_[$i]/g;
    }
    return $str;
}

##############################################################################
#
# Clusterm Manager GUI Look and Feel functions
#
##############################################################################

#
# Start the action bar
#

sub start_action_bar_table($$$$) {
    my ($self, $title, $useform, $tdinfo) = @_;

    if (! defined($tdinfo)) {
	$tdinfo = { ALIGN => "LEFT", NOWRAP => 1};
    }

    $title = $self->join_links($title);

    # Start the form if we want it
    if (!defined $useform || $useform != FALSE) {
	    print $self->startform({ METHOD => "POST",
				     ACTION => "",
				     NAME   => "actionform"
				     });
    }

    print $self->start_table({ CLASS => "action-bar-table",
			       WIDTH => "100%",
			       BORDER => "0",
			       CELLPADDING => "0",
			       CELLSPACING => "0"
			       });
    
    print $self->start_Tr({ CLASS => "breadcrumb-row" });
    print $self->start_td({ COLSPAN => 4 });
    print $self->start_p({ CLASS => "breadcrumb-text"});
    print $title;
    print $self->end_p();
    print $self->end_td();
    print $self->end_Tr();
    print $self->start_Tr({ HEIGHT => 4 });
    print $self->start_td({ COLSPAN => 4 });
    print $self->img({ SRC    => "/images/dot.gif",
		       ALT    => "",
		       HEIGHT => 4 
		       });
    print $self->end_td();
    print $self->end_Tr();
    print $self->start_Tr();

    print $self->start_td({WIDTH => 5});
    print $self->img({ SRC    => "/images/dot.gif",
		       ALT    => "",
		       WIDTH => 5
		       });
    print $self->end_td();

    print $self->start_td($tdinfo);
}

# This routine generates the title string for a page, with "breadcrumb"
# href links.
# This routine takes either a string, or an array reference
# ["component", "url", "component", "url", ...] (last url is optional)
# A string will be unprocessed, while the array will be converted to a
# single string with the urls wrapped with <a href> as appropriate.
sub join_links($$)
{
    my ($self, $args) = @_;
    if (ref($args) eq "") {
	return $args;
    }
    my $line = "";
    for (my $i = 0; $i <= $#{$args}; $i += 2) {
	if (defined($args->[$i+1])) {
	    $line .= $self->a({href => $args->[$i+1],
				class => "breadcrumb-link-text"},
			    $args->[$i]);
	} else {
	    $line .= $args->[$i];
	}
	if ($i+2 <= $#{$args}) {
	    $line .= "&nbsp;<b>&gt;</b>&nbsp;";
	}
    }
    return $line;
}

sub print_title($$)
{
	my ($self, $title) = @_;
	$title = $self->join_links($title);
    	print $self->start_table({ CLASS => "action-bar-table",
			       WIDTH => "100%",
			       BORDER => "0",
			       CELLPADDING => "0",
			       CELLSPACING => "0"
			       });
    
	print $self->start_Tr({ CLASS => "breadcrumb-row" });
	print $self->start_td({ COLSPAN => 2 });
	print $self->start_p({ CLASS => "breadcrumb-text"});
	print $title;
	print $self->end_p();
	print $self->end_td();
	print $self->end_Tr();
	print $self->end_table();
}

#
# Mid action bar
#

sub mid_action_bar_table($) {
    my ($self) = @_;
    
    print $self->end_td();
    print $self->start_td({ ALIGN => "RIGHT",
			    NOWRAP => 1
			    });
}

#
# End the action bar
#

sub end_action_bar_table($) {
    my ($self, $useform) = @_;

    print $self->end_td();
    print $self->start_td({WIDTH => 5});
    print $self->img({ SRC    => "/images/dot.gif",
		       ALT    => "",
		       WIDTH => 5
		       });
    print $self->end_td();

    print $self->end_Tr();
    print $self->start_Tr({ HEIGHT => 4 });
    print $self->start_td({ COLSPAN => 4 });
    print $self->img({ SRC    => "/images/dot.gif",
		       ALT    => "",
		       HEIGHT => 4 
		       });
    print $self->end_td();
    print $self->end_Tr();
    print $self->start_Tr({ HEIGHT => 1 });
    print $self->start_td({ COLSPAN => 4,
			    CLASS => "breadcrumb-line-row"
			    });
    print $self->img({ SRC    => "/images/dot.gif",
		       ALT    => "",
		       HEIGHT => 1 
		       });
    print $self->end_td();
    print $self->end_Tr();
    print $self->end_table();

    # End the form
    if (!defined $useform || $useform != FALSE) {
	    print $self->endform();
    }	
}

#
# Return the jump menu javascript
#

sub jump_menu_jscript() {
  return "
	function jumpActionsMenu(ref) {
	  if (ref.selectedIndex != 0 && ref.selectedIndex != 1) { 
	     top.helpMarker = jsactionURLs[ref.selectedIndex]
	     // confirm(\"jumpActionsMenu() \" + top.helpMarker )
             document.location = ref.options[ref.selectedIndex].value
          }
        }

        function jumpViewMenu(ref) {
           self.location.href = ref.options[ref.selectedIndex].value
        }
   "
} # jump_menu_jscript


#
# Start the view menu
#

sub view_menu($$$) {
    my ($self, $itemref, $default) = @_;

    # Print the title if one was given
    print $self->start_span({ CLASS => "action-bar-menu-label-text" });
    print gettext("View:")."&nbsp;";
    print $self->end_span();

    $self->select_menu($itemref, "viewmenu", $default);

    # Set the selection to the current page
    my $pos = 0;
    for (my $i = 1; $i < scalar(@$itemref); $i += 2, $pos++) {
	if ($ENV{"REQUEST_URI"} eq $itemref->[$i]) {
	    print $self->start_script();
	    print "document.actionform.viewmenu.selectedIndex=$pos\n";
	    print $self->end_script();
	    last;
	}
    }
}

sub select_menu($$$$) {
    my ($self, $itemref, $name, $default) = @_;

    # Extract the values and labels
    my (@menuitems, @values, %labels);
    @menuitems = @$itemref;
    for (my $i=0; $i <= ($#menuitems-1); $i+=2) {
        $labels{$menuitems[$i+1]} = $menuitems[$i];
        push @values, $menuitems[$i+1];
    }

    # Print the selection box
    print $self->popup_menu({ NAME     => "$name",
                              VALUES   => \@values,
                              LABELS   => \%labels,
                              DEFAULT  => $default,
                              ONCHANGE => "jumpViewMenu(this.form.$name)"
                              });
}

#
# Draw the telnet link to the specified node
#

sub telnet_link($$) {
	my ($self, $hostname) = @_;

	# Get the ip address
	my $addr = gethostbyname($hostname);
	$addr = inet_ntoa($addr);

	$self->html_button("telnet://$addr",
			   sprintf(gettext("&nbsp;Telnet to %s&nbsp;"),
				   $hostname));
}

#
# Draw a HTML button
#

sub html_button() {
	my ($self, $href, $text, $onclick) = @_;

	print $self->start_table({ BORDER => 0,
				   CELLPADDING => 1,
				   CELLSPACING => 0,
				   CLASS => "button-frame-enabled"
				   });

	print $self->start_Tr();

	# Print the onclick action if defined
	if (defined $onclick && $onclick ne "") {
		print $self->start_td({ ONCLICK => $onclick,
					STYLE => "cursor:pointer; cursor:hand"
					});
	} else {
		print $self->start_td({ STYLE => "cursor:pointer; cursor:hand"
					});
	}

	print $self->start_table({ BORDER => 0,
				   CELLPADDING => 0,
				   CELLSPACING => 0,
				   WIDTH => "100%",
				   CLASS => "button-content-enabled"
				   });

	print $self->start_Tr();
	print $self->start_td({ ALIGN => "center",
				VALIGN => "middle",
				NOWRAP => 1
				});

	print $self->start_div({ CLASS => "button-link-enabled-text" });

	# Print the onclick action if defined
	if (defined $onclick && $onclick ne "") {
		print $self->start_a({ HREF => $href,
				       ONCLICK => $onclick,
				       CLASS => "button-link"
				       });
	} else {
		print $self->start_a({ HREF => $href,
				       CLASS => "button-link"
				       });
	}

	print $text;
	print $self->end_a();
	print $self->end_div();
	print $self->end_td(), $self->end_Tr(), $self->end_table();
	print $self->end_td(), $self->end_Tr(), $self->end_table();
}
  
#
# Draw the header link divider
#

sub header_link_divider($) {
    my ($self) = @_;
    print $self->start_span({ CLASS => "action-bar-link-divider" });
    print "&nbsp;|&nbsp;";
    print $self->end_span();
}

#
# Draw action bar link text
#

sub action_bar_link_text($$$) {
    my ($self, $title, $link) = @_;
    print $self->start_a({ CLASS => "action-bar-link-text",
			   HREF => $link
			   });
    print $title;
    print $self->end_a();
}

#
# Draw the actions menu
#

sub actions_menu($$$$) {
    my ($self, $title, $itemref, $default) = @_;

    # Extract the values and labels
    my (@menuitems, @values, %labels, $dashes, $maxstrlen);

    # Preload @values & %links with the title links
    push @values, "NULL1", "NULL2";

    # Add the dashes to the title
    $title = "- $title -";

    # Get the array from the reference
    @menuitems = @$itemref;

    # Create the values array and the labels hash
    for (my $i=0; $i <= ($#menuitems-1); $i+=2) {
	$labels{$menuitems[$i+1]} = $menuitems[$i];

	# Add to the list of values
	push @values, $menuitems[$i+1];
    }

    $labels{"NULL1"} = $title;
    $labels{"NULL2"} = gettext("---------------------");

    # Print the selection box
    print $self->popup_menu({ NAME     => "actionsmenu",
			      VALUES   => \@values,
			      LABELS   => \%labels,
			      DEFAULT  => $default,
			      ONCHANGE => "jumpActionsMenu(this.form.actionsmenu)"
			      });
    print $self->start_script();
    print "document.actionform.actionsmenu.selectedIndex=0\n";
    print $self->end_script();
}


sub load_action_helpMarkers($$) {
  my ($self, $hlinx) = @_;
  my (@hlnx);
  @hlnx = @$hlinx;
  print "\n<script> var jsactionURLs = [";
  # fill up first two slots to match action_menu list
  for (my $i=0; $i < 2; $i++) {
    print "\"null\", ";
  }
  for (my $i=0; $i < $#hlnx; $i++) {
    print "\"$hlnx[$i]\", ";
  }
  print "\"$hlnx[$#hlnx]\" ";
  print "] </script>\n";
} # load_action_helpMarkers


##############################################################################
#
# Property table -- generic table which shows properties and values
#
##############################################################################

#
# Start the property table
#

sub start_prop_table($$$$) {
    my ($self, $title, $headref, $img, $cellpadding) = @_;
  
    # Get the table headers
    my @headers = @$headref;

    # Print the optional title
    if (defined $title && $title ne "") {

	    # Print the title table
	    print $self->start_table({ WIDTH       => "98%",
				       BORDER      => 0,
				       CELLSPACING => 0,
				       CELLPADDING => 0,
				       ALIGN       => "CENTER",
				       CLASS       => "title-table"
				       });

	    print $self->start_Tr();
	    print $self->start_td();
	    print $self->start_div({ CLASS => "table-title-text" });

	    if (defined $img && $img ne "") {
		    print $self->img({ SRC    => "$img",
				       WIDTH  => 16,
				       HEIGHT => 16,
				       BORDER => 0,
				       HSPACE => 3,
				       ALT => "",
				       });
	    }	

	    print $title;
	    print $self->end_div();
	    print $self->end_td();
	    print $self->end_Tr();
	    print $self->end_table();
    }

    # Print the outer (back) table   
    print $self->start_table({ WIDTH       => "98%",
			       BORDER      => 0,
			       CELLSPACING => 0,
			       CELLPADDING => 1,
			       ALIGN       => "CENTER",
			       CLASS       => "back-table"
			       });

    print $self->start_Tr();
    print $self->start_td();

    # Print the inner (front) table
    print $self->start_table({ WIDTH       => "100%",
			       BORDER      => 0,
			       CELLSPACING => 0,
			       CELLPADDING => 0,
			       ALIGN       => "CENTER",
			       CLASS       => "front-table"
			       });

    print $self->start_Tr({ CLASS => "header-row" });

    # For each head title, print a column with the specified width
    for (my $i=0; $i <= ($#headers-1); $i+=2) {
	print $self->start_td({ WIDTH => $headers[$i + 1] });
	print $self->start_div({ CLASS => "table-header-text" });
	print $headers[$i];
	print $self->end_div();
	print $self->end_td();
    }

    print $self->end_Tr();
}

#
# Start a property table row
#

sub start_prop_tr($$) {
    my ($self, $args) = @_;
    print $self->start_Tr($args);
}


#
# End a property table row
#

sub end_prop_tr($) {
	my ($self) = @_;
	print $self->end_Tr();
}

#
# Start a property table column
#

sub start_prop_td($$) {
	my ($self, $args) = @_;
	print $self->start_td($args);
}

#
# End a property table column
#

sub end_prop_td($) {
	my ($self) = @_;
	print $self->end_td();
}

#
# Print the property table divider line row
#

sub line_row($$) {
	my ($self, $numcols) = @_;
	print $self->start_Tr({ HEIGHT => 1 });
	print $self->start_td({ HEIGHT => 1,
				COLSPAN => $numcols,
				CLASS => "line-row"
				});
	print $self->img({ SRC    => "/images/dot.gif",
			   WIDTH  => 1,
			   HEIGHT => 1,
			   BORDER => 0,
			   ALT    => ""
			   });
	print $self->end_td();
	print $self->end_Tr();	       
}

#
# Print table text in the specified style
#

sub start_table_text($$) {
	my ($self, $style) = @_;
	print $self->start_div({ CLASS => $style });
}

#
# End the table text style for the table cell
#

sub end_table_text($) {
	my ($self) = @_;
	print $self->end_div();
}

#
# Look up a status string in the conversion table.  Display the appropriate
# icon and the appropriate internationalized text
# in the appropriate style.  The icon is put in a separate table cell.
# If conversion is undefined, the text is just printed.
# If conversion is a string, it forces the type
#

sub table_status_text($$) {
	my ($self, $text, $conversion, $strong) = @_;

	my ($type, $status, $style, $lctext, $image);
	$lctext = lc($text);
	$type = "";

	if (defined($conversion) && ref($conversion)) {
	    # Look up the text string in the conversion table
	    for (my $i = 0; $i < scalar(@$conversion); $i++) {
		if ($lctext eq lc($conversion->[$i][0])) {
		    # Strings must be defined for gettext elsewhere
		    $status = gettext($conversion->[$i][1]);
		    $type = $conversion->[$i][2];
		    last;
		}
	    }
	} elsif (defined($conversion)) {
	    # Use specified type
	    $status = $text;
	    $type = $conversion;
	} else {
	    # Don't use any icons
	    $status = $text;
	    $type = "none";
	}

	# Get the associated style and icon
	if ($type eq "ok") {
	    $image = "/images/dot.gif";
	    $style = "ok-status-text";
	} elsif ($type eq "minor") {
	    $image = "/images/minor_14.gif";
	    $style = "minor-status-text";
	} elsif ($type eq "major") {
	    $image = "/images/major_14.gif";
	    $style = "major-status-text";
	} elsif ($type eq "critical") {
	    $image = "/images/critical_14.gif";
	    $style = "critical-status-text";
	} elsif ($type eq "none") {
	    $image = "/images/dot.gif";
	    $style = "table-normal-text";
	} else {
	    # Default to ok
	    $image = "/images/dot.gif";
	    $style = "ok-status-text";
	    $status = gettext($text);
	}

	if (defined $strong) {
	    $style =~ s/-text/-strong-text/;
	}

	print $self->img({ SRC    => $image,
			   ALT    => "",
			   WIDTH  => 14,
			   HEIGHT => 14,
			   BORDER => 0,
			   HSPACE => 3
			   });
	print $self->end_td(), $self->start_td();
	$self->start_table_text($style);
	if ($status =~ /^\s*$/) {
	    $status = "&nbsp;"; # To prevent table from collapsing
	}
	print $status;
	$self->end_table_text();
}

#
# Take some text, a link, and a graphic, and display them as
# table-link-text. This is meant to go in the left-hand column of the
# property tables.
#

sub table_link_text($$$$) {
	my ($self, $text, $alt, $link, $image) = @_;

	print $self->start_a({ HREF => $link,
			       CLASS => "table-link" 
			       });
	$self->start_table_text("table-link-text");
	if (defined($image)) {
		print $self->img({ SRC    => $image,
				   ALT    => $alt,
				   WIDTH  => 16,
				   HEIGHT => 16,
				   BORDER => 0,
				   HSPACE => 3
				   });
	}
	print $text;
	$self->end_table_text();
	$self->end_a();
}

#
# End the property table
#

sub end_prop_table($) {
	my ($self) = @_;

	# End the front table
	print $self->end_table();

	# End the back table
	print $self->end_td();
	print $self->end_Tr();
	print $self->end_table();
	print $self->start_p();
	print $self->end_p();
}

sub start_message_table($$) {
	my ($self, $type) = @_;

	# Print the title table
	print $self->start_table({ WIDTH       => "97%",
			       BORDER      => 0,
			       CELLSPACING => 0,
			       CELLPADDING => 0,
			       ALIGN       => "center"
			       });

	print $self->start_Tr();
	print $self->start_td({ align => "center",
				valign => "top",
				width => 32,
				height => 32
				});
	my $image = sprintf("/images/%s_32.gif", $type);
	print $self->img({ SRC    => $image,
			   WIDTH  => 32,
			   HEIGHT => 32,
			   BORDER => 0,
			   ALT    => gettext($type),
			   });
	print $self->end_td();
	print $self->start_td({ width => "95%" });
	# Internationalization for alt tags
	;# gettext("info");
	;# gettext("warning");
	;# gettext("error");
}

sub end_message_table($)
{
	my ($self) = @_;

	print $self->end_td();
	print $self->end_Tr();
	print $self->end_table();
}

sub buttons_table($$$$$) {
	my ($self, $value1, $action1, $value2, $action2) = @_;

	print $self->start_table({ class => "action-window",
				width => "97%",
				border => 0,
				cellspacing => 2,
				cellpadding => 0,
				align => "center"
				});

	print $self->start_Tr({ height => "15" });
	print $self->td({ colspan => 2 });
	print $self->img({ SRC    => "/images/dot.gif",
			alt => "",
			height => 15 });
	print $self->end_td();
	print $self->end_Tr();

	print $self->start_Tr({ height => "1" });
	print $self->td({ colspan => 2,
			class => "breadcrumb-row"});
	print $self->img({ SRC    => "/images/dot.gif",
			alt => "",
			height => 1 });
	print $self->end_td();
	print $self->end_Tr();

	print $self->start_Tr({ height => "6" });
	print $self->td({ colspan => 2,
			height => "5"});
	print $self->img({ SRC    => "/images/dot.gif",
			alt => "",
			height => 6 });
	print $self->end_td();
	print $self->end_Tr();
	
	print $self->start_Tr();
	print $self->td({ align => "right",
			width => "80%"});
	print $self->button({ value => $value1,
			onClick => $action1
			});
	print $self->end_td();
	
	print $self->td({ align => "right",
			width => "20%"});
	if (defined $value2) {
		print $self->button({ value => $value2,
				onClick => $action2
				});
	} else {
		print $self->indent(1);
	}
	print $self->end_td();
	
	print $self->end_Tr();
	print $self->end_table();
}

##############################################################################
#
# Action table -- fuctions for action wizards
#
##############################################################################

#
# Start an action table
#

sub start_action_table($) {
	my ($self) = @_;	

	# Print the title table
	print $self->start_table({ BORDER      => 0,
				   CELLSPACING => 0,
				   CELLPADDING => 5,
				   CLASS       => "action-table"
				   });
}

#
# Start an action table row
#

sub start_action_tr($) {
    my ($self, $args) = @_;
    print $self->start_Tr($args);
}


#
# End an action table row
#

sub end_action_tr($) {
	my ($self) = @_;
	print $self->end_Tr();
}

#
# Start an action table column
#

sub start_action_td($$) {
	my ($self, $args) = @_;
	print $self->start_td($args);
}

#
# End an action table column
#

sub end_action_td($) {
	my ($self) = @_;
	print $self->end_td();
}

#
# Start action label text
#

sub action_label_text($$) {
	my ($self, $text) = @_;	
	$self->start_table_text("action-window-label-text");
	print $text;
	$self->end_table_text();
}

#
# Start action normal text
#

sub action_normal_text($$) {
	my ($self, $text) = @_;	
	$self->start_table_text("action-window-normal-text");
	print $text;
	$self->end_table_text();
}
#
# End an action table
#

sub end_action_table($) {
	my ($self) = @_;	
	print $self->end_table();
}

##############################################################################
#
# Action button table -- contains the action/cancel buttons
#
##############################################################################

#
# Start an action table
#

sub start_action_button_table($) {
	my ($self) = @_;	

	# Print the title table
	print $self->start_table({ BORDER      => 0,
				   CELLSPACING => 0,
				   CELLPADDING => 0,
				   WIDTH       => "97%",
				   ALIGN       => "CENTER",
				   CLASS       => "action-table"
				   });
}

#
# Cause the menu to be reloaded
#

sub reload_menu ($) {
	my ($self) = @_;
	print $self->start_script();
	print "top.menu.location.reload(true)";
	print $self->end_script();
}

#
# Return the script to yoke the page to the tree, tied by the key
#
sub yoke_jscript($$) {
    my ($self, $key) = @_;
    return "\nyokeKey = \"$key\";\n" .
	"if (top && top.menu && top.menu.yokeTo) {\n" .
	"    top.menu.yokeTo(yokeKey);\n" .
	"}\n";
}

# Return a script for an up/down ordering box.
sub orderbox_jscript() {
  return <<END_OF_SCRIPT
// based on http://javascript.internet.com/forms/menu-swapper.html

function horizontal_shift(fbox,tbox) {
  for(var i=0; i<fbox.options.length; i++) {
      if(fbox.options[i].selected
           && fbox.options[i].value != ""
           && fbox.options[i].value != "ignoreMe") {

	// ignoreMe is last entry: save it
	var tboxlen = tbox.options.length;
	var iotext = tbox.options[tboxlen-1].text;
	var iovalue = tbox.options[tboxlen-1].value;

	// write new entry on top of old ignoreMe
        tbox.options[tboxlen-1].text = fbox.options[i].text;
        tbox.options[tboxlen-1].value = fbox.options[i].value;

	// insert new last option to hold ignoreMe
        var no = new Option();
        tbox.options[tboxlen] = no;
        tbox.options[tboxlen].text = iotext;
        tbox.options[tboxlen].value = iovalue;

        fbox.options[i].value = "";
        fbox.options[i].text = "";
      }
  }
  BumpUp(fbox);
  // let selection drop down except onto spacer
  var fboxlen = fbox.options.length;
  if(fbox.options[fboxlen - 1].value == "ignoreMe") {
    fbox.options[fboxlen - 1].selected = false;
  }
} // horizontal_shift

function BumpUp(box)  {
  for(var i=0; i<box.options.length; i++) {
    if(box.options[i].value == "")  {
      for(var j=i; j<box.options.length-1; j++)  {
        box.options[j].value = box.options[j+1].value;
        box.options[j].text = box.options[j+1].text;
      }
      var ln = i;
      break;
    }
  }
  if(ln < box.options.length)  {
    box.options.length -= 1;
    BumpUp(box);
   }
} // BumpUp

function vertical_shift(box,down) {
  for(var i=0; i<box.options.length; i++) {
      if(box.options[i].selected
           && box.options[i].value != ""
           && box.options[i].value != "ignoreMe") {
	if(down == "1") {
	    swap(box,i,i+1);
	    if(box.options[i+1].value != "ignoreMe") {
	        box.options[i].selected = false;
		box.options[i+1].selected = true;
	    }
	} else {
	    if(i>0) { // IE Solaris fails the >=0 bounds checking in swap()
		swap(box,i,i-1);
  	        box.options[i].selected = false;
		box.options[i-1].selected = true;
	    }
	}
	break;
      }
  }
} // vertical_shift

function swap(box,pos1,pos2) {
    if(box.options[pos1].value != "ignoreMe"
        && box.options[pos2].value != "ignoreMe"
	&& pos1 <= box.options.length
	&& pos2 <= box.options.length
	&& pos1 >= 0
	&& pos2 >= 0 ) {
	var savetext =  box.options[pos1].text;
	var savevalue = box.options[pos1].value;

	box.options[pos1].text = box.options[pos2].text;
	box.options[pos1].value = box.options[pos2].value;

	box.options[pos2].text = savetext;
	box.options[pos2].value = savevalue;
    }
} // swap

function bundleListAsInput(box,outval) {
  outval.value = "";
  for(var i=0; i<box.options.length; i++) {
      if(box.options[i].value != ""
          && box.options[i].value != "ignoreMe") {
        if(outval.value == "") {
          outval.value = box.options[i].value;
        } else {
          outval.value = outval.value + "," + box.options[i].value;
        }
      }
  }  
} //bundleListAsInput
END_OF_SCRIPT
}

#
# Return the script to do event refreshing.
# Arguments: frame: name of the frame to refresh
# pattern: list of event prefixes to match against
# link: optional link to load on event
sub refresh_jscript($$) {
    my ($self, $frame, $pattern, $link) = @_;
    my ($script);
    if (defined($link)) {
	$link = "'$link'";
    } else {
	$link = "location";
    }
    $script = "if (parent && parent.banner && ".
	"parent.banner.document && ".
	"parent.banner.document.statusApplet) {".
	"parent.banner.document.statusApplet.register(".
	"'$frame','$pattern',$link);}";
    return $script;
}

# Return the script to unregister event refreshing.
sub unregister_jscript($$) {
    my ($self) = @_;
    my ($script);
    return $self->refresh_jscript("content", "", "");
}

# Alert & error messages used by role.pl & login.pl
sub login_alert {
    my ($self, $header, $text) = @_;

    my $string;
    $string .= $self->start_Tr();
    $string .= $self->start_td({ COLSPAN => 2 });
    $string .= $self->img({ SRC    => "/images/dot.gif",
			 ALT    => " ",
			 HEIGHT => 20,
			 BORDER => 0
			 });
    $string .= $self->end_td();
    $string .= $self->start_td({ COLSPAN => 2 });
    $string .= $self->start_table({ CLASS  => "alert-error-frame",
				 BORDER => 0,
				 CELLSPACING => 0,
				 CELLPADDING => 2
				 });
    $string .= $self->start_Tr();
    $string .= $self->start_td({ COLSPAN => 2 });
    $string .= $self->start_table({ CLASS  => "alert-error-content",
				 BORDER => 0,
				 CELLSPACING => 0,
				 CELLPADDING => 5
				 });
    $string .= $self->start_Tr();
    $string .= $self->start_td({ VALIGN => "top" });
    $string .= $self->img({ SRC    => "/images/error_32.gif",
			 ALT    => "Error",
			 HEIGHT => 32,
			 WIDTH  => 32
			 });
    $string .= $self->end_td();
    $string .= $self->start_td();
    $string .= $self->start_div({ CLASS => "alert-header-text" });
    $string .= $header;
    $string .= $self->end_div();
    $string .= $self->start_div({ CLASS => "alert-normal-text" });
    $string .= $text;
    $string .= $self->end_div();
    $string .= $self->end_td();
    $string .= $self->end_Tr();
    $string .= $self->end_table();
    $string .= $self->end_td();
    $string .= $self->end_Tr();
    $string .= $self->end_table();
    $string .= $self->end_td();
    $string .= $self->end_Tr();

    return $string;
}

# Informational messages used by role.pl & login.pl
sub login_info {
    my ($self, $header, $text) = @_;

    my $string;
    $string .= $self->start_Tr();
    $string .= $self->start_td({ COLSPAN => 2 });
    $string .= $self->img({ SRC    => "/images/dot.gif",
			 ALT    => " ",
			 HEIGHT => 20,
			 BORDER => 0
			 });
    $string .= $self->end_td();
    $string .= $self->start_td({ COLSPAN => 2 });
    $string .= $self->start_table({ CLASS  => "alert-info-frame",
				 BORDER => 0,
				 CELLSPACING => 0,
				 CELLPADDING => 2
				 });
    $string .= $self->start_Tr();
    $string .= $self->start_td({ COLSPAN => 2 });
    $string .= $self->start_table({ CLASS  => "alert-info-content",
				 BORDER => 0,
				 CELLSPACING => 0,
				 CELLPADDING => 5
				 });
    $string .= $self->start_Tr();
    $string .= $self->start_td({ VALIGN => "top" });
    $string .= $self->img({ SRC    => "/images/info_32.gif",
			 ALT    => "Information",
			 HEIGHT => 32,
			 WIDTH  => 32
			 });
    $string .= $self->end_td();
    $string .= $self->start_td();
    $string .= $self->start_div({ CLASS => "alert-header-text" });
    $string .= $header;
    $string .= $self->end_div();
    $string .= $self->start_div({ CLASS => "alert-normal-text" });
    $string .= $text;
    $string .= $self->end_div();
    $string .= $self->end_td();
    $string .= $self->end_Tr();
    $string .= $self->end_table();
    $string .= $self->end_td();
    $string .= $self->end_Tr();
    $string .= $self->end_table();
    $string .= $self->end_td();
    $string .= $self->end_Tr();

    return $string;
}
