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
8 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program; if not, write to the Free Software
22 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 # TODO: Allow filtering of the "Recent builds" list to show
25 # e.g. only broken builds or only builds that you care about.
29 use FindBin qw($RealBin);
36 use CGI qw/:standard/;
39 my $WEBDIR = "$RealBin";
40 my $BASEDIR = "$WEBDIR/..";
41 my $CACHEDIR = "$WEBDIR/../cache";
46 my $DEADAGE = 60*60*24*4;
48 ##############################################
49 # this defines what it is possible to build
50 # and what boxes. Should be in a config file
51 my @compilers = util::load_list("$WEBDIR/compilers.list");
52 my (%hosts) = util::load_hash("$WEBDIR/hosts.list");
53 my @hosts = sort { $hosts{$a} cmp $hosts{$b} } keys %hosts;
54 my (%trees) = util::load_hash("$WEBDIR/trees.list");
55 # these aren't really trees... they're just things we want in the menu.
56 # (for recent checkins)
57 my @pseudo_trees = util::load_list("$WEBDIR/pseudo.list");
59 # this is automatically filled in
62 ###############################################
63 # URL so I can refer to myself in links
64 my $myself = $req->url();
66 ################################################
70 print start_html(-title => 'samba.org build farm',
71 -script => {-language=> 'JAVASCRIPT', -src=>"/build_farm.js" },
73 -keywords => "Samba SMB CIFS Build Farm",
74 -description => "Home of the Samba Build Farm, the automated testing facility.",
79 Link({-rel => "stylesheet",
80 -href => "build_farm.css",
83 Link({-rel => "stylesheet",
84 -href => "http://master.samba.org/samba/style/common.css",
87 Link({-rel=>"shortcut icon",
88 -href=>"http://www.samba.org/samba/images/favicon.ico"})
92 print util::FileLoad("$BASEDIR/web/header2.html");
94 print util::FileLoad("$BASEDIR/web/header3.html");
97 ################################################
100 print util::FileLoad("$BASEDIR/web/footer.html");
101 print $req->end_html;
104 ################################################
105 # print an error on fatal errors
110 print $req->h1("ERROR: $msg");
115 ################################################
116 # get a param from the request, after sanitizing it
120 if (!defined $req->param($param)) {
121 return wantarray ? () : undef;
126 @result = $req->param($param);
128 $result[0] = $req->param($param);
131 for (my $i = 0; $i <= $#result; $i++) {
132 $result[$i] =~ s/ /_/g;
136 if ($_ =~ m/[^a-zA-Z0-9\-\_\.]/) {
137 fatal("Parameter $param is invalid");
138 return wantarray ? () : undef;
142 return wantarray ? @result : $result[0];
145 ################################
146 # get the name of the build file
147 sub build_fname($$$$)
149 my ($tree, $host, $compiler, $rev) = @_;
151 return "oldrevs/build.$tree.$host.$compiler-$rev";
153 return "build.$tree.$host.$compiler";
156 ###########################################
157 # get a list of old builds and their status
158 sub get_old_revs($$$)
160 my ($tree, $host, $compiler) = @_;
161 my @list = split('\n', `ls oldrevs/build.$tree.$host.$compiler-*.log`);
164 if ($l =~ /-(\d+).log$/) {
166 $ret{$rev} = build_status($host, $tree, $compiler, $rev);
174 # the mtime age is used to determine if builds are still happening
176 # the ctime age is used to determine when the last real build happened
178 ##############################################
179 # get the age of build from mtime
180 sub build_age_mtime($$$$)
182 my ($host, $tree, $compiler, $rev) = @_;
183 my $file=build_fname($tree, $host, $compiler, $rev);
187 $st = stat("$file.log");
189 $age = time() - $st->mtime;
195 ##############################################
196 # get the age of build from ctime
197 sub build_age_ctime($$$$)
199 my ($host, $tree, $compiler, $rev) = @_;
200 my $file = build_fname($tree, $host, $compiler, $rev);
204 $st = stat("$file.log");
206 $age = time() - $st->ctime;
212 ##############################################
213 # get the svn revision of build
214 sub build_revision($$$$)
216 my ($host, $tree, $compiler, $rev) = @_;
217 my $file = build_fname($tree, $host, $compiler, $rev);
225 my $st1 = stat("$file.log");
230 my $st2 = stat("$CACHEDIR/$file.revision");
232 # the ctime/mtime asymmetry is needed so we don't get fooled by
233 # the mtime update from rsync
234 if ($st1 && $st2 && $st1->ctime <= $st2->mtime) {
235 return util::FileLoad("$CACHEDIR/$file.revision");
238 $log = util::FileLoad("$file.log");
240 if ($log =~ /BUILD REVISION:(.*)/) {
244 util::FileSave("$CACHEDIR/$file.revision", "$ret");
249 #############################################
250 # get the overall age of a host
255 for my $compiler (@compilers) {
256 for my $tree (keys %trees) {
257 my $age = build_age_mtime($host, $tree, $compiler, "");
258 if ($age != -1 && ($age < $ret || $ret == -1)) {
266 #############################################
267 # show an age as a string
272 if ($age > $OLDAGE) {
273 return $req->span({-class=>"old"}, util::dhm_time($age));
275 return util::dhm_time($age);
278 ##############################################
279 # get status of build
280 sub build_status($$$$)
282 my ($host, $tree, $compiler, $rev) = @_;
283 my $file = build_fname($tree, $host, $compiler, $rev);
284 my $cachefile="$CACHEDIR/$file.status";
285 my ($cstatus, $bstatus, $istatus, $tstatus, $sstatus, $dstatus);
286 $cstatus = $bstatus = $istatus = $tstatus = $sstatus = $dstatus =
287 $req->span({-class=>"status unknown"}, "?");
292 my $st1 = stat("$file.log");
294 return "Unknown Build";
296 my $st2 = stat("$cachefile");
298 if ($st1 && $st2 && $st1->ctime <= $st2->mtime) {
299 return util::FileLoad($cachefile);
302 $log = util::FileLoad("$file.log");
304 if ($log =~ /TEST STATUS:([0-9]+)/) {
306 $tstatus = $req->span({-class=>"status passed"}, "ok");
308 $tstatus = $req->span({-class=>"status failed"}, $1);
312 if ($log =~ /INSTALL STATUS:(.*)/) {
314 $istatus = $req->span({-class => "status passed"}, "ok");
316 $istatus = $req->span({-class=>"status failed"}, $1);
320 if ($log =~ /BUILD STATUS:(.*)/) {
322 $bstatus = $req->span({-class => "status passed"}, "ok");
324 $bstatus = $req->span({-class => "status failed"}, $1);
328 if ($log =~ /CONFIGURE STATUS:(.*)/) {
330 $cstatus = $req->span({-class => "status passed"}, "ok");
332 $cstatus = $req->span({-class=> "status failed"}, $1);
336 if ($log =~ /(PANIC|INTERNAL ERROR):.*/ ) {
337 $sstatus = "/".$req->span({-class=>"status panic"}, "PANIC");
342 if ($log =~ /No space left on device.*/ ) {
343 $dstatus = "/".$req->span({-class=>"status failed"}, "disk full");
348 if ($log =~ /CC_CHECKER STATUS: (.*)/ && $1 > 0) {
349 $sstatus .= "/".$req->span({-class=>"status checker"}, $1);
352 $req->a({-href=>"$myself?function=View+Build;host=$host;tree=$tree;compiler=$compiler" . ($rev?";revision=$rev":"")}, "$cstatus/$bstatus/$istatus/$tstatus$sstatus$dstatus");
354 util::FileSave("$CACHEDIR/$file.status", $ret);
359 ##############################################
360 # translate a status into a set of int representing status
361 sub build_status_vals($) {
362 my $status = util::strip_html(shift);
366 $status =~ s/PANIC/1/g;
368 return split m%/%, $status;
371 ##############################################
372 # get status of build
375 my ($host, $tree, $compiler, $rev) = @_;
376 my $file = build_fname($tree, $host, $compiler, $rev);
379 my $st1 = stat("$file.err");
383 my $st2 = stat("$CACHEDIR/$file.errcount");
385 if ($st1 && $st2 && $st1->ctime <= $st2->mtime) {
386 return util::FileLoad("$CACHEDIR/$file.errcount");
389 $err = util::FileLoad("$file.err") or return 0;
391 my $ret = util::count_lines($err);
393 util::FileSave("$CACHEDIR/$file.errcount", "$ret");
398 ##############################################
400 sub view_summary($) {
402 my $list = `ls *.log`;
406 # either "text" or anything else.
407 my $output_type = shift;
414 # zero broken and panic counters
415 for my $tree (keys %trees) {
416 $broken_count{$tree} = 0;
417 $panic_count{$tree} = 0;
418 $host_count{$tree} = 0;
421 # set up a variable to store the broken builds table's code, so we can output when we want
422 my $broken_table = "";
426 # for the text report, include the current time
427 if ($output_type eq 'text') {
429 print "Build status as of $time\n\n";
432 for my $host (@hosts) {
433 for my $compiler (@compilers) {
434 for my $tree (keys %trees) {
435 my $status = build_status($host, $tree, $compiler, "");
436 next if $status =~ /^Unknown Build/;
437 my $age_mtime = build_age_mtime($host, $tree, $compiler, "");
439 if ($age_mtime != -1 && $age_mtime < $DEADAGE) {
440 $host_count{$tree}++;
443 if ($age_mtime < $DEADAGE && $status =~ /status failed/) {
444 $broken_count{$tree}++;
445 if ($status =~ /PANIC/) {
446 $panic_count{$tree}++;
453 if ($output_type eq 'text') {
454 print "Build counts:\n";
455 printf "%-12s %-6s %-6s %-6s\n", "Tree", "Total", "Broken", "Panic";
458 print $req->start_div(-id=>"build-counts", -class=>"build-section");
459 print $req->h2('Build counts:');
460 print $req->start_table({-class => "real"}),
462 $req->Tr($req->th("Tree"), $req->th("Total"),
463 $req->th("Broken"), $req->th("Panic"))),
467 for my $tree (sort keys %trees) {
468 if ($output_type eq 'text') {
469 printf "%-12s %-6s %-6s %-6s\n", $tree, $host_count{$tree},
470 $broken_count{$tree}, $panic_count{$tree};
472 print $req->start_Tr;
473 print $req->td($req->a({-href=>"$myself?function=Recent+Builds;tree=$tree",
474 -title=>"View recent builds for $tree"}, $tree));
475 print $req->td($host_count{$tree});
476 print $req->td($broken_count{$tree});
477 if ($panic_count{$tree}) {
478 print $req->start_td({-class => "panic"});
480 print $req->start_td;
482 print $panic_count{$tree} . $req->end_td;
487 if ($output_type eq 'text') {
490 print $req->end_tbody, $req->end_table;
495 ##############################################
496 # Draw the "recent builds" view
497 sub view_recent_builds($$) {
498 my ($tree, $sort_by) = @_;
506 my $sort = { revision => sub { $$b[6] <=> $$a[6] },
507 age => sub { $$a[0] <=> $$b[0] },
508 host => sub { $$a[2] cmp $$b[2] },
509 platform => sub { $$a[1] cmp $$b[1] },
510 compiler => sub { $$a[3] cmp $$b[3] },
512 my (@bstat) = build_status_vals($$b[5]);
513 my (@astat) = build_status_vals($$a[5]);
516 if (defined $bstat[4] && !defined $astat[4]) {
518 } elsif (!defined $bstat[4] && defined $astat[4]) {
521 return ($bstat[0] <=> $astat[0] || # configure
522 $bstat[1] <=> $astat[1] || # compile
523 $bstat[2] <=> $astat[2] || # install
524 $bstat[3] <=> $astat[3] # test
529 util::InArray($tree, [keys %trees]) || fatal("not a build tree");
530 util::InArray($sort_by, [keys %$sort]) || fatal("not a valid sort");
532 for my $host (@hosts) {
533 for my $compiler (@compilers) {
534 my $status = build_status($host, $tree, $compiler, "");
535 my $age_mtime = build_age_mtime($host, $tree, $compiler, "");
536 my $age_ctime = build_age_ctime($host, $tree, $compiler, "");
537 my $revision = build_revision($host, $tree, $compiler, "");
538 push @all_builds, [$age_ctime, $hosts{$host}, $req->a({-href=>"$myself?function=View+Host;host=$host;tree=$tree;compiler=$compiler#$host"}, $host), $compiler, $tree, $status, $revision]
539 unless $age_mtime == -1 or $age_mtime >= $DEADAGE;
543 @all_builds = sort { $sort->{$sort_by}() || $sort->{age}() } @all_builds;
545 my $sorturl = "$myself?tree=$tree;function=Recent+Builds";
547 print $req->start_div(-id=>"recent-builds", -class=>"build-section"),
548 $req->h2("Recent builds of $tree");
550 print $req->start_table({-class => "real"}),
553 $req->th($req->a({-href => "$sorturl;sortby=age",
554 -title => "Sort by build age"}, "Age")),
555 $req->th($req->a({-href => "$sorturl;sortby=revision",
556 -title => "Sort by build revision"},
559 $req->th($req->a({-href => "$sorturl;sortby=platform",
560 -title => "Sort by platform"}, "Platform")),
561 $req->th($req->a({-href => "$sorturl;sortby=host",
562 -title => "Sort by host"}, "Host")),
563 $req->th($req->a({-href=>"$sorturl;sortby=compiler",
564 -title=>"Sort by compiler"}, "Compiler")),
565 $req->th($req->a({-href=>"$sorturl;sortby=status",
566 -title=>"Sort by build status"}, "Status"))
571 for my $build (@all_builds) {
572 my $age_mtime = $$build[0];
573 my $rev = $$build[6];
575 $req->td(util::dhm_time($age_mtime)),
577 $req->td($$build[4]),
578 $req->td($$build[1]),
579 $req->td($$build[2]),
580 $req->td($$build[3]),
581 $req->td($$build[5]));
583 print $req->end_tbody, $req->end_table;
587 ##############################################
588 # Draw the "dead hosts" table
589 sub draw_dead_hosts {
590 my $output_type = shift;
593 # don't output anything if there are no dead hosts
594 return if ($#deadhosts < 0);
596 # don't include in text report
597 return if ($output_type eq 'text');
599 print $req->start_div(-class => "build-section", -id=>"dead-hosts"),
600 $req->h2('Dead Hosts:');
601 print $req->start_table({-class => "real"}),
604 $req->th("Host"), $req->th("OS"),
605 $req->th("Min Age"))),
608 for my $host (@deadhosts) {
609 my $age_ctime = host_age($host);
610 print $req->tr($req->td($host), $req->td($hosts{$host}),
611 $req->td(util::dhm_time($age_ctime)));
614 print $req->end_tbody, $req->end_table;
618 ##############################################
619 # show the available old revisions, if any
620 sub show_oldrevs($$$)
622 my ($tree, $host, $compiler) = @_;
623 my %revs = get_old_revs($tree, $host, $compiler);
624 my @revs = sort { $revs{$b} cmp $revs{$a} } keys %revs;
626 return if ($#revs < 1);
628 my $ret = $req->h2("Older builds:");
630 $ret .= $req->start_table({-class => "real"}),
631 $req->thead($req->Tr($req->th(["Revision", "Status"]))),
636 for my $rev (@revs) {
639 next if ($s eq $lastrev);
641 $ret.=$req->Tr($req->td([$rev, $revs{$rev}]));
643 if ($lastrev ne "") {
644 print $ret . $req->end_tbody, $req->end_table;
648 ##############################################
649 # view one build in detail
650 sub view_build($$$$) {
651 my ($tree, $host, $compiler, $rev) = @_;
652 # ensure the params are valid before using them
653 util::InArray($host, [keys %hosts]) || fatal("unknown host");
654 util::InArray($compiler, \@compilers) || fatal("unknown compiler");
655 util::InArray($tree, [keys %trees]) || fatal("not a build tree");
657 my $file=build_fname($tree, $host, $compiler, $rev);
663 my $age_mtime = build_age_mtime($host, $tree, $compiler, $rev);
664 my $revision = build_revision($host, $tree, $compiler, $rev);
665 my $status = build_status($host, $tree, $compiler, $rev);
667 $rev = int($rev) if $rev;
669 $log = util::FileLoad("$file.log");
670 $err = util::FileLoad("$file.err");
673 $log = escapeHTML($log);
675 if ($log =~ /(.*)/) { $uname=$1; }
676 if ($log =~ /CFLAGS=(.*)/) { $cflags=$1; }
677 if ($log =~ /configure options: (.*)/) { $config=$1; }
681 $err = escapeHTML($err);
684 print $req->h2('Host information:');
686 print util::FileLoad("../web/$host.html");
688 print $req->table({-class=>"real"},
690 $req->td(["Host:", $req->a({-href=>"$myself?function=View+Host;host=$host;tree=$tree;compiler=$compiler#$host"}, $host)." - $hosts{$host}"]),
691 $req->td(["Uname:", $uname]),
692 $req->td(["Tree:", $tree]),
693 $req->td(["Build Revision:", $revision]),
694 $req->td(["Build age:", $req->div({-class=>"age"}, red_age($age_mtime))]),
695 $req->td(["Status:", $status]),
696 $req->td(["Compiler:", $compiler]),
697 $req->td(["CFLAGS:", $cflags]),
698 $req->td(["configure options:", $config])]));
700 show_oldrevs($tree, $host, $compiler);
702 # check the head of the output for our magic string
703 my $plain_logs = (defined get_param("plain") &&
704 get_param("plain") =~ /^(yes|1|on|true|y)$/i);
707 $rev_var = ";revision=$rev";
710 print $req->start_div({-id=>"log"});
714 print $req->p("Switch to the ".$req->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"));
716 print $req->start_div(-id=>"actionList");
717 # These can be pretty wide -- perhaps we need to
718 # allow them to wrap in some way?
720 print $req->h2("No error log available");
722 print $req->h2("Error log:");
723 print make_collapsible_html('action', "Error Output", "\n$err", "stderr-0");
727 print $req->h2("No build log available");
729 print $req->h2("Build log:");
730 print_log_pretty($log);
733 print $req->p($req->small("Some of the above icons derived from the ".$req->a({-href=>"http://www.gnome.org"}, "Gnome Project")."'s stock icons."));
736 print $req->p("Switch to the ".$req->a({-href=>"$myself?function=View+Build;host=$host;tree=$tree;compiler=$compiler$rev_var", -title=>"Switch to colourful, javascript-enabled, styled view"}, "Enhanced View"));
738 print $req->h2("No error log available");
740 print $req->h2('Error log:');
741 print $req->div({-id=>"errorLog"}, $req->pre($err));
744 print $req->h2('No build log available');
747 print $req->h2('Build log:');
748 print $req->div({-id=>"buildLog"}, $req->pre($log));
755 ##################################################
756 # print the host's table of information
758 my (@requested_hosts) = @_;
760 my $output_type = "html";
762 if ($output_type eq 'text') {
763 print "Host summary:\n";
765 print $req->start_div({-class=>"build-section", -id=>"build-summary"});
766 print $req->h2('Host summary:');
769 my $list = `ls *.log`;
771 foreach (@requested_hosts) {
772 util::InArray($_, [keys %hosts]) || fatal("unknown host");
775 for my $host (@requested_hosts) {
776 # make sure we have some data from it
777 if (! ($list =~ /$host/)) {
778 if ($output_type ne 'text') {
779 print "<!-- skipping $host -->\n";
786 for my $compiler (@compilers) {
787 for my $tree (sort keys %trees) {
788 my $revision = build_revision($host, $tree, $compiler, "");
789 my $age_mtime = build_age_mtime($host, $tree, $compiler, "");
790 my $age_ctime = build_age_ctime($host, $tree, $compiler, "");
791 my $warnings = err_count($host, $tree, $compiler, "");
792 if ($age_ctime != -1 && $age_ctime < $DEADAGE) {
793 my $status = build_status($host, $tree, $compiler, "");
795 if ($output_type eq 'text') {
796 printf "%-12s %-10s %-10s %-10s %-10s\n",
797 "Tree", "Compiler", "Build Age", "Status", "Warnings";
800 print $req->start_div({-class=>"host summary"});
801 print $req->a({-id=>$host, -name=>$host});
802 print $req->h3("$host - $hosts{$host}");
803 print $req->start_table({-class=>"real"}),
804 $req->thead($req->Tr(
805 $req->th(["Target", "Build<br/>Revision", "Build<br />Age", "Status<br />config/build<br />install/test", "Warnings"]))),
810 if ($output_type eq 'text') {
811 printf "%-12s %-10s %-10s %-10s %-10s\n",
812 $tree, $compiler, util::dhm_time($age_mtime),
813 util::strip_html($status), $warnings;
815 print $req->Tr($req->td([$req->span({-class=>"tree"}, $tree)."/$compiler", $revision, $req->div({-class=>"age"}, red_age($age_mtime)), $req->div({-class=>"status"}, $status), $warnings]));
822 if ($output_type eq 'text') {
825 print $req->end_tbody, $req->end_table;
829 push(@deadhosts, $host);
833 if ($output_type ne 'text') {
837 draw_dead_hosts($output_type, @deadhosts);
840 ##############################################
841 # prints the log in a visually appealing manner
842 sub print_log_pretty() {
845 # do some pretty printing for the actions
848 Running\ action\s+([\w\-]+)
850 ACTION\ (PASSED|FAILED):\ ([\w\-]+)
856 # handle pretty-printing of static-analysis tools
857 if ($actionName eq 'cc_checker') {
858 $output = print_log_cc_checker($output);
861 make_collapsible_html('action', $actionName, $output, $id++,
865 # $log is already CGI-escaped, so handle '>' in test name by handling >
867 --==--==--==--==--==--==--==--==--==--==--.*?
868 Running\ test\ ([\w\-=,_:\ /.&;]+).*?
869 --==--==--==--==--==--==--==--==--==--==--
871 ==========================================.*?
872 TEST\ (FAILED|PASSED|SKIPPED):.*?
873 ==========================================\s+
874 }{make_collapsible_html('test', $1, $2, $id++, $3)}exgs;
876 print $req->p($req->tt($req->pre($log)))."\n";
879 ##############################################
880 # generate pretty-printed html for static analysis tools
881 sub print_log_cc_checker($) {
885 # for now, we only handle the IBM Checker's output style
886 if ($input !~ m/^BEAM_VERSION/ms) {
894 my ($entry, $title, $status, $id);
896 foreach (split /\n/, $input) {
898 # for each line, check if the line is a new entry,
899 # otherwise, store the line under the current entry.
904 $output .= make_collapsible_html('cc_checker', $title, $content,
910 # clear maintenance vars
911 ($inEntry, $content) = (1, "");
914 m/^-- ((ERROR|WARNING|MISTAKE).*?)\s+>>>([a-zA-Z0-9]+_(\w+)_[a-zA-Z0-9]+)/;
916 # then store the result
917 ($title, $status, $id) = ("$1 $4", $2, $3);
918 } elsif (m/^CC_CHECKER STATUS/) {
920 $output .= make_collapsible_html('cc_checker', $title, $content,
928 # not a new entry, so part of the current entry's output
933 # This function does approximately the same as the following, following
934 # commented-out regular expression except that the regex doesn't quite
935 # handle IBM Checker's newlines quite right.
937 # --\ ((ERROR|WARNING|MISTAKE).*?)\s+
941 # }{make_collapsible_html('cc_checker', "$1 $4", $5, $3, $2)}exgs;
945 ##############################################
946 # generate html for a collapsible section
947 sub make_collapsible_html($$$$)
949 my ($type, # the logical type of it. e.g. "test" or "action"
950 $title, # the title to be displayed
952 my $status = (shift or "");
954 my $icon = (defined $status && ($status =~ /failed/i)) ? 'icon_hide_16.png' : 'icon_unhide_16.png';
956 # trim leading and trailing whitespace
957 $output =~ s/^\s+//s;
958 $output =~ s/\s+$//s;
960 # note that we may be inside a <pre>, so we don't put any extra whitespace in this html
961 return $req->div({-class=>"$type unit $status",
963 $req->a({-href=>"javascript:handle('$id');"},
964 $req->img({-id=>"img-$id", -name=>"img-$id",
967 $req->div({-class => "$type title"}, $title),
969 $req->div({-class=> "$type status $status"}, $status) .
970 $req->div({-class => "$type output", -id=>"output-$id"}, $req->pre($output)));
973 ##############################################
976 return $req->startform("GET"),
977 $req->start_div({-id=>"build-menu"}),
978 $req->popup_menu(-name=>'host',
981 $req->popup_menu("tree", [sort (keys %trees, @pseudo_trees)]),
982 $req->popup_menu("compiler", \@compilers),
984 $req->submit('function', 'View Build'),
985 $req->submit('function', 'View Host'),
986 $req->submit('function', 'Recent Checkins'),
987 $req->submit('function', 'Summary'),
988 $req->submit('function', 'Recent Builds'),
990 $req->endform() . "\n";
993 ###############################################
994 # display top of page
997 chdir("$BASEDIR/data") || fatal("can't change to data directory");
1000 ###############################################
1003 my $fn_name = get_param('function') || '';
1005 if ($fn_name eq 'text_diff') {
1006 print header('application/x-diff');
1007 chdir("$BASEDIR/data") || fatal("can't change to data directory");
1008 history::diff(get_param('author'),
1011 get_param('revision'),
1013 } elsif ($fn_name eq 'Text_Summary') {
1014 print header('text/plain');
1015 chdir("$BASEDIR/data") || fatal("can't change to data directory");
1016 view_summary('text');
1020 if ($fn_name eq "View_Build") {
1021 view_build(get_param("tree"), get_param("host"), get_param("compiler"),
1022 get_param('revision'));
1023 } elsif ($fn_name eq "View_Host") {
1024 view_host(get_param('host'));
1025 } elsif ($fn_name eq "Recent_Builds") {
1026 view_recent_builds(get_param("tree"), get_param("sortby") || "revision");
1027 } elsif ($fn_name eq "Recent_Checkins") {
1028 history::history(get_param('tree'));
1029 } elsif ($fn_name eq "diff") {
1030 history::diff(get_param('author'),
1033 get_param('revision'),
1036 view_summary('html');