Fix for bug 84338: initial implementation of attachment tracker, which lets users flag attachments with statuses.

Patch by Myk Melez <myk@mozilla.org>
r=justdave@syndicomm.com
This commit is contained in:
myk%mozilla.org 2001-08-31 03:54:37 +00:00
Родитель 5434e861aa
Коммит 0228e535c8
15 изменённых файлов: 646 добавлений и 14 удалений

Просмотреть файл

@ -1194,7 +1194,8 @@ sub DumpBugActivity {
$datepart = "and bugs_activity.bug_when >= $starttime";
}
my $query = "
SELECT IFNULL(fielddefs.name, bugs_activity.fieldid),
SELECT IFNULL(fielddefs.description, bugs_activity.fieldid),
bugs_activity.attach_id,
bugs_activity.bug_when,
bugs_activity.removed, bugs_activity.added,
profiles.login_name
@ -1219,7 +1220,9 @@ sub DumpBugActivity {
my @row;
my $incomplete_data = 0;
while (@row = FetchSQLData()) {
my ($field,$when,$removed,$added,$who) = (@row);
my ($field,$attachid,$when,$removed,$added,$who) = (@row);
$field =~ s/^Attachment/<a href="attachment.cgi?id=$attachid&action=view">Attachment #$attachid<\/a>/
if (Param('useattachmenttracker') && $attachid);
$removed = html_quote($removed);
$added = html_quote($added);
$removed = "&nbsp;" if $removed eq "";
@ -1289,6 +1292,8 @@ Actions:
}
if (UserInGroup("editcomponents")) {
$html .= ", <a href=editproducts.cgi>components</a>";
$html .= ", <a href=editattachstatuses.cgi><NOBR>attachment statuses</NOBR></a>"
if Param('useattachmenttracker');
}
if (UserInGroup("creategroups")) {
$html .= ", <a href=editgroups.cgi>groups</a>";

Просмотреть файл

@ -283,18 +283,26 @@ if (@::legal_keywords) {
};
}
print "<tr><td align=right><B>Attachments:</b></td>\n";
SendSQL("select attach_id, creation_ts, mimetype, description from attachments where bug_id = $id");
while (MoreSQLData()) {
my ($attachid, $date, $mimetype, $desc) = (FetchSQLData());
if ($date =~ /^(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/) {
$date = "$3/$4/$2 $5:$6";
# 2001-05-16 myk@mozilla.org: use the attachment tracker to display attachments
# if this installation has enabled use of the attachment tracker.
if (Param('useattachmenttracker')) {
print "</table>\n";
use Attachment;
&Attachment::list($id);
} else {
print "<tr><td align=right><B>Attachments:</b></td>\n";
SendSQL("select attach_id, creation_ts, mimetype, description from attachments where bug_id = $id");
while (MoreSQLData()) {
my ($attachid, $date, $mimetype, $desc) = (FetchSQLData());
if ($date =~ /^(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/) {
$date = "$3/$4/$2 $5:$6";
}
my $link = "showattachment.cgi?attach_id=$attachid";
$desc = value_quote($desc);
print qq{<td><a href="$link">$date</a></td><td colspan=6>$desc&nbsp;&nbsp;&nbsp;($mimetype)</td></tr><tr><td></td>};
}
my $link = "showattachment.cgi?attach_id=$attachid";
$desc = value_quote($desc);
print qq{<td><a href="$link">$date</a></td><td colspan=6>$desc&nbsp;&nbsp;&nbsp;($mimetype)</td></tr><tr><td></td>};
print "<td colspan=7><a href=\"createattachment.cgi?id=$id\">Create a new attachment</a> (proposed patch, testcase, etc.)</td></tr></table>\n";
}
print "<td colspan=7><a href=\"createattachment.cgi?id=$id\">Create a new attachment</a> (proposed patch, testcase, etc.)</td></tr></table>\n";
sub EmitDependList {

Просмотреть файл

@ -415,6 +415,22 @@ sub GenerateSQL {
}
$f = "$table.$field";
},
# 2001-05-16 myk@mozilla.org: enable querying against attachment status
# if this installation has enabled use of the attachment manager.
"^attachstatusdefs.name," => sub {
my $attachtable = "attachments_$chartid";
my $statustable = "attachstatuses_$chartid";
my $statusdefstable = "attachstatusdefs_$chartid";
push(@supptables, "attachments $attachtable");
push(@supptables, "attachstatuses $statustable");
push(@supptables, "attachstatusdefs $statusdefstable");
push(@wherepart, "bugs.bug_id = $attachtable.bug_id");
push(@wherepart, "$attachtable.attach_id = $statustable.attach_id");
push(@wherepart, "$statustable.statusid = $statusdefstable.id");
my $table = $statusdefstable;
my $field = "name";
$f = "$table.$field";
},
"^changedin," => sub {
$f = "(to_days(now()) - to_days(bugs.delta_ts))";
},

Просмотреть файл

@ -192,6 +192,7 @@ unless (have_vers("DBI","1.13")) { push @missing,"DBI" }
unless (have_vers("Data::Dumper",0)) { push @missing,"Data::Dumper" }
unless (have_vers("DBD::mysql","1.2209")) { push @missing,"DBD::mysql" }
unless (have_vers("Date::Parse",0)) { push @missing,"Date::Parse" }
unless (have_vers("Template","2.01")) { push @missing,"Template" }
# If CGI::Carp was loaded successfully for version checking, it changes the
# die and warn handlers, we don't want them changed, so we need to stash the
@ -838,6 +839,7 @@ my %table;
$table{bugs_activity} =
'bug_id mediumint not null,
attach_id mediumint null,
who mediumint not null,
bug_when datetime not null,
fieldid mediumint not null,
@ -859,10 +861,33 @@ $table{attachments} =
filename mediumtext not null,
thedata longblob not null,
submitter_id mediumint not null,
isobsolete tinyint not null default 0,
index(bug_id),
index(creation_ts)';
# 2001-05-05 myk@mozilla.org: Tables to support the attachment tracker.
# "attachstatuses" stores one record for each status on each attachment.
# "attachstatusdefs" defines the statuses that can be set on attachments.
# Note: These tables are only used if the parameter "useattachmenttracker"
# is turned on via editparameters.cgi.
$table{attachstatuses} =
'
attach_id MEDIUMINT NOT NULL ,
statusid SMALLINT NOT NULL ,
PRIMARY KEY(attach_id, statusid)
';
$table{attachstatusdefs} =
'
id SMALLINT NOT NULL PRIMARY KEY ,
name VARCHAR(50) NOT NULL ,
description MEDIUMTEXT NULL ,
sortkey SMALLINT NOT NULL DEFAULT 0 ,
product VARCHAR(64) NOT NULL
';
#
# Apostrophe's are not supportied in the enum types.
# See http://bugzilla.mozilla.org/show_bug.cgi?id=27309
@ -1294,6 +1319,8 @@ AddFDef("attachments.description", "Attachment description", 0);
AddFDef("attachments.thedata", "Attachment data", 0);
AddFDef("attachments.mimetype", "Attachment mime type", 0);
AddFDef("attachments.ispatch", "Attachment is patch", 0);
AddFDef("attachments.isobsolete", "Attachment is obsolete", 0);
AddFDef("attachstatusdefs.name", "Attachment Status", 0);
AddFDef("target_milestone", "Target Milestone", 0);
AddFDef("delta_ts", "Last changed date", 0);
AddFDef("(to_days(now()) - to_days(bugs.delta_ts))", "Days since bug changed",
@ -2322,6 +2349,12 @@ unless (-d 'data/duplicates') {
#
AddField('groups', 'isactive', 'tinyint not null default 1');
#
# 2001-06-15 myk@mozilla.org:
# isobsolete determines whether or not an attachment is pertinent/relevant/valid.
#
AddField('attachments', 'isobsolete', 'tinyint not null default 0');
# 2001-04-29 jake@acutex.net - Remove oldemailtech
# http://bugzilla.mozilla.org/show_bugs.cgi?id=71552
if (-d 'shadow') {
@ -2470,6 +2503,11 @@ AddField("bugs", "assignee_accessible", "tinyint not null default 1");
AddField("bugs", "qacontact_accessible", "tinyint not null default 1");
AddField("bugs", "cclist_accessible", "tinyint not null default 1");
# 2001-08-21 myk@mozilla.org bug84338:
# Add a field for the attachment ID to the bugs_activity table, so installations
# using the attachment manager can record changes to attachments.
AddField("bugs_activity", "attach_id", "mediumint null");
# If you had to change the --TABLE-- definition in any way, then add your
# differential change code *** A B O V E *** this comment.
#

Просмотреть файл

@ -669,4 +669,9 @@ DefParam("moved-default-component",
"t",
'');
DefParam("useattachmenttracker",
"Whether or not to use the attachment tracker that adds additional features for tracking bug attachments.",
"b",
0);
1;

Просмотреть файл

@ -135,7 +135,7 @@ sub ProcessOneBug {
SendSQL("SELECT profiles.login_name, fielddefs.description, " .
" bug_when, removed, added " .
" bug_when, removed, added, attach_id " .
"FROM bugs_activity, fielddefs, profiles " .
"WHERE bug_id = $id " .
" AND fielddefs.fieldid = bugs_activity.fieldid " .
@ -153,13 +153,14 @@ sub ProcessOneBug {
my $difftext = "";
my $lastwho = "";
foreach my $ref (@diffs) {
my ($who, $what, $when, $old, $new) = (@$ref);
my ($who, $what, $when, $old, $new, $attachid) = (@$ref);
if ($who ne $lastwho) {
$lastwho = $who;
$difftext .= "\n$who" . Param('emailsuffix') . " changed:\n\n";
$difftext .= FormatTriple("What ", "Removed", "Added");
$difftext .= ('-' x 76) . "\n";
}
$what =~ s/^Attachment/Attachment #$attachid/ if (Param('useattachmenttracker') && $attachid);
$difftext .= FormatTriple($what, $old, $new);
}

Просмотреть файл

@ -0,0 +1,192 @@
[% INCLUDE global/header
title = "Edit Attachment #$attachid for Bug #$bugid"
h1 = "Edit Attachment #$attachid for <a href=\"show_bug.cgi?id=$bugid\">Bug #$bugid</a>"
h2 = bugsummary
style = "
th { text-align: right; vertical-align: top; }
td { text-align: left; vertical-align: top; }
td#info { text-align: right; vertical-align: top; }
td#actions { text-align: right; vertical-align: bottom; }
td#noview { text-align: left; vertical-align: center; }
"
%]
<form method="post" action="attachment.cgi" onsubmit="normalizeComments();">
<input type="hidden" name="id" value="[% attachid %]">
<input type="hidden" name="action" value="update">
<table width="100%">
<tr>
<td width="25%">
<small>
<b>Description:</b><br>
<textarea rows="3" cols="25" name="description">[% description %]</textarea><br>
<b>MIME Type:</b><br>
<input type="text" size="20" name="mimetype" value="[% mimetype %]"><br>
<b>Flags:</b><br>
<input type="checkbox" name="ispatch" value="1"[% " checked" IF ispatch %]>patch
<input type="checkbox" name="isobsolete" value="1"[% " checked" IF isobsolete %]>obsolete<br>
[% IF statusdefs.size %]
<b>Status:</b><br>
[% FOREACH def = statusdefs %]
<input type="checkbox" name="status" value="[% def.id %]"[% " checked" IF statuses.${def.id} %]>[% def.name %]<br>
[% END %]
[% END %]
<div id="smallCommentFrame">
<b>Comment (on the bug):</b><br>
<textarea name="comment" rows="5" cols="25"></textarea><br>
</div>
<input type="submit" value="Submit">
</small>
</td>
[% IF isviewable %]
<td width="75%">
<textarea id="editFrame" name="comment" style="height: 400px; width: 100%; display: none;"></textarea>
<iframe id="viewFrame" src="attachment.cgi?id=[% attachid %]&action=view" style="height: 400px; width: 100%;">
<b>You cannot view the attachment while editing it because your browser does not support IFRAMEs.
<a href="attachment.cgi?id=[% attachid %]&action=view">View the attachment on a separate page</a>.</b>
</iframe>
<button id="editButton" onclick="editAsComment();">Edit Attachment As Comment</button>
<button id="undoEditButton" onclick="undoEditAsComment();" style="display: none;">Undo Edit As Comment</button>
<button id="redoEditButton" onclick="redoEditAsComment();" style="display: none;">Redo Edit As Comment</button>
</td>
[% ELSE %]
<td id="noview" width="50%">
<p><b>
Attachment cannot be viewed because its MIME type is not either text/*, image/*, or application/vnd.mozilla.*.
<a href="attachment.cgi?id=[% attachid %]&action=view">Download the attachment instead</a>.
</b></p>
</td>
[% END %]
</tr>
</table>
Attachments on this Bug:
[% FOREACH a = attachments %]
[% IF a == attachid %]
#[% a %]
[% ELSE %]
<a href="attachment.cgi?id=[% a %]&action=edit">#[% a %]</a>
[% END %]
[% "|" UNLESS loop.last() %]
[% END %]
</form>
<script type="application/x-javascript" language="JavaScript">
function editAsComment()
{
// Get the content of the document as a string.
var viewFrame = document.getElementById('viewFrame');
var aSerializer = new XMLSerializer();
var contentDocument = viewFrame.contentDocument;
var theContent = aSerializer.serializeToString(contentDocument);
// If this is a plaintext document, remove cruft that Mozilla adds
// because it treats it as an HTML document with a big PRE section.
// http://bugzilla.mozilla.org/show_bug.cgi?id=86012
var mimeType = '[% mimetype %]';
if ( mimeType == 'text/plain' )
{
theContent = theContent.replace( /^<html><head\/><body><pre>/ , "" );
theContent = theContent.replace( /<\/pre><\/body><\/html>$/ , "" );
theContent = theContent.replace( /&lt;/gi , "<" );
theContent = theContent.replace( /&gt;/gi , ">" );
theContent = theContent.replace( /&amp;/gi , "&" );
}
// Add mail-style quote indicators (>) to the beginning of each line.
// ".*\n" matches lines that end with a newline, while ".+" matches
// the rare situation in which the last line of a file does not end
// with a newline.
theContent = theContent.replace( /(.*\n|.+)/g , ">$1" );
hideElementById('viewFrame');
hideElementById('editButton');
hideElementById('smallCommentFrame');
showElementById('undoEditButton');
// Show the TEXTAREA that will contain the editable attachment
// and copy the content of the attachment into it.
showElementById('editFrame');
var editFrame = document.getElementById('editFrame');
editFrame.value = theContent;
editFrame.value += "\n\n";
}
function undoEditAsComment()
{
// Hide the "edit attachment as comment" TEXTAREA and the "undo" button.
hideElementById('undoEditButton');
hideElementById('editFrame');
// Show the "view attachment" IFRAME, the "redo" button that allows the user
// to go back to editing the attachment as a comment, and the small comment field.
showElementById('viewFrame');
showElementById('redoEditButton');
showElementById('smallCommentFrame');
}
function redoEditAsComment()
{
// Hide the "view attachment" IFRAME, the "redo" button that allows the user
// to go back to editing the attachment as a comment, and the small comment field.
hideElementById('viewFrame');
hideElementById('redoEditButton');
hideElementById('smallCommentFrame');
// Show the "edit attachment as comment" TEXTAREA and the "undo" button.
showElementById('undoEditButton');
showElementById('editFrame');
}
function hideElementById(id)
{
var elm = document.getElementById(id);
if (!elm) return;
elm.style.setProperty('display', 'none', '');
}
function showElementById(id, val)
{
var elm = document.getElementById(id);
if (!elm) return;
if (!val) val = 'inline';
elm.style.setProperty('display', val, '');
}
function normalizeComments()
{
// Remove the unused comment field from the document so its contents
// do not get transmitted back to the server.
var small = document.getElementById('smallCommentFrame');
var big = document.getElementById('editFrame');
if ( small.style.getProperty('display') == 'none' )
{
small.parentNode.removeChild(small);
}
if ( big.style.getProperty('display') == 'none' )
{
big.parentNode.removeChild(big);
}
}
</script>
<br>
[% INCLUDE global/footer %]

Просмотреть файл

@ -0,0 +1,58 @@
<br>
<table cellspacing="0" cellpadding="4" border="1">
<tr>
<th bgcolor="#cccccc" align="left">Attachment</th>
<th bgcolor="#cccccc" align="left">Type</th>
<th bgcolor="#cccccc" align="left">Modified</th>
<th bgcolor="#cccccc" align="left">Status</th>
<th bgcolor="#cccccc" align="left">Actions</th>
</tr>
[% FOREACH attachment = attachments %]
<tr>
<td valign="top">
[% IF attachment.isobsolete %]
<strike><a href="attachment.cgi?id=[% attachment.attachid %]&action=view">[% attachment.description %]</a></strike>
[% ELSE %]
<a href="attachment.cgi?id=[% attachment.attachid %]&action=view">[% attachment.description %]</a>
[% END %]
</td>
<td valign="top">
[% IF attachment.ispatch %]
<i>patch</i>
[% ELSE %]
[% attachment.mimetype %]
[% END %]
</td>
<td valign="top">[% attachment.date %]</td>
<td valign="top">
<nobr>
[% IF attachment.statuses.size == 0 %]
<i>none</i>
[% ELSE %]
[% FOREACH s = attachment.statuses %]
[% s %]<br>
[% END %]
[% END %]
</nobr>
</td>
<td valign="top">
<a href="attachment.cgi?id=[% attachment.attachid %]&action=edit">Edit</a>
</td>
</tr>
[% END %]
<tr>
<td colspan="4">
<a href="createattachment.cgi?id=[% bugid %]">Create a new attachment</a> (proposed patch, testcase, etc.)
</td>
<td colspan="1">
<a href="attachment.cgi?bugid=[% bugid %]&action=viewall">View All</a>
</td>
</tr>
</table>
<br>

Просмотреть файл

@ -0,0 +1,16 @@
[% INCLUDE global/header
title = "Changes Submitted"
style = "th { text-align: left; }"
%]
<p>
Your changes have been submitted for
<a href="attachment.cgi?id=[% attachid %]&action=edit">attachment #[% attachid %]</a>
of <a href="show_bug.cgi?id=[% bugid %]">bug #[% bugid %]</a>.
</p>
[% mailresults %]
<br>
[% INCLUDE global/footer %]

Просмотреть файл

@ -0,0 +1,78 @@
[% INCLUDE global/header
title = "View All Attachments for Bug #$bugid"
h1 = "View All Attachments for <a href=\"show_bug.cgi?id=$bugid\">Bug #$bugid</a>"
h2 = bugsummary
style = "
th { text-align: right; vertical-align: top; }
td { text-align: left; vertical-align: top; }
td#info { text-align: right; vertical-align: top; }
td#actions { text-align: right; vertical-align: bottom; }
"
%]
<br>
[% FOREACH a = attachments %]
<div align="center">
<table cellspacing="0" cellpadding="4" border="1" width="75%">
<tr>
<td valign="top" bgcolor="#cccccc" colspan="5">
<big><b>Attachment #[% a.attachid %]</b></big>
</td>
</tr>
<tr>
<td valign="top">
[% IF a.isobsolete %]
<strike>[% a.description %]</strike>
[% ELSE %]
[% a.description %]
[% END %]
</td>
<td valign="top">
[% IF a.ispatch %]
<i>patch</i>
[% ELSE %]
[% a.mimetype %]
[% END %]
</td>
<td valign="top">[% a.date %]</td>
<td valign="top">
<nobr>
[% IF a.statuses.size == 0 %]
<i>none</i>
[% ELSE %]
[% FOREACH s = a.statuses %]
[% s %]<br>
[% END %]
[% END %]
</nobr>
</td>
<td valign="top">
<a href="attachment.cgi?id=[% a.attachid %]&action=edit">Edit</a>
</td>
</tr>
</table>
[% IF a.isviewable %]
<iframe src="attachment.cgi?id=[% a.attachid %]&action=view" width="75%" height="350">
<b>You cannot view the attachment on this page because your browser does not support IFRAMEs.
<a href="attachment.cgi?id=[% a.attachid %]&action=view">View the attachment on a separate page</a>.</b>
</iframe>
[% ELSE %]
<p><b>
Attachment cannot be viewed because its MIME type is not text/*, image/*, or application/vnd.mozilla.*.
<a href="attachment.cgi?id=[% a.attachid %]&action=view">Download the attachment instead</a>.
</b></p>
[% END %]
</div>
<br><br>
[% END %]
[% INCLUDE global/footer %]

Просмотреть файл

@ -0,0 +1,56 @@
[% INCLUDE global/header
title = "Create Attachment Status"
style = "
th { text-align: right; vertical-align: top; }
td { text-align: left; vertical-align: top; }
"
%]
<form method="post" action="editattachstatuses.cgi">
<input type="hidden" name="action" value="insert">
<input type="hidden" name="id" value="[% id %]">
<table cellspacing="0" cellpadding="4" border="0">
<tr>
<th>Name:</th>
<td>
<input type="text" name="name" size="50" maxlength="50">
</td>
</tr>
<tr>
<th>Description:</th>
<td>
<textarea name="desc" rows="4" cols="50"></textarea>
</td>
</tr>
<tr>
<th>Sort Key:</th>
<td>
<input type="text" name="sortkey" size="5" maxlength="5">
</td>
</tr>
<tr>
<th>Product:</th>
<td>
<select name="product">
[% FOREACH item = products %]
<option value="[% item %]">[% item %]</option>
[% END %]
</select>
</td>
</tr>
<tr>
<th></th>
<td>
<input type="submit" value="Add">
</td>
</tr>
</table>
</form>
[% INCLUDE global/footer %]

Просмотреть файл

@ -0,0 +1,52 @@
[% INCLUDE global/header
title = "Edit Attachment Status"
style = "
th { text-align: right; vertical-align: top; }
td { text-align: left; vertical-align: top; }
"
%]
<form method="post" action="editattachstatuses.cgi">
<input type="hidden" name="action" value="update">
<input type="hidden" name="id" value="[% id %]">
<table cellspacing="0" cellpadding="4" border="0">
<tr>
<th>Name:</th>
<td>
<input type="text" name="name" value="[% name %]" size="50" maxlength="50">
</td>
</tr>
<tr>
<th>Description:</th>
<td>
<textarea name="desc" rows="4" cols="50">[% desc %]</textarea>
</td>
</tr>
<tr>
<th>Sort Key:</th>
<td>
<input type="text" name="sortkey" value="[% sortkey %]" size="5" maxlength="5">
</td>
</tr>
<tr>
<th>Product:</th>
<td>
[% product %]
</td>
</tr>
<tr>
<th></th>
<td>
<input type="submit" value="Update">
</td>
</tr>
</table>
</form>
[% INCLUDE global/footer %]

Просмотреть файл

@ -0,0 +1,52 @@
[% INCLUDE global/header
title = 'Administer Attachment Statuses'
message = message
style = "
th { text-align: left; }
"
%]
<table cellspacing="0" cellpadding="4" border="1">
<tr>
<th>Name</th>
<th>Description</th>
<th>Sort Key</th>
<th>Product</th>
<th>Action(s)</th>
</tr>
[% FOREACH statusdef = statusdefs %]
<tr>
<td>[% statusdef.name FILTER html %]</td>
<td>[% statusdef.description FILTER html %]</td>
<td>[% statusdef.sortkey %]</td>
<td>[% statusdef.product %]</td>
<td>
<a href="editattachstatuses.cgi?action=edit&id=[% statusdef.id %]">Edit</a>
<a href="editattachstatuses.cgi?action=delete&id=[% statusdef.id %]" onclick="return confirmDelete();">Delete</a>
</td>
</tr>
[% END %]
<tr>
<td colspan="4"></td>
<td>
<a href="editattachstatuses.cgi?action=create">Create</a>
</td>
</tr>
</table>
<script language="JavaScript">
function confirmDelete()
{
return confirm('Are you sure you want to permanently delete ' +
'this attachment status? All attachments ' +
'with this status will have it unset.');
}
</script>
[% INCLUDE global/footer %]

Просмотреть файл

@ -0,0 +1,6 @@
[% PerformSubsts(Param('footerhtml')) %]
</body>
</html>

Просмотреть файл

@ -0,0 +1,49 @@
[% DEFAULT
title = ""
h1 = title
h2 = ""
extra = ""
jscript = ""
style = ""
message = ""
%]
<html>
<head>
<title>[% title %]</title>
[% Param('headerhtml') %]
[% jscript %]
<style>
[% style %]
</style>
</head>
<body [% Param('bodyhtml') %] [% extra %]>
[% PerformSubsts(Param('bannerhtml')) %]
[% IF h1 || h2 %]
<table border="0" cellspacing="0" width="100%">
<tr>
<td width="10%" valign="top" align="left">
<table border="0" cellpadding="0" cellspacing="2">
<tr>
<td valign="top" align="left" nowrap>
<font size="+1"><b>[% h1 %]</b></font>
</td>
</tr>
</table>
</td>
<td valign="center">&nbsp;</td>
<td valign="center" align="left">[% h2 %]</td>
</tr>
</table>
[% END %]
[% IF message %]
<table width="100%" cellspacing="0" cellpadding="5" border="1"><tr><td>
<font color="green">[% message %]</font>
</td></tr></table>
[% END %]
[% Param('shutdownhtml') %]