2 # This CGI script presents the results of the build_farm build
4 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2001-2005
5 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001
6 # Copyright (C) Vance Lankhaar <vance@samba.org> 2002-2005
7 # Copyright (C) Martin Pool <mbp@samba.org> 2001
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 # TODO: Allow filtering of the "Recent builds" list to show
24 # e.g. only broken builds or only builds that you care about.
28 use FindBin qw($RealBin);
38 my $WEBDIR = "$RealBin";
39 my $BASEDIR = "$WEBDIR/..";
40 my $CACHEDIR = "$WEBDIR/../cache";
45 my $DEADAGE = 60*60*24*4;
47 ##############################################
48 # this defines what it is possible to build
49 # and what boxes. Should be in a config file
50 my @compilers = util::load_list("$WEBDIR/compilers.list");
51 my (%hosts) = util::load_hash("$WEBDIR/hosts.list");
52 my @hosts = sort { $hosts{$a} cmp $hosts{$b} } keys %hosts;
53 my (%trees) = util::load_hash("$WEBDIR/trees.list");
54 # these aren't really trees... they're just things we want in the menu.
55 # (for recent checkins)
56 my @pseudo_trees = util::load_list("$WEBDIR/pseudo.list");
58 # this is automatically filled in
61 ###############################################
62 # work out a URL so I can refer to myself in links
63 my $myself = $req->self_url;
64 if ($myself =~ /(.*)[?].*/) {
67 if ($myself =~ /http:\/\/.*\/(.*)/) {
71 # for now, hard code the self url - need to sanitize self_url
72 $myself = "http://build.samba.org/";
74 my $cgi_headers_done = 0;
76 ################################################
79 return if ($cgi_headers_done);
80 $cgi_headers_done = 1;
82 print "Content-type: text/html\r\n";
83 #print "Content-type: application/xhtml+xml\r\n";
87 print util::FileLoad("$BASEDIR/web/header.html");
88 print '<title>samba.org build farm</title>';
89 print util::FileLoad("$BASEDIR/web/header2.html");
91 print util::FileLoad("$BASEDIR/web/header3.html");
94 ################################################
95 # start CGI headers for diffs
96 sub cgi_headers_diff() {
97 print "Content-type: application/x-diff\r\n";
101 ################################################
102 # start CGI headers for text output
103 sub cgi_headers_text() {
104 print "Content-type: text/plain\r\n";
108 ################################################
111 print util::FileLoad("$BASEDIR/web/footer.html");
114 ################################################
115 # print an error on fatal errors
120 print "<h1>ERROR: $msg</h1>\n";
125 ################################################
126 # get a param from the request, after sanitizing it
130 if (!defined $req->param($param)) {
131 return wantarray ? () : undef;
136 @result = $req->param($param);
139 $result[0] = $req->param($param);
142 for (my $i = 0; $i <= $#result; $i++) {
143 $result[$i] =~ s/ /_/g;
147 if ($_ =~ m/[^a-zA-Z0-9\-\_\.]/) {
148 fatal("Parameter $param is invalid");
149 return wantarray ? () : undef;
153 return wantarray ? @result : $result[0];
156 ################################
157 # get the name of the build file
158 sub build_fname($$$$)
160 my ($tree, $host, $compiler, $rev) = @_;
162 return "oldrevs/build.$tree.$host.$compiler-$rev";
164 return "build.$tree.$host.$compiler";
167 ###########################################
168 # get a list of old builds and their status
169 sub get_old_revs($$$)
174 my @list = split('\n', `ls oldrevs/build.$tree.$host.$compiler-*.log`);
177 if ($l =~ /-(\d+).log$/) {
179 $ret{$rev} = build_status($host, $tree, $compiler, $rev);
187 # the mtime age is used to determine if builds are still happening
189 # the ctime age is used to determine when the last real build happened
191 ##############################################
192 # get the age of build from mtime
193 sub build_age_mtime($$$$)
195 my ($host, $tree, $compiler, $rev) = @_;
196 my $file=build_fname($tree, $host, $compiler, $rev);
200 $st = stat("$file.log");
202 $age = time() - $st->mtime;
208 ##############################################
209 # get the age of build from ctime
210 sub build_age_ctime($$$$)
212 my ($host, $tree, $compiler, $rev) = @_;
213 my $file = build_fname($tree, $host, $compiler, $rev);
217 $st = stat("$file.log");
219 $age = time() - $st->ctime;
225 ##############################################
226 # get the svn revision of build
227 sub build_revision($$$$)
229 my ($host, $tree, $compiler, $rev) = @_;
230 my $file = build_fname($tree, $host, $compiler, $rev);
238 my $st1 = stat("$file.log");
243 my $st2 = stat("$CACHEDIR/$file.revision");
245 # the ctime/mtime asymmetry is needed so we don't get fooled by
246 # the mtime update from rsync
247 if ($st1 && $st2 && $st1->ctime <= $st2->mtime) {
248 return util::FileLoad("$CACHEDIR/$file.revision");
251 $log = util::FileLoad("$file.log");
253 if ($log =~ /BUILD REVISION:(.*)/) {
257 util::FileSave("$CACHEDIR/$file.revision", "$ret");
262 #############################################
263 # get the overall age of a host
268 for my $compiler (@compilers) {
269 for my $tree (keys %trees) {
270 my $age = build_age_mtime($host, $tree, $compiler, "");
271 if ($age != -1 && ($age < $ret || $ret == -1)) {
279 #############################################
280 # show an age as a string
285 if ($age > $OLDAGE) {
286 return sprintf("<span class=\"old\">%s</span>", util::dhm_time($age));
288 return util::dhm_time($age);
291 ##############################################
292 # get status of build
293 sub build_status($$$$)
295 my ($host, $tree, $compiler, $rev) = @_;
296 my $file = build_fname($tree, $host, $compiler, $rev);
297 my $cachefile="$CACHEDIR/" . $file . ".status";
298 my ($cstatus, $bstatus, $istatus, $tstatus, $sstatus, $dstatus);
299 $cstatus = $bstatus = $istatus = $tstatus = $sstatus = $dstatus =
300 "<span class=\"status unknown\">?</span>";
305 my $st1 = stat("$file.log");
307 return "Unknown Build";
309 my $st2 = stat("$cachefile");
311 if ($st1 && $st2 && $st1->ctime <= $st2->mtime) {
312 return util::FileLoad($cachefile);
315 $log = util::FileLoad("$file.log");
317 if ($log =~ /TEST STATUS:(.*)/) {
319 $tstatus = "<span class=\"status passed\">ok</span>";
321 $tstatus = "<span class=\"status failed\">$1</span>";
325 if ($log =~ /INSTALL STATUS:(.*)/) {
327 $istatus = "<span class=\"status passed\">ok</span>";
329 $istatus = "<span class=\"status failed\">$1</span>";
333 if ($log =~ /BUILD STATUS:(.*)/) {
335 $bstatus = "<span class=\"status passed\">ok</span>";
337 $bstatus = "<span class=\"status failed\">$1</span>";
341 if ($log =~ /CONFIGURE STATUS:(.*)/) {
343 $cstatus = "<span class=\"status passed\">ok</span>";
345 $cstatus = "<span class=\"status failed\">$1</span>";
349 if ($log =~ /(PANIC|INTERNAL ERROR):.*/ ) {
350 $sstatus = "/<span class=\"status panic\">PANIC</span>";
355 if ($log =~ /No space left on device.*/ ) {
356 $dstatus = "/<span class=\"status failed\">disk full</span>";
361 if ($log =~ /CC_CHECKER STATUS: (.*)/ && $1 > 0) {
362 $sstatus .= "/<span class=\"status checker\">$1</span>";
365 $ret = "<a href=\"$myself?function=View+Build;host=$host;tree=$tree;compiler=$compiler";
367 $ret .= ";revision=$rev";
369 $ret .= "\">$cstatus/$bstatus/$istatus/$tstatus$sstatus$dstatus</a>";
371 util::FileSave("$CACHEDIR/$file.status", $ret);
376 ##############################################
377 # translate a status into a set of int representing status
378 sub build_status_vals($) {
379 my $status = strip_html(shift);
383 $status =~ s/PANIC/1/g;
385 return split m%/%, $status;
387 ##############################################
388 # get status of build
391 my ($host, $tree, $compiler, $rev) = @_;
392 my $file = build_fname($tree, $host, $compiler, $rev);
395 my $st1 = stat("$file.err");
399 my $st2 = stat("$CACHEDIR/$file.errcount");
401 if ($st1 && $st2 && $st1->ctime <= $st2->mtime) {
402 return util::FileLoad("$CACHEDIR/$file.errcount");
405 $err = util::FileLoad("$file.err");
407 if (! $err) { return 0; }
409 my $ret = util::count_lines($err);
411 util::FileSave("$CACHEDIR/$file.errcount", "$ret");
416 ##############################################
418 sub view_summary($) {
420 my $list = `ls *.log`;
424 # either "text" or anything else.
425 my $output_type = shift;
432 # zero broken and panic counters
433 for my $tree (keys %trees) {
434 $broken_count{$tree} = 0;
435 $panic_count{$tree} = 0;
436 $host_count{$tree} = 0;
439 # set up a variable to store the broken builds table's code, so we can output when we want
440 my $broken_table = "";
444 # for the text report, include the current time
445 if ($output_type eq 'text') {
447 print "Build status as of $time\n\n";
450 for my $host (@hosts) {
451 for my $compiler (@compilers) {
452 for my $tree (keys %trees) {
453 my $status = build_status($host, $tree, $compiler, "");
454 next if $status =~ /^Unknown Build/;
455 my $age_mtime = build_age_mtime($host, $tree, $compiler, "");
457 if ($age_mtime != -1 && $age_mtime < $DEADAGE) {
458 $host_count{$tree}++;
461 if ($age_mtime < $DEADAGE && $status =~ /status failed/) {
462 $broken_count{$tree}++;
463 if ($status =~ /PANIC/) {
464 $panic_count{$tree}++;
471 if ($output_type eq 'text') {
472 print "Build counts:\n";
473 printf "%-12s %-6s %-6s %-6s\n", "Tree", "Total", "Broken", "Panic";
477 <div id="build-counts" class="build-section">
478 <h2>Build counts:</h2>
482 <th>Tree</th><th>Total</th><th>Broken</th><th>Panic</th>
489 for my $tree (sort keys %trees) {
490 if ($output_type eq 'text') {
491 printf "%-12s %-6s %-6s %-6s\n", $tree, $host_count{$tree},
492 $broken_count{$tree}, $panic_count{$tree};
494 print " <tr><td><a href=\"$myself?function=Recent+Builds;tree=$tree\" title=\"View recent builds for $tree\">$tree</a></td><td>$host_count{$tree}</td><td>$broken_count{$tree}</td>";
496 if ($panic_count{$tree}) {
497 $panic = " class=\"panic\"";
499 print "<td$panic>$panic_count{$tree}</td></tr>\n";
503 if ($output_type eq 'text') {
506 print " </tbody>\n</table></div>\n";
510 ##############################################
511 # Draw the "recent builds" view
512 sub view_recent_builds() {
523 my $sort = { revision => sub { $$b[6] <=> $$a[6] },
524 age => sub { $$a[0] <=> $$b[0] },
525 host => sub { $$a[2] cmp $$b[2] },
526 platform => sub { $$a[1] cmp $$b[1] },
527 compiler => sub { $$a[3] cmp $$b[3] },
529 my (@bstat) = build_status_vals($$b[5]);
530 my (@astat) = build_status_vals($$a[5]);
533 if (defined $bstat[4] && !defined $astat[4]) {
535 } elsif (!defined $bstat[4] && defined $astat[4]) {
538 return ($bstat[0] <=> $astat[0] || # configure
539 $bstat[1] <=> $astat[1] || # compile
540 $bstat[2] <=> $astat[2] || # install
541 $bstat[3] <=> $astat[3] # test
546 my $tree = get_param("tree");
547 my $sort_by = get_param("sortby") || "revision"; # default to revision
549 util::InArray($tree, [keys %trees]) || fatal("not a build tree");
550 util::InArray($sort_by, [keys %$sort]) || fatal("not a valid sort");
552 for my $host (@hosts) {
553 for my $compiler (@compilers) {
554 my $status = build_status($host, $tree, $compiler, "");
555 my $age_mtime = build_age_mtime($host, $tree, $compiler, "");
556 my $age_ctime = build_age_ctime($host, $tree, $compiler, "");
557 my $revision = build_revision($host, $tree, $compiler, "");
558 push @all_builds, [$age_ctime, $hosts{$host}, "<a href=\"$myself?function=View+Host;host=$host;tree=$tree;compiler=$compiler#$host\">$host</a>", $compiler, $tree, $status, $revision]
559 unless $age_mtime == -1 or $age_mtime >= $DEADAGE;
563 @all_builds = sort { $sort->{$sort_by}() || $sort->{age}() } @all_builds;
565 my $sorturl = "$myself?tree=$tree;function=Recent+Builds";
569 <div id="recent-builds" class="build-section">
570 <h2>Recent builds of $tree</h2>
574 <th><a href="$sorturl;sortby=age" title="Sort by build age">Age</a></th>
575 <th><a href="$sorturl;sortby=revision" title="Sort by build revision">Revision</a></th>
577 <th><a href="$sorturl;sortby=platform" title="Sort by platform">Platform</a></th>
578 <th><a href="$sorturl;sortby=host" title="Sort by host">Host</a></th>
579 <th><a href="$sorturl;sortby=compiler" title="Sort by compiler">Compiler</a></th>
580 <th><a href="$sorturl;sortby=status" title="Sort by build status">Status</a></th>
587 for my $build (@all_builds) {
588 my $age_mtime = $$build[0];
589 my $rev = $$build[6];
591 join("</td><td>" , util::dhm_time($age_mtime),
592 $rev, @$build[4, 1, 2, 3, 5]),
595 print " </tbody>\n</table>\n</div>\n";
598 ##############################################
599 # Draw the "dead hosts" table
600 sub draw_dead_hosts {
601 my $output_type = shift;
604 # don't output anything if there are no dead hosts
605 if ($#deadhosts < 0) {
609 # don't include in text report
610 if ($output_type eq 'text') {
615 <div class="build-section" id="dead-hosts">
619 <tr><th>Host</th><th>OS</th><th>Min Age</th></tr>
624 for my $host (@deadhosts) {
625 my $age_ctime = host_age($host);
626 print " <tr><td>$host</td><td>$hosts{$host}</td><td>", util::dhm_time($age_ctime), "</td></tr>";
630 print " </tbody>\n</table>\n</div>\n";
633 ##############################################
634 # show the available old revisions, if any
635 sub show_oldrevs($$$)
640 my %revs = get_old_revs($tree, $host, $compiler);
641 my @revs = sort { $revs{$b} cmp $revs{$a} } keys %revs;
643 return if ($#revs < 1);
645 print "<h2>Older builds:</h2>\n";
648 <table class=\"real\">
649 <tr><th>Revision</th><th>Status</th></tr>
654 for my $rev (@revs) {
657 next if ($s eq $lastrev);
659 print "<tr><td>$rev</td><td>$revs{$rev}</td></tr>\n";
664 ##############################################
665 # view one build in detail
667 my $tree=get_param("tree");
668 my $host=get_param("host");
669 my $compiler=get_param("compiler");
670 my $rev=get_param('revision');
672 # ensure the params are valid before using them
673 util::InArray($host, [keys %hosts]) || fatal("unknown host");
674 util::InArray($compiler, \@compilers) || fatal("unknown compiler");
675 util::InArray($tree, [keys %trees]) || fatal("not a build tree");
677 my $file=build_fname($tree, $host, $compiler, $rev);
683 my $age_mtime = build_age_mtime($host, $tree, $compiler, $rev);
684 my $revision = build_revision($host, $tree, $compiler, $rev);
685 my $status = build_status($host, $tree, $compiler, $rev);
687 $rev = int($rev) if $rev;
689 $log = util::FileLoad("$file.log");
690 $err = util::FileLoad("$file.err");
693 $log = util::cgi_escape($log);
695 if ($log =~ /(.*)/) { $uname=$1; }
696 if ($log =~ /CFLAGS=(.*)/) { $cflags=$1; }
697 if ($log =~ /configure options: (.*)/) { $config=$1; }
701 $err = util::cgi_escape($err);
704 print "<h2>Host information:</h2>\n";
706 print util::FileLoad("../web/$host.html");
709 <table class=\"real\">
710 <tr><td>Host:</td><td><a href=\"$myself?function=View+Host;host=$host;tree=$tree;compiler=$compiler#$host\">$host</a> - $hosts{$host}</td></tr>
711 <tr><td>Uname:</td><td>$uname</td></tr>
712 <tr><td>Tree:</td><td>$tree</td></tr>
713 <tr><td>Build Revision:</td><td>" . $revision . "</td></tr>
714 <tr><td>Build age:</td><td class=\"age\">" . red_age($age_mtime) . "</td></tr>
715 <tr><td>Status:</td><td>$status</td></tr>
716 <tr><td>Compiler:</td><td>$compiler</td></tr>
717 <tr><td>CFLAGS:</td><td>$cflags</td></tr>
718 <tr><td>configure options: </td><td>$config</td></tr>
722 show_oldrevs($tree, $host, $compiler);
725 # check the head of the output for our magic string
726 my $plain_logs = (defined get_param("plain") &&
727 get_param("plain") =~ /^(yes|1|on|true|y)$/i);
730 $rev_var = ";revision=$rev";
733 print "<div id=\"log\">\n";
737 print "<p>Switch to the <a href=\"$myself?function=View+Build;host=$host;tree=$tree;compiler=$compiler$rev_var;plain=true\" title=\"Switch to bland, non-javascript, unstyled view\">Plain View</a></p>";
739 print "<div id=\"actionList\">\n";
740 # These can be pretty wide -- perhaps we need to
741 # allow them to wrap in some way?
743 print "<h2>No error log available</h2>\n";
745 print "<h2>Error log:</h2>\n";
746 print make_collapsible_html('action', "Error Output", "\n$err", "stderr-0");
750 print "<h2>No build log available</h2>\n";
752 print "<h2>Build log:</h2>\n";
753 print_log_pretty($log);
756 print "<p><small>Some of the above icons derived from the <a href=\"http://www.gnome.org\">Gnome Project</a>'s stock icons.</p>";
760 print "<p>Switch to the <a href=\"$myself?function=View+Build;host=$host;tree=$tree;compiler=$compiler$rev_var\" title=\"Switch to colourful, javascript-enabled, styled view \">Enhanced View</a></p>";
762 print "<h2>No error log available</h2>\n";
764 print "<h2>Error log:</h2>\n";
765 print "<div id=\"errorLog\"><pre>" . join('', $err) . "</pre></div>\n";
768 print "<h2>No build log available</h2>n";
771 print "<h2>Build log:</h2>\n";
772 print "<div id=\"buildLog\"><pre>" . join('', $log) . "</pre></div>\n";
779 ##################################################
780 # print the host's table of information
783 my $output_type = "html";
785 if ($output_type eq 'text') {
786 print "Host summary:\n";
789 print "<div class=\"build-section\" id=\"build-summary\">\n";
790 print "<h2>Host summary:</h2>\n";
793 my $list = `ls *.log`;
795 my (@requested_hosts) = get_param('host');
797 foreach (@requested_hosts) {
798 util::InArray($_, [keys %hosts]) || fatal("unknown host");
801 for my $host (@requested_hosts) {
802 # make sure we have some data from it
803 if (! ($list =~ /$host/)) {
804 if ($output_type ne 'text') {
805 print "<!-- skipping $host -->\n";
812 for my $compiler (@compilers) {
813 for my $tree (sort keys %trees) {
814 my $revision = build_revision($host, $tree, $compiler, "");
815 my $age_mtime = build_age_mtime($host, $tree, $compiler, "");
816 my $age_ctime = build_age_ctime($host, $tree, $compiler, "");
817 my $warnings = err_count($host, $tree, $compiler, "");
818 if ($age_ctime != -1 && $age_ctime < $DEADAGE) {
819 my $status = build_status($host, $tree, $compiler, "");
821 if ($output_type eq 'text') {
822 printf "%-12s %-10s %-10s %-10s %-10s\n",
823 "Tree", "Compiler", "Build Age", "Status", "Warnings";
828 <div class="host summary">
829 <a id="$host" name="$host" />
830 <h3>$host - $hosts{$host}</h3>
834 <th>Target</th><th>Build<br />Revision</th><th>Build<br />Age</th><th>Status<br />config/build<br />install/test</th><th>Warnings</th>
842 if ($output_type eq 'text') {
843 printf "%-12s %-10s %-10s %-10s %-10s\n",
844 $tree, $compiler, util::dhm_time($age_mtime),
845 strip_html($status), $warnings;
848 print " <tr><td><span class=\"tree\">$tree</span>/$compiler</td><td>$revision</td><td class=\"age\">" . red_age($age_mtime) . "</td><td class=\"status\">$status</td><td>$warnings</td></tr>\n";
855 if ($output_type eq 'text') {
859 print " </tbody>\n</table></div>\n";
862 push(@deadhosts, $host);
867 if ($output_type ne 'text') {
871 draw_dead_hosts($output_type, @deadhosts);
875 ##############################################
876 # prints the log in a visually appealing manner
877 sub print_log_pretty() {
880 # do some pretty printing for the actions
883 Running\ action\s+([\w\-]+)
885 ACTION\ (PASSED|FAILED):\ ([\w\-]+)
891 # handle pretty-printing of static-analysis tools
892 if ($actionName eq 'cc_checker') {
893 $output = print_log_cc_checker($output);
896 make_collapsible_html('action', $actionName, $output, $id++,
900 # $log is already CGI-escaped, so handle '>' in test name by handling >
902 --==--==--==--==--==--==--==--==--==--==--.*?
903 Running\ test\ ([\w\-=,_:\ /.&;]+).*?
904 --==--==--==--==--==--==--==--==--==--==--
906 ==========================================.*?
907 TEST\ (FAILED|PASSED|SKIPPED):.*?
908 ==========================================\s+
909 }{make_collapsible_html('test', $1, $2, $id++, $3)}exgs;
911 print "<tt><pre>" .join('', $log) . "</pre></tt><p>\n";
914 ##############################################
915 # generate pretty-printed html for static analysis tools
916 sub print_log_cc_checker($) {
920 # for now, we only handle the IBM Checker's output style
921 if ($input !~ m/^BEAM_VERSION/ms) {
929 my ($entry, $title, $status, $id);
931 foreach (split /\n/, $input) {
933 # for each line, check if the line is a new entry,
934 # otherwise, store the line under the current entry.
939 $output .= make_collapsible_html('cc_checker', $title, $content,
945 # clear maintenance vars
946 ($inEntry, $content) = (1, "");
949 m/^-- ((ERROR|WARNING|MISTAKE).*?)\s+>>>([a-zA-Z0-9]+_(\w+)_[a-zA-Z0-9]+)/;
951 # then store the result
952 ($title, $status, $id) = ("$1 $4", $2, $3);
953 } elsif (m/^CC_CHECKER STATUS/) {
955 $output .= make_collapsible_html('cc_checker', $title, $content,
963 # not a new entry, so part of the current entry's output
968 # This function does approximately the same as the following, following
969 # commented-out regular expression except that the regex doesn't quite
970 # handle IBM Checker's newlines quite right.
972 # --\ ((ERROR|WARNING|MISTAKE).*?)\s+
976 # }{make_collapsible_html('cc_checker', "$1 $4", $5, $3, $2)}exgs;
980 ##############################################
981 # generate html for a collapsible section
982 sub make_collapsible_html($$$$)
984 my ($type, # the logical type of it. e.g. "test" or "action"
985 $title, # the title to be displayed
987 my $status = (shift or "");
989 my $icon = (defined $status && ($status =~ /failed/i)) ? 'icon_hide_16.png' : 'icon_unhide_16.png';
991 # trim leading and trailing whitespace
992 $output =~ s/^\s+//s;
993 $output =~ s/\s+$//s;
995 # note that we may be inside a <pre>, so we don't put any extra whitespace in this html
996 my $return = "<div class=\"$type unit \L$status\E\" id=\"$type-$id\">" .
997 "<a href=\"javascript:handle('$id');\">" .
998 "<img id=\"img-$id\" name=\"img-$id\" src=\"$icon\" /> " .
999 "<div class=\"$type title\">$title</div>" .
1001 "<div class=\"$type status \L$status\E\">$status</div>" .
1002 "<div class=\"$type output\" id=\"output-$id\">" .
1003 "<pre>$output</pre>" .
1010 ##############################################
1011 # simple html markup stripper
1015 # get rid of comments
1016 $string =~ s/<!\-\-(.*?)\-\->/$2/g;
1019 while ($string =~ s&<(\w+).*?>(.*?)</\1>&$2&) {
1026 ##############################################
1029 print $req->startform("GET");
1030 print "<div id=\"build-menu\">\n";
1031 print $req->popup_menu(-name=>'host',
1033 -labels=>\%hosts) . "\n";
1034 print $req->popup_menu("tree", [sort (keys %trees, @pseudo_trees)]) . "\n";
1035 print $req->popup_menu("compiler", \@compilers) . "\n";
1037 print $req->submit('function', 'View Build') . "\n";
1038 print $req->submit('function', 'View Host') . "\n";
1039 print $req->submit('function', 'Recent Checkins') . "\n";
1040 print $req->submit('function', 'Summary') . "\n";
1041 print $req->submit('function', 'Recent Builds') . "\n";
1043 print $req->endform() . "\n";
1046 ###############################################
1047 # display top of page
1050 chdir("$BASEDIR/data") || fatal("can't change to data directory");
1053 ###############################################
1056 my $fn_name = get_param('function') || '';
1058 if ($fn_name eq 'text_diff') {
1060 chdir("$BASEDIR/data") || fatal("can't change to data directory");
1061 history::diff(get_param('author'),
1064 get_param('revision'),
1067 elsif ($fn_name eq 'Text_Summary') {
1069 chdir("$BASEDIR/data") || fatal("can't change to data directory");
1070 view_summary('text');
1074 if ($fn_name eq "View_Build") {
1076 } elsif ($fn_name eq "View_Host") {
1078 } elsif ($fn_name eq "Recent_Builds") {
1079 view_recent_builds();
1080 } elsif ($fn_name eq "Recent_Checkins") {
1081 history::history(get_param('tree'));
1082 } elsif ($fn_name eq "diff") {
1083 history::diff(get_param('author'),
1086 get_param('revision'),
1089 view_summary('html');