diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index 2be8b01f..0b8b4124 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -38,6 +38,37 @@ sub index : Chained('jobset') PathPart('') Args(0) { {}, {select => ["job"], order_by => ["job"], group_by => ["job"], having => { 'sum(isCurrent)' => 0 }} )]; + $c->stash->{systems} = [$c->stash->{jobset}->builds->search({iscurrent => 1}, {select => ["system"], distinct => 1})]; + + # status per system + my @systems = (); + foreach my $system (@{$c->stash->{systems}}) { + push(@systems, $system->system); + } + + if(scalar(@{$c->stash->{activeJobs}}) <= 20) { + my @select = (); + my @as = (); + push(@select, "job"); push(@as, "job"); + foreach my $system (@systems) { + push(@select, "(SELECT buildstatus FROM BuildResultInfo bri NATURAL JOIN Builds b WHERE b.id = (SELECT MAX(id) FROM Builds t WHERE t.project = me.project AND t.jobset = me.jobset AND t.job = me.job AND t.system = '$system'))"); + push(@as, $system); + push(@select, "(SELECT b.id FROM BuildResultInfo bri NATURAL JOIN Builds b WHERE b.id = (SELECT MAX(id) FROM Builds t WHERE t.project = me.project AND t.jobset = me.jobset AND t.job = me.job AND t.system = '$system'))"); + push(@as, $system."-build"); + } + $c->stash->{activeJobsStatus} = [$c->model('DB')->resultset('ActiveJobsForJobset') + ->search( {} + , { bind => [$c->stash->{project}->name, $c->stash->{jobset}->name] + , select => \@select + , as => \@as + , order_by => ["job"] + })]; + } + + # last builds for jobset + my $tmp = $c->stash->{jobset}->builds; + $c->stash->{lastBuilds} = [joinWithResultInfo($c, $tmp) + ->search({finished => 1}, {order_by => "timestamp DESC", rows => 5 })] ; } diff --git a/src/lib/Hydra/Schema/Builds.pm b/src/lib/Hydra/Schema/Builds.pm index c251c5ab..16e729be 100644 --- a/src/lib/Hydra/Schema/Builds.pm +++ b/src/lib/Hydra/Schema/Builds.pm @@ -191,6 +191,14 @@ __PACKAGE__->has_many( use Hydra::Helper::Nix; +# order buildsteps +__PACKAGE__->has_many( + "buildsteps", + "Hydra::Schema::BuildSteps", + { "foreign.build" => "self.id" }, + { order_by => "stepnr" }, +); + __PACKAGE__->has_many( "dependents", "Hydra::Schema::BuildInputs", @@ -273,6 +281,8 @@ QUERY $joinWithStatusChange QUERY ); + + makeSource("ActiveJobs$name", "(select distinct project, jobset, job from Builds where isCurrent = 1 $constraint)"); makeSource( "LatestSucceeded$name", diff --git a/src/lib/Hydra/Schema/Projects.pm b/src/lib/Hydra/Schema/Projects.pm index 808bbfed..45d4a133 100644 --- a/src/lib/Hydra/Schema/Projects.pm +++ b/src/lib/Hydra/Schema/Projects.pm @@ -97,6 +97,12 @@ __PACKAGE__->has_many( # Created by DBIx::Class::Schema::Loader v0.04999_09 @ 2009-11-17 16:05:10 # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:+HDJ8tIPcvj5+IwgHqTnaw +__PACKAGE__->has_many( + "jobsets", + "Hydra::Schema::Jobsets", + { "foreign.project" => "self.name" }, + { order_by => "name" }, +); # You can replace this text with custom content, and it will be preserved on regeneration 1; diff --git a/src/root/build.tt b/src/root/build.tt index ab448acb..b30faf0f 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -2,11 +2,69 @@ [% PROCESS common.tt %] [% PROCESS "product-list.tt" %] [% USE HTML %] +[% USE Date %] [% project = build.project %] [% jobset = build.jobset %] [% job = build.job %] +[% BLOCK renderBuildSteps %] + + <h2 id="buildsteps">Build steps</h2> + <table class="tablesorter"> + <thead> + <tr><th>Nr</th><th>What</th><th>Duration</th><th>Status</th></tr> + </thead> + <tbody> + [% FOREACH step IN build.buildsteps -%] + [% IF (onlyFailed != 1) || (step.status != 0) -%] + [% log = c.uri_for('/build' build.id 'nixlog' step.stepnr) %] + <tr class="[% IF step.logfile %]clickable[% END %]" + [% IF step.logfile %] onclick="window.location = '[% log %]'" [% END %]> + <td>[% step.stepnr %]</td> + <td> + [% IF step.type == 0 %] + Build of <tt>[% step.outpath %]</tt> + [% ELSE %] + Substitution of <tt>[% step.outpath %]</tt> + [% END %] + </td> + <td> + [% IF step.busy == 0 %] + [% INCLUDE renderDuration duration = step.stoptime - step.starttime %] + [% ELSE %] + [% IF build.finished %] + [% INCLUDE renderDuration duration = build.resultInfo.stoptime - step.starttime %] + [% ELSE %] + [% INCLUDE renderDuration duration = curTime - step.starttime %] + [% END %] + [% END %] + </td> + <td> + [% IF step.busy == 1 %] + [% IF build.finished %] + <span class="error">Aborted</span> + [% ELSE %] + <strong>Building</strong> + [% END %] + [% ELSIF step.status == 0 %] + Succeeded + [% ELSE %] + <span class="error">Failed: [% HTML.escape(step.errormsg) %]</span> + [% END %] + [% IF step.logfile %] + (<a href="[% log %]">log</a>, <a href="[% "$log/raw" %]">raw</a>, <a href="[% "$log/tail" %]">tail</a>) + [% END %] + </td> + </tr> + [% END %] + [% END %] + </tbody> + </table> + +[% END %] + + <h1> Job <tt>[% project.name %]:[% jobset.name %]:[% job.name %]</tt> build [% id %] [% IF !build.finished %] @@ -24,360 +82,349 @@ [% END %] -<h2>Information</h2> -<table class="layoutTable"> - <tr> - <th>Build ID:</th> - <td>[% build.id %]</td> - </tr> - <tr> - <th>Status:</th> - <td> - [% IF build.finished %] - [% IF build.resultInfo.buildstatus == 0 %] - <img src="/static/images/success.gif" alt="Succeeded" /> - <strong>Success</strong> - [% ELSIF build.resultInfo.buildstatus == 1 %] - <img src="/static/images/failure.gif" alt="Failed" /> - <span class="error">Build returned a non-zero exit code</span> - [% ELSIF build.resultInfo.buildstatus == 2 %] - <img src="/static/images/failure.gif" alt="Failed" /> - <span class="error">A dependency of the build failed</span> - [% ELSIF build.resultInfo.buildstatus == 4 %] - <img src="/static/images/failure.gif" alt="Failed" /> - <span class="error">Cancelled by user</span> - [% ELSIF build.resultInfo.buildstatus == 5 %] - <img src="/static/images/failure.gif" alt="Failed" /> - <span class="error">Build inhibited because a dependency previously failed to build</span> - [% failedDep = build.resultInfo.failedDep %] - (namely, <a href="[% c.uri_for('/build' failedDep.build.id 'nixlog' failedDep.stepnr) %]"><tt>[% failedDep.outpath %]</tt></a>) - [% ELSE %] - <img src="/static/images/failure.gif" alt="Failed" /> - <span class="error">Build failed</span> - (see <a href="#nix-error">below</a>) - [% END %] - [% IF c.user_exists && (build.resultInfo.buildstatus == 3 || build.resultInfo.buildstatus == 4) %] - <form action="[% c.uri_for('/build' build.id 'restart') %]" method="post" class="inline"> - <button id="restart" type="submit">Restart</button> - </form> - [% END %] - [% ELSIF build.schedulingInfo.busy %] - <strong>Build in progress</strong> - since [% INCLUDE renderDateTime timestamp = build.schedulingInfo.starttime %] - [% ELSE %] - <strong>Scheduled to be built</strong> - [% IF c.user_exists %] - <form action="[% c.uri_for('/build' build.id 'cancel') %]" method="post" class="inline"> - <button id="cancel" type="submit">Cancel</button> - </form> - [% END %] - [% END %] - </td> - </tr> - <tr> - <th>Project:</th> - <td>[% INCLUDE renderProjectName project=project.name %]</td> - </tr> - <tr> - <th>Jobset:</th> - <td>[% INCLUDE renderJobsetName project=project.name jobset=jobset.name %]</td> - </tr> - <tr> - <th>Job name:</th> - <td>[% INCLUDE renderJobName project=project.name jobset=jobset.name job=job.name %]</td> - </tr> - [% IF build.nixexprinput %] - <tr> - <th>Nix expression:</th> - <td>file <tt>[% HTML.escape(build.nixexprpath) %]</tt> in input <tt>[% HTML.escape(build.nixexprinput) %]</tt></td> - </tr> - [% END %] - <tr> - <th>Nix name:</th> - <td><tt>[% build.nixname %]</tt></td> - </tr> - [% IF build.resultInfo.releasename %] - <tr> - <th>Release name:</th> - <td><tt>[% HTML.escape(build.resultInfo.releasename) %]</tt></td> - </tr> - [% END %] - <tr> - <th>Short description:</th> - <td>[% IF build.description %][% HTML.escape(build.description) %][% ELSE %]<em>(not given)</em>[% END %]</td> - </tr> - <tr> - <th>Long description:</th> - <td>[% IF build.longdescription %][% HTML.escape(build.longdescription) %][% ELSE %]<em>(not given)</em>[% END %]</td> - </tr> - <tr> - <th>License:</th> - <td>[% IF build.license %][% HTML.escape(build.license) %][% ELSE %]<em>(not given)</em>[% END %]</td> - </tr> - <tr> - <th>Homepage:</th> - <td>[% IF build.homepage %]<a [% HTML.attributes(href => build.homepage) %]>[% HTML.escape(build.homepage) %]</a>[% ELSE %]<em>(not given)</em>[% END %]</td> - </tr> - <tr> - <th>Maintainer(s):</th> - <td>[% IF build.maintainers %]<tt>[% HTML.escape(build.maintainers) %]</tt>[% ELSE %]<em>(not given)</em>[% END %]</td> - </tr> - <tr> - <th>System:</th> - <td><tt>[% build.system %]</tt></td> - </tr> - <tr> - <th>Derivation store path:</th> - <td> - <tt>[% build.drvpath %]</tt> - [% IF drvAvailable %] - (build-time dependencies: <a href="[% c.uri_for('/build' build.id 'buildtime-deps') %]">graph</a> | <a href="[% c.uri_for('/build' build.id 'deps') %]#buildtime">list</a>) - [% END %] - </td> - </tr> - <tr> - <th>Output store path:</th> - <td> - <tt>[% build.outpath %]</tt> - [% IF available %] - (runtime dependencies: <a href="[% c.uri_for('/build' build.id 'runtime-deps') %]">graph</a> | <a href="[% c.uri_for('/build' build.id 'deps') %]#runtime">list</a>) - [% END %] - </td> - </tr> - <tr> - <th>Time added:</th> - <td>[% INCLUDE renderDateTime timestamp = build.timestamp %]</td> - </tr> - [% IF build.finished && build.resultInfo.buildstatus != 4 %] - [% IF build.resultInfo.iscachedbuild && cachedBuild %] - <tr> - <th>Cached build:</th> - <td><a href="[% c.uri_for('/build' cachedBuild.id ) %]">[% cachedBuild.id %]</a></td> - </tr> - [% END %] - - <tr> - <th>Build started:</th> - <td>[% IF build.resultInfo.starttime %][% INCLUDE renderDateTime timestamp = build.resultInfo.starttime %][% ELSE %]<em>(cached build)</em>[% END %]</td> - </tr> - <tr> - <th>Build finished:</th> - <td>[% IF build.resultInfo.stoptime %][% INCLUDE renderDateTime timestamp = build.resultInfo.stoptime %][% ELSE %]<em>(cached build)</em>[% END %]</td> - </tr> - <tr> - <th>Duration:</th> - <td> - [% IF build.resultInfo.iscachedbuild %] - <em>(cached build)</em> - [% ELSE %] - [% INCLUDE renderDuration duration = build.resultInfo.stoptime - build.resultInfo.starttime %] - [% END %] - </td> - </tr> - [% IF build.resultInfo.logfile %] - <tr> - <th>Logfile:</th> - <td> - <a href="[% c.uri_for('/build' build.id 'log') %]"><strong>Available</strong></a> - (<a href="[% c.uri_for('/build' build.id 'log' 'raw') %]">raw</a>, - <a href="[% c.uri_for('/build' build.id 'log' 'tail') %]">tail</a>) - </td> - </tr> - [% END %] - [% END %] - [% IF !build.finished %] - <tr> - <th>Priority:</th> - <td>[% build.schedulingInfo.priority %]</td> - </tr> - [% END %] - [% IF build.finished && build.buildproducts %] - <tr> - <th>Availability:</th> - <td> - [% IF !available %] - <em>Build output is no longer available</em> - [% ELSIF build.resultInfo.keep %] - <em>Build output will be kept permanently</em> - [% IF c.user_exists %] - <form action="[% c.uri_for('/build' build.id 'keep' 0) %]" method="post" class="inline"> - <button id="unkeep" type="submit">Unkeep</button> - </form> - [% END %] - [% ELSE %] - <em>Build output is available, but may be garbage-collected</em> - [% IF c.user_exists %] - <form action="[% c.uri_for('/build' build.id 'keep' 1) %]" method="post" class="inline"> - <button id="keep" type="submit">Keep</button> - </form> - [% END %] - [% END %] - </td> - </tr> - [% END %] -</table> +<div id="generic-tabs"> + <ul> + <li><a href="#tabs-summary">Summary</a></li> + <li><a href="#tabs-information">Information</a></li> + <li><a href="#tabs-buildinputs">Build Inputs</a></li> + [% IF build.buildsteps %]<li><a href="#tabs-buildsteps">Build Steps</a></li>[% END %] + [% IF build.dependents %]<li><a href="#tabs-usedby">Used by</a></li>[% END%] + </ul> + <div id="tabs-summary"> + <table class="statusTable"> + <tr> + <td> + [% INCLUDE renderBuildStatusIcon size=128, build=build %] + </td> + <td> + <table class="layoutTable"> + <tr> + <th>Build ID:</th> + <td>[% build.id %]</td> + </tr> + [% IF build.resultInfo.releasename %] + <tr> + <th>Release name:</th> + <td><tt>[% HTML.escape(build.resultInfo.releasename) %]</tt></td> + </tr> + [% ELSE %] + <tr> + <th>Nix name:</th> + <td><tt>[% build.nixname %]</tt></td> + </tr> + [% END %] + <tr> + <th>Status:</th> + <td> + [% INCLUDE renderStatus build=build %] + </td> + </tr> + <tr> + <th>System:</th> + <td><tt>[% build.system %]</tt></td> + </tr> + [% IF !build.schedulingInfo %] + <tr> + <th>Duration:</th> + <td> + [% IF build.resultInfo.iscachedbuild %] + (cached from [% INCLUDE renderFullBuildLink build=cachedBuild %]) + [% ELSE %] + [% INCLUDE renderDuration duration = build.resultInfo.stoptime - build.resultInfo.starttime %] + [% END %] + </td> + </tr> + [% END %] + [% IF build.resultInfo.logfile %] + <tr> + <th>Logfile:</th> + <td> + <a href="[% c.uri_for('/build' build.id 'log') %]"><strong>Available</strong></a> + (<a href="[% c.uri_for('/build' build.id 'log' 'raw') %]">raw</a>, + <a href="[% c.uri_for('/build' build.id 'log' 'tail') %]">tail</a>) + </td> + </tr> + [% END %] + </table> + </td> + </tr> + </table> + + [% IF c.user_exists && available %] + <form action="[% c.uri_for('/build' build.id 'add-to-release') %]" method="post"> + <p>Add to release: <input type="text" class="string" name="name" /> + <button type="submit"><img src="/static/images/success.gif" />Apply</button></p> + </form> + [% END %] + + [% IF c.user_exists %] + <p>[<a href="[% c.uri_for('/build' build.id 'clone') %]">Clone this build</a>]</p> + [% END %] -[% IF c.user_exists && available %] -<form action="[% c.uri_for('/build' build.id 'add-to-release') %]" method="post"> - <p>Add to release: <input type="text" class="string" name="name" /> - <button type="submit"><img src="/static/images/success.gif" />Apply</button></p> -</form> -[% END %] + [% IF build.buildproducts %] -[% IF c.user_exists %] -<p>[<a href="[% c.uri_for('/build' build.id 'clone') %]">Clone this build</a>]</p> -[% END %] + <h2>Build products</h2> + + [% IF !available %] + <p class="error">Note: this build is no longer available.</p> + [% END %] + + [% INCLUDE renderProductList latestRoot=['/job' build.project.name build.jobset.name build.job.name 'latest'] %] + + [% END %] -[% IF build.buildproducts %] -<h2>Build products</h2> + [% IF build.finished %] + [% IF build.buildsteps && (build.resultInfo.buildstatus == 2 || build.resultInfo.buildstatus == 5)%] + [% INCLUDE renderBuildSteps onlyFailed=1 %] + [% END %] -[% IF !available %] - <p class="error">Note: this build is no longer available.</p> -[% END %] + [% IF build.resultInfo.errormsg && build.resultInfo.buildstatus != 5 %] + + <h2 id="nix-error">Nix error output</h2> + + <pre class="buildlog"> + [% HTML.escape(build.resultInfo.errormsg) -%] + </pre> + [% END %] + [% ELSIF build.schedulingInfo.busy %] + <h2>Log</h2> + + <pre class="buildlog"> + [% HTML.escape(logtext) -%] + </pre> + [% END %] + </div> + <div id="tabs-information"> -[% INCLUDE renderProductList latestRoot=['/job' build.project.name build.jobset.name build.job.name 'latest'] %] - -[% END %] - - -<h2>Build inputs</h2> - -<table class="tablesorter"> - <thead> - <tr><th>Name</th><th>Type</th><th>Value</th><th>Revision</th><th>Store path</th></tr> - </thead> - <tbody> - [% FOREACH input IN build.inputs -%] - <tr> - <td><tt>[% input.name %]</tt></td> - <td><tt>[% type = input.type; inputTypes.$type %]</tt></td> - <td> - [% IF input.type == "build" || input.type == "sysbuild" %] - Job [% INCLUDE renderFullJobNameOfBuild build=input.dependency %] <a href="[% c.uri_for('/build' input.dependency.id) %]">build [% input.dependency.id %]</a> - [% ELSIF input.type == "string" || input.type == "boolean" %] - <tt>"[% input.value %]"</tt> - [% ELSE %] - <tt>[% input.uri %]</tt> - [% END %] - </td> - <td>[% IF input.revision %][% input.revision %][% END %]</td> - <td><tt>[% input.path %]</tt></td> - </tr> - [% END -%] - </tbody> -</table> + <h2>Information</h2> + + <table class="layoutTable"> + <tr> + <th>Build ID:</th> + <td>[% build.id %]</td> + </tr> + <tr> + <th>Status:</th> + <td> + [% INCLUDE renderStatus build=build %] + </td> + </tr> + <tr> + <th>Project:</th> + <td>[% INCLUDE renderProjectName project=project.name %]</td> + </tr> + <tr> + <th>Jobset:</th> + <td>[% INCLUDE renderJobsetName project=project.name jobset=jobset.name %]</td> + </tr> + <tr> + <th>Job name:</th> + <td>[% INCLUDE renderJobName project=project.name jobset=jobset.name job=job.name %]</td> + </tr> + [% IF build.nixexprinput %] + <tr> + <th>Nix expression:</th> + <td>file <tt>[% HTML.escape(build.nixexprpath) %]</tt> in input <tt>[% HTML.escape(build.nixexprinput) %]</tt></td> + </tr> + [% END %] + <tr> + <th>Nix name:</th> + <td><tt>[% build.nixname %]</tt></td> + </tr> + [% IF build.resultInfo.releasename %] + <tr> + <th>Release name:</th> + <td><tt>[% HTML.escape(build.resultInfo.releasename) %]</tt></td> + </tr> + [% END %] + <tr> + <th>Short description:</th> + <td>[% IF build.description %][% HTML.escape(build.description) %][% ELSE %]<em>(not given)</em>[% END %]</td> + </tr> + <tr> + <th>Long description:</th> + <td>[% IF build.longdescription %][% HTML.escape(build.longdescription) %][% ELSE %]<em>(not given)</em>[% END %]</td> + </tr> + <tr> + <th>License:</th> + <td>[% IF build.license %][% HTML.escape(build.license) %][% ELSE %]<em>(not given)</em>[% END %]</td> + </tr> + <tr> + <th>Homepage:</th> + <td>[% IF build.homepage %]<a [% HTML.attributes(href => build.homepage) %]>[% HTML.escape(build.homepage) %]</a>[% ELSE %]<em>(not given)</em>[% END %]</td> + </tr> + <tr> + <th>Maintainer(s):</th> + <td>[% IF build.maintainers %]<tt>[% HTML.escape(build.maintainers) %]</tt>[% ELSE %]<em>(not given)</em>[% END %]</td> + </tr> + <tr> + <th>System:</th> + <td><tt>[% build.system %]</tt></td> + </tr> + <tr> + <th>Derivation store path:</th> + <td> + <tt>[% build.drvpath %]</tt> + [% IF drvAvailable %] + (build-time dependencies: <a href="[% c.uri_for('/build' build.id 'buildtime-deps') %]">graph</a> | <a href="[% c.uri_for('/build' build.id 'deps') %]#buildtime">list</a>) + [% END %] + </td> + </tr> + <tr> + <th>Output store path:</th> + <td> + <tt>[% build.outpath %]</tt> + [% IF available %] + (runtime dependencies: <a href="[% c.uri_for('/build' build.id 'runtime-deps') %]">graph</a> | <a href="[% c.uri_for('/build' build.id 'deps') %]#runtime">list</a>) + [% END %] + </td> + </tr> + <tr> + <th>Time added:</th> + <td>[% INCLUDE renderDateTime timestamp = build.timestamp %]</td> + </tr> + [% IF build.finished && build.resultInfo.buildstatus != 4 %] + [% IF build.resultInfo.iscachedbuild && cachedBuild %] + <tr> + <th>Cached build:</th> + <td>[% INCLUDE renderFullBuildLink build=cachedBuild %]</td> + </tr> + [% END %] + + <tr> + <th>Build started:</th> + <td>[% IF build.resultInfo.starttime %][% INCLUDE renderDateTime timestamp = build.resultInfo.starttime %][% ELSE %]<em>(cached build)</em>[% END %]</td> + </tr> + <tr> + <th>Build finished:</th> + <td>[% IF build.resultInfo.stoptime %][% INCLUDE renderDateTime timestamp = build.resultInfo.stoptime %][% ELSE %]<em>(cached build)</em>[% END %]</td> + </tr> + <tr> + <th>Duration:</th> + <td> + [% IF build.resultInfo.iscachedbuild %] + <em>(cached build)</em> + [% ELSE %] + [% INCLUDE renderDuration duration = build.resultInfo.stoptime - build.resultInfo.starttime %] + [% END %] + </td> + </tr> + [% IF build.resultInfo.logfile %] + <tr> + <th>Logfile:</th> + <td> + <a href="[% c.uri_for('/build' build.id 'log') %]"><strong>Available</strong></a> + (<a href="[% c.uri_for('/build' build.id 'log' 'raw') %]">raw</a>, + <a href="[% c.uri_for('/build' build.id 'log' 'tail') %]">tail</a>) + </td> + </tr> + [% END %] + [% END %] + [% IF !build.finished %] + <tr> + <th>Priority:</th> + <td>[% build.schedulingInfo.priority %]</td> + </tr> + [% END %] + [% IF build.finished && build.buildproducts %] + <tr> + <th>Availability:</th> + <td> + [% IF !available %] + <em>Build output is no longer available</em> + [% ELSIF build.resultInfo.keep %] + <em>Build output will be kept permanently</em> + [% IF c.user_exists %] + <form action="[% c.uri_for('/build' build.id 'keep' 0) %]" method="post" class="inline"> + <button id="unkeep" type="submit">Unkeep</button> + </form> + [% END %] + [% ELSE %] + <em>Build output is available, but may be garbage-collected</em> + [% IF c.user_exists %] + <form action="[% c.uri_for('/build' build.id 'keep' 1) %]" method="post" class="inline"> + <button id="keep" type="submit">Keep</button> + </form> + [% END %] + [% END %] + </td> + </tr> + [% END %] + </table> + </div> + <div id="tabs-buildinputs"> + + <h2>Build inputs</h2> + + <table class="tablesorter"> + <thead> + <tr><th>Name</th><th>Type</th><th>Value</th><th>Revision</th><th>Store path</th></tr> + </thead> + <tbody> + [% FOREACH input IN build.inputs -%] + <tr> + <td><tt>[% input.name %]</tt></td> + <td><tt>[% type = input.type; inputTypes.$type %]</tt></td> + <td> + [% IF input.type == "build" || input.type == "sysbuild" %] + [% INCLUDE renderFullBuildLink build=input.dependency %]</a> + [% ELSIF input.type == "string" || input.type == "boolean" %] + <tt>"[% input.value %]"</tt> + [% ELSE %] + <tt>[% input.uri %]</tt> + [% END %] + </td> + <td>[% IF input.revision %][% input.revision %][% END %]</td> + <td><tt>[% input.path %]</tt></td> + </tr> + [% END -%] + </tbody> + </table> + </div> [% IF build.buildsteps %] - -<h2 id="buildsteps">Build steps</h2> - -<table class="tablesorter"> - <thead> - <tr><th>Nr</th><th>What</th><th>Duration</th><th>Status</th></tr> - </thead> - <tbody> - [% FOREACH step IN build.buildsteps -%] - [% log = c.uri_for('/build' build.id 'nixlog' step.stepnr) %] - <tr class="[% IF step.logfile %]clickable[% END %]" - [% IF step.logfile %] onclick="window.location = '[% log %]'" [% END %]> - <td>[% step.stepnr %]</td> - <td> - [% IF step.type == 0 %] - Build of <tt>[% step.outpath %]</tt> - [% ELSE %] - Substitution of <tt>[% step.outpath %]</tt> - [% END %] - </td> - <td> - [% IF step.busy == 0 %] - [% INCLUDE renderDuration duration = step.stoptime - step.starttime %] - [% ELSE %] - [% IF build.finished %] - [% INCLUDE renderDuration duration = build.resultInfo.stoptime - step.starttime %] - [% ELSE %] - [% INCLUDE renderDuration duration = curTime - step.starttime %] - [% END %] - [% END %] - </td> - <td> - [% IF step.busy == 1 %] - [% IF build.finished %] - <span class="error">Aborted</span> - [% ELSE %] - <strong>Building</strong> - [% END %] - [% ELSIF step.status == 0 %] - Succeeded - [% ELSE %] - <span class="error">Failed: [% HTML.escape(step.errormsg) %]</span> - [% END %] - [% IF step.logfile %] - (<a href="[% log %]">log</a>, <a href="[% "$log/raw" %]">raw</a>, <a href="[% "$log/tail" %]">tail</a>) - [% END %] - </td> - </tr> - [% END %] - </tbody> -</table> - + <div id="tabs-buildsteps"> + [% INCLUDE renderBuildSteps onlyFailed=0 %] + </div> [% END %] - - -[% IF build.finished %] - - -[% IF build.resultInfo.errormsg && build.resultInfo.buildstatus != 5 %] - -<h2 id="nix-error">Nix error output</h2> - -<pre class="buildlog"> -[% HTML.escape(build.resultInfo.errormsg) -%] -</pre> - -[% END %] - + [% IF build.dependents %] + <div id="tabs-usedby"> -<h2>Used by</h2> - -<p>The following builds have used this build as an input:</p> - -<table class="tablesorter"> - <thead> - <tr><th>Build</th><th>Input name</th><th>System</th><th>Timestamp</th></tr> - </thead> - <tbody> - [% FOREACH input IN build.dependents -%] - <tr> - <td>Job [% INCLUDE renderFullJobNameOfBuild build=input.build %] <a href="[% c.uri_for('/build' input.build.id) %]">build [% input.build.id %]</a></td> - <td><tt>[% input.name %]</tt></td> - <td><tt>[% input.build.system %]</tt></td> - <td>[% INCLUDE renderDateTime timestamp = input.build.timestamp %]</td> - </tr> - [% END -%] - </tbody> -</table> - + <h2>Used by</h2> + + <p>The following builds have used this build as an input:</p> + + <table class="tablesorter"> + <thead> + <tr><th>Build</th><th>Input name</th><th>System</th><th>Timestamp</th></tr> + </thead> + <tbody> + [% FOREACH input IN build.dependents -%] + <tr> + <td>[% INCLUDE renderFullBuildLink build=input.build %]</td> + <td><tt>[% input.name %]</tt></td> + <td><tt>[% input.build.system %]</tt></td> + <td>[% INCLUDE renderDateTime timestamp = input.build.timestamp %]</td> + </tr> + [% END -%] + </tbody> + </table> + </div> [% END %] +</div> -[% ELSIF build.schedulingInfo.busy %] +<script type="text/javascript"> + $("#generic-tabs").tabs(); +</script> -<h2>Log</h2> - -<pre class="buildlog"> -[% HTML.escape(logtext) -%] -</pre> - - -[% END %] - [% END %] diff --git a/src/root/common.tt b/src/root/common.tt index 1be6c0fd..a1dc377e 100644 --- a/src/root/common.tt +++ b/src/root/common.tt @@ -196,3 +196,72 @@ HTML.escape(value); END -%] [% END -%] + +[% BLOCK renderFullBuildLink; %] + Job [% INCLUDE renderFullJobNameOfBuild build=build %] <a href="[% c.uri_for('/build' build.id) %]">build [% build.id %] +[% END %] + +[% BLOCK renderBuildStatusIcon; %] + [% IF build.finished %] + [% IF build.resultInfo.buildstatus == 0 %] + <img src="/static/images/checkmark_[% size %].png" alt="Succeeded" /> + [% ELSIF build.resultInfo.buildstatus == 1 %] + <img src="/static/images/error_[% size %].png" alt="Failed" /> + [% ELSIF build.resultInfo.buildstatus == 2 %] + <img src="/static/images/error_[% size %].png" alt="Failed" /> + [% ELSIF build.resultInfo.buildstatus == 4 %] + <img src="/static/images/error_[% size %].png" alt="Failed" /> + [% ELSIF build.resultInfo.buildstatus == 5 %] + <img src="/static/images/error_[% size %].png" alt="Failed" /> + [% ELSE %] + <img src="/static/images/error_[% size %].png" alt="Failed" /> + [% END %] + [% ELSIF build.schedulingInfo.busy %] + <img src="/static/images/help_[% size %].png" alt="Budy" /> + [% ELSE %] + <img src="/static/images/help_[% size %].png" alt="Scheduled" /> + [% END %] +[% END %] + +[% BLOCK renderStatus; %] + [% IF build.finished %] + [% IF build.resultInfo.buildstatus == 0 %] + <img src="/static/images/success.gif" alt="Succeeded" /> + <strong>Success</strong> + [% ELSIF build.resultInfo.buildstatus == 1 %] + <img src="/static/images/failure.gif" alt="Failed" /> + <span class="error">Build returned a non-zero exit code</span> + [% ELSIF build.resultInfo.buildstatus == 2 %] + <img src="/static/images/failure.gif" alt="Failed" /> + <span class="error">A dependency of the build failed</span> + [% ELSIF build.resultInfo.buildstatus == 4 %] + <img src="/static/images/failure.gif" alt="Failed" /> + <span class="error">Cancelled by user</span> + [% ELSIF build.resultInfo.buildstatus == 5 %] + <img src="/static/images/failure.gif" alt="Failed" /> + <span class="error">Build inhibited because a dependency previously failed to build</span> + [% failedDep = build.resultInfo.failedDep %] + (namely, <a href="[% c.uri_for('/build' failedDep.build.id 'nixlog' failedDep.stepnr) %]"><tt>[% failedDep.outpath %]</tt></a>) + [% ELSE %] + <img src="/static/images/failure.gif" alt="Failed" /> + <span class="error">Build failed</span> + (see <a href="#nix-error">below</a>) + [% END %] + [% IF c.user_exists && (build.resultInfo.buildstatus == 3 || build.resultInfo.buildstatus == 4) %] + <form action="[% c.uri_for('/build' build.id 'restart') %]" method="post" class="inline"> + <button id="restart" type="submit">Restart</button> + </form> + [% END %] + [% ELSIF build.schedulingInfo.busy %] + <strong>Build in progress</strong> + since [% INCLUDE renderDateTime timestamp = build.schedulingInfo.starttime %] + [% ELSE %] + <strong>Scheduled to be built</strong> + [% IF c.user_exists %] + <form action="[% c.uri_for('/build' build.id 'cancel') %]" method="post" class="inline"> + <button id="cancel" type="submit">Cancel</button> + </form> + [% END %] + [% END %] +[% END -%] + diff --git a/src/root/job.tt b/src/root/job.tt index a34179d7..4e2b237e 100644 --- a/src/root/job.tt +++ b/src/root/job.tt @@ -9,44 +9,55 @@ title = jobset.name %]:[% job.name %]</tt></h1> -<h2>Status</h2> - -[% INCLUDE renderBuildList builds=currentBuilds showStatusChange=0 %] - -<h2>Channels</h2> - -<p>This job provides the following Nix channels:</p> - -<ul> - <li> - <a href="[% c.uri_for('/job' project.name jobset.name job.name - 'channel' 'latest') %]"><tt>latest</tt></a> — contains the latest - successful build for each platform. - </li> - <li> - <a href="[% c.uri_for('/job' project.name jobset.name job.name - 'channel' 'all') %]"><tt>all</tt></a> — contains every successful - build of this job. - </li> -</ul> - - -<h2>Latest builds</h2> - -<ul> - <li><a href="[% c.uri_for('/job' project.name jobset.name job.name - 'latest') %]">Latest successful build.</a></li> - [% FOREACH system IN systems %] - <li><a href="[% c.uri_for('/job' project.name jobset.name job.name - 'latest-for' system.system) %]">Latest successful build for <tt>[% - system.system %]</tt>.</a></li> - [% END %] -</ul> - - -<h2>Statistics</h2> - -[% INCLUDE showBuildStats %] +<div id="generic-tabs"> + <ul> + <li><a href="#tabs-status">Status</a></li> + <li><a href="#tabs-channels">Channels</a></li> + <li><a href="#tabs-latestbuilds">Latest builds</a></li> + <li><a href="#tabs-statistics">Statistics</a></li> + </ul> + <div id="tabs-status"> + <h2>Finished builds</h2> + [% INCLUDE renderBuildList builds=currentBuilds showStatusChange=0 %] + [% IF runningBuilds %] + <h2>Running builds</h2> + [% INCLUDE renderBuildList builds=runningBuilds showStatusChange=0 %] + [% END %] + </div> + <div id="tabs-channels"> + <p>This job provides the following Nix channels:</p> + + <ul> + <li> + <a href="[% c.uri_for('/job' project.name jobset.name job.name + 'channel' 'latest') %]"><tt>latest</tt></a> — contains the latest + successful build for each platform. + </li> + <li> + <a href="[% c.uri_for('/job' project.name jobset.name job.name + 'channel' 'all') %]"><tt>all</tt></a> — contains every successful + build of this job. + </li> + </ul> + </div> + <div id="tabs-latestbuilds"> + <ul> + <li><a href="[% c.uri_for('/job' project.name jobset.name job.name + 'latest') %]">Latest successful build.</a></li> + [% FOREACH system IN systems %] + <li><a href="[% c.uri_for('/job' project.name jobset.name job.name + 'latest-for' system.system) %]">Latest successful build for <tt>[% + system.system %]</tt>.</a></li> + [% END %] + </ul> + </div> + <div id="tabs-statistics"> + [% INCLUDE showBuildStats %] + </div> +</div> +<script type="text/javascript"> + $("#generic-tabs").tabs(); +</script> [% END %] diff --git a/src/root/jobset.tt b/src/root/jobset.tt index 341c0a32..8362533a 100644 --- a/src/root/jobset.tt +++ b/src/root/jobset.tt @@ -53,130 +53,203 @@ [% END %] -<h2>Information[% IF !edit %] <a class="smallLink" href="[% c.uri_for('/jobset' project.name jobset.name 'edit') %]">[Edit]</a>[% END %]</h2> - -<table class="layoutTable"> - [% IF edit %] - <tr> - <th>Identifier:</th> - <td>[% INCLUDE maybeEditString param="name" value=jobset.name %]</td> - </tr> - [% END %] - <tr> - <th>Description:</th> - <td>[% INCLUDE maybeEditString param="description" value=jobset.description %]</td> - </tr> - <tr> - <th>Nix expression:</th> - <td> - <tt>[% INCLUDE maybeEditString param="nixexprpath" value=jobset.nixexprpath extraClass="shortString" %]</tt> in input - <tt>[% INCLUDE maybeEditString param="nixexprinput" value=jobset.nixexprinput extraClass="shortString" %]</tt> - </td> - </tr> - <tr> - <th>Enabled:</th> - <td> - [% INCLUDE renderSelection param="enabled" curValue=jobset.enabled options={"1" = "Yes", "0" = "No"} %] - </td> - </tr> - <tr> - <th>Enable email notification:</th> - <td> - [% INCLUDE renderSelection param="enableemail" curValue=jobset.enableemail options={"1" = "Yes", "0" = "No"} %] - </td> - </tr> - <tr> - <th>Email override:</th> - <td> - [% INCLUDE maybeEditString param="emailoverride" value=jobset.emailoverride %] - </td> - </tr> - [% IF !edit %] - <tr> - <th>Last checked:</th> - <td> - [% IF jobset.lastcheckedtime %] - [% INCLUDE renderDateTime timestamp = jobset.lastcheckedtime -%][% IF jobset.errormsg -%]<em>, evaluation error</em>: - <pre class="multiLineMsg error">[% HTML.escape(jobset.errormsg) %]</pre> - [% ELSE %], <em>no errors</em> - [% END %] - [% ELSE %] - <em>never</em> - [% END %] - </td> - </tr> - [% END %] -</table> - - -<h3>Inputs</h3> - -<table class="tablesorter"> - <thead> - <tr><th>Input name</th><th>Type</th><th>Values</th></tr> - </thead> - <tbody class="inputs"> - [% FOREACH input IN jobset.jobsetinputs -%] - [% INCLUDE renderInput input=input baseName="input-$input.name" %] - [% END %] - [% IF edit %] - <tr> - <td colspan="3"><button type="button" class="add-input">Add a new input</button></td> - </tr> - [% END %] - </tbody> -</table> - - -<h2>Channels</h2> - -<p>This jobset provides the following Nix channels:</p> - -<ul> - <li> - <a href="[% c.uri_for('/jobset' project.name jobset.name 'channel' - 'latest') %]"><tt>latest</tt></a> — contains the latest successful - build of every job in this jobset. - </li> - <li> - <a href="[% c.uri_for('/jobset' project.name jobset.name 'channel' - 'all') %]"><tt>all</tt></a> — contains every successful, - non-garbage-collected build of every job in this project. - </li> -</ul> - - -[% IF !edit %] - - -<h2>Jobs</h2> - -<p>This jobset currently contains the following [% activeJobs.size %] jobs: - - <blockquote> - [% IF activeJobs.size == 0 %]<em>(none)</em>[% END %] - [% FOREACH j IN activeJobs %] [% INCLUDE renderJobName project=project.name jobset=jobset.name job=j.get_column('job') %] [% END %] - </blockquote> -</p> - -<p>This jobset used to contain the following [% inactiveJobs.size %] jobs: - - <blockquote> - [% IF inactiveJobs.size == 0 %]<em>(none)</em>[% END %] - [% FOREACH j IN inactiveJobs %] [% INCLUDE renderJobName project=project.name jobset=jobset.name job=j.get_column('job') %] [% END %] - </blockquote> - -</p> - - -<h2>Statistics</h2> - -[% INCLUDE showBuildStats %] - +[% BLOCK renderInputs %] + <h3>Inputs</h3> + + <table class="tablesorter"> + <thead> + <tr><th>Input name</th><th>Type</th><th>Values</th></tr> + </thead> + <tbody class="inputs"> + [% FOREACH input IN jobset.jobsetinputs -%] + [% INCLUDE renderInput input=input baseName="input-$input.name" %] + [% END %] + [% IF edit %] + <tr> + <td colspan="3"><button type="button" class="add-input">Add a new input</button></td> + </tr> + [% END %] + </tbody> + </table> [% END %] +<div id="generic-tabs"> + <ul> + [% IF !edit -%] + <li><a href="#tabs-information">Jobset</a></li> + [% IF jobset.errormsg -%]<li><a href="#tabs-errors">Errors</a></li>[% END %] + <li><a href="#tabs-jobs">Jobs ([% activeJobs.size %])</a></li> + [% END %] + <li><a href="#tabs-setup">Setup</a></li> + [% IF !edit -%] + <li><a href="#tabs-channels">Channels</a></li> + <li><a href="#tabs-statistics">Statistics</a></li> + [% END %] + </ul> + <div id="tabs-information"> + [% IF lastBuilds %] + <h2>Most recent builds</h2> + [% INCLUDE renderBuildList builds=lastBuilds %] + [% END %] + + [% IF !edit && activeJobsStatus -%] + <h2>Status</h2> + <table class="activeJobsStatus"> + <thead><tr><th>Job</th>[% FOREACH s IN systems %]<th>[% s.system %]</th>[% END %]</tr></thead> + <tbody> + [% odd = 0 %] + [% FOREACH j IN activeJobsStatus %] + <tr class="[% IF odd %] odd [% END; odd = !odd -%]"> + <td>[% INCLUDE renderJobName project=project.name jobset = jobset.name job = j.get_column('job') %]</td> + [% FOREACH s IN systems %] + [% system = s.system %] + [% systemStatus = j.get_column(system) %] + <td> + [% IF systemStatus != undef %] + <a href="[% c.uri_for('/build' j.get_column(system _ '-build') ) %]"> + [% IF systemStatus == 0 %] + <img src="/static/images/success.gif" alt="Succeeded" /> + [% ELSE %] + <img src="/static/images/failure.gif" alt="Failed" /> + [% END %] + </a> + [% END %] + </td> + [% END %] + </tr> + [% END %] + </tbody> + </table> + [% END %] + + </div> + [% IF !edit -%] + + [% IF jobset.errormsg -%] + <div id="tabs-errors"> + <h3>Evaluation error</h3> + <pre class="multiLineMsg error">[% HTML.escape(jobset.errormsg) %]</pre> + </div> + [% END %] + [% END %] + <div id="tabs-setup"> + <h2>Information[% IF !edit %] <a class="smallLink" href="[% c.uri_for('/jobset' project.name jobset.name 'edit') %]">[Edit]</a>[% END %]</h2> + + <table class="layoutTable"> + [% IF edit %] + <tr> + <th>Identifier:</th> + <td>[% INCLUDE maybeEditString param="name" value=jobset.name %]</td> + </tr> + [% END %] + <tr> + <th>Description:</th> + <td>[% INCLUDE maybeEditString param="description" value=jobset.description %]</td> + </tr> + <tr> + <th>Nix expression:</th> + <td> + <tt>[% INCLUDE maybeEditString param="nixexprpath" value=jobset.nixexprpath extraClass="shortString" %]</tt> in input + <tt>[% INCLUDE maybeEditString param="nixexprinput" value=jobset.nixexprinput extraClass="shortString" %]</tt> + </td> + </tr> + <tr> + <th>Enabled:</th> + <td> + [% INCLUDE renderSelection param="enabled" curValue=jobset.enabled options={"1" = "Yes", "0" = "No"} %] + </td> + </tr> + <tr> + <th>Enable email notification:</th> + <td> + [% INCLUDE renderSelection param="enableemail" curValue=jobset.enableemail options={"1" = "Yes", "0" = "No"} %] + </td> + </tr> + <tr> + <th>Email override:</th> + <td> + [% INCLUDE maybeEditString param="emailoverride" value=jobset.emailoverride %] + </td> + </tr> + [% IF !edit %] + <tr> + <th>Last checked:</th> + <td> + [% IF jobset.lastcheckedtime %] + [% INCLUDE renderDateTime timestamp = jobset.lastcheckedtime -%][% IF jobset.errormsg -%]<em>, with errors!</em> + [% ELSE %], <em>no errors</em> + [% END %] + [% ELSE %] + <em>never</em> + [% END %] + </td> + </tr> + [% END %] + </table> + + + [% INCLUDE renderInputs %] + </div> + [% IF !edit -%] + <div id="tabs-channels"> + + <h2>Channels</h2> + + <p>This jobset provides the following Nix channels:</p> + + <ul> + <li> + <a href="[% c.uri_for('/jobset' project.name jobset.name 'channel' + 'latest') %]"><tt>latest</tt></a> — contains the latest successful + build of every job in this jobset. + </li> + <li> + <a href="[% c.uri_for('/jobset' project.name jobset.name 'channel' + 'all') %]"><tt>all</tt></a> — contains every successful, + non-garbage-collected build of every job in this project. + </li> + </ul> + + </div> + + <div id="tabs-jobs"> + + <h2>Jobs</h2> + + <p>This jobset currently contains the following [% activeJobs.size %] jobs: + + <blockquote> + [% IF activeJobs.size == 0 %]<em>(none)</em>[% END %] + [% FOREACH j IN activeJobs %] [% INCLUDE renderJobName project=project.name jobset=jobset.name job=j.get_column('job') %] [% END %] + </blockquote> + </p> + + <p>This jobset used to contain the following [% inactiveJobs.size %] jobs: + + <blockquote> + [% IF inactiveJobs.size == 0 %]<em>(none)</em>[% END %] + [% FOREACH j IN inactiveJobs %] [% INCLUDE renderJobName project=project.name jobset=jobset.name job=j.get_column('job') %] [% END %] + </blockquote> + + </p> + + </div> + <div id="tabs-statistics"> + + <h2>Statistics</h2> + + [% INCLUDE showBuildStats %] + </div> + [% END %] + +</div> + +<script type="text/javascript"> + $("#generic-tabs").tabs(); +</script> + [% IF edit %] <table class="template"> <!-- dummy wrapper needed because “hidden” trs are visible anyway --> diff --git a/src/root/layout.tt b/src/root/layout.tt index a729d164..6d6b6280 100644 --- a/src/root/layout.tt +++ b/src/root/layout.tt @@ -10,11 +10,11 @@ <head> <title>Hydra - [% title %]</title> + <link type="text/css" href="/static/js/jquery/css/smoothness/jquery-ui-1.7.2.custom.css" rel="Stylesheet" /> <link rel="stylesheet" href="/static/css/hydra.css" type="text/css" /> <link rel="stylesheet" href="/static/css/nix-common.css" type="text/css" /> <link rel="stylesheet" href="/static/css/nixos-site.css" type="text/css" /> <link rel="stylesheet" href="/static/css/logfile.css" type="text/css" /> - <link type="text/css" href="/static/js/jquery/css/smoothness/jquery-ui-1.7.2.custom.css" rel="Stylesheet" /> <script type="text/javascript" src="/static/js/jquery/js/jquery-1.3.2.min.js"></script> <script type="text/javascript" src="/static/js/jquery/js/jquery-ui-1.7.2.custom.min.js"></script> <script type="text/javascript" src="/static/js/tablesorter/jquery.tablesorter.js"></script> diff --git a/src/root/overview.tt b/src/root/overview.tt index 873427eb..24a7db27 100644 --- a/src/root/overview.tt +++ b/src/root/overview.tt @@ -1,15 +1,6 @@ [% WRAPPER layout.tt title="Overview" %] [% PROCESS common.tt %] - -<h1>Hydra Overview</h1> - -<p>Welcome to Hydra, the <a href="http://nixos.org/">Nix-based</a> -continuous build system. Hydra continuously builds, tests and -releases software packages from source repositories. <a -href="http://nixos.org/hydra"><em>[Read more...]</em></a></p> - - <h2>Projects</h2> <p>The following projects are hosted on this server:</p> diff --git a/src/root/project.tt b/src/root/project.tt index 2b36cecf..ebbefcb5 100644 --- a/src/root/project.tt +++ b/src/root/project.tt @@ -1,173 +1,194 @@ [% WRAPPER layout.tt title=(edit ? (create ? "New Project" : "Editing Project ‘$project.name’") : "Project ‘$project.name’") %] [% PROCESS common.tt %] - -[% IF edit %] - <form action="[% IF create %][% c.uri_for('/create-project/submit') %][% ELSE %][% c.uri_for('/project' project.name 'submit') %][% END %]" method="post"> -[% END %] - - [% IF create %] <h1>New Project</h1> [% ELSE %] <h1>Project <tt>[% project.name %]</tt></h1> [% END %] +<div id="generic-tabs"> + <ul> + [% IF !edit %] + <li><a href="#tabs-project">Project</a></li> + [% END %] + <li><a href="#tabs-settings">Settings</a></li> + [% IF !edit %] + <li><a href="#tabs-views">Views</a></li> + <li><a href="#tabs-channels">Channels</a></li> + <li><a href="#tabs-statistics">Statistics</a></li> + [% END %] + </ul> -<h2>Information[% IF !edit %] <a class="smallLink" href="[% c.uri_for('/project' project.name 'edit') %]">[Edit]</a>[% END %]</h2> + [% IF !edit %] + <div id="tabs-project"> + <h2>Jobsets</h2> + + [% IF project.jobsets.size > 0 %] + + <p>This project has the following jobsets:</p> + + <table class="tablesorter"> + <thead> + <tr> + <th>Id</th> + <th>Description</th> + <th>Last evaluated</th> + </tr> + </thead> + <tbody> + [% FOREACH j IN project.jobsets %] + <tr class="clickable [% IF odd %] odd [% END; odd = !odd %]" + onclick="window.location = '[% c.uri_for('/jobset' project.name j.name) %]'"> + <td>[% INCLUDE renderJobsetName project = project.name jobset = j.name %]</td> + <td>[% HTML.escape(j.description) %]</td> + <td>[% INCLUDE renderDateTime timestamp = j.lastcheckedtime %]</td> + </tr> + [% END %] + </tbody> + </table> + + [% ELSE %] + + <p>No jobsets have been defined yet.</p> + + [% END %] + + <p><a href="[% c.uri_for(c.controller('Project').action_for('create_jobset'), [project.name]) %]">[Create a new jobset]</a></p> + + </div> + [% END %] + <div id="tabs-settings"> + [% IF edit %] + <form action="[% IF create %][% c.uri_for('/create-project/submit') %][% ELSE %][% c.uri_for('/project' project.name 'submit') %][% END %]" method="post"> + [% END %] + + + <h2>Information[% IF !edit %] <a class="smallLink" href="[% c.uri_for('/project' project.name 'edit') %]">[Edit]</a>[% END %]</h2> + + <table class="layoutTable"> + [% IF edit %] + <tr> + <th>Identifier:</th> + <td><tt>[% INCLUDE maybeEditString param="name" value=project.name %]</tt></td> + </tr> + [% END %] + <tr> + <th>Display name:</th> + <td>[% INCLUDE maybeEditString param="displayname" value=project.displayname %]</td> + </tr> + <tr> + <th>Description:</th> + <td>[% INCLUDE maybeEditString param="description" value=project.description %]</td> + </tr> + <tr> + <th>Homepage:</th> + <td> + [% IF edit %] + [% INCLUDE maybeEditString param="homepage" value=project.homepage %] + [% ELSE %] + [% IF project.homepage %] + <a [% HTML.attributes(href => project.homepage) %]>[% HTML.escape(project.homepage) %]</a> + [% ELSE %] + <em>(not specified)</em> + [% END %] + [% END %] + </td> + </tr> + <tr> + <th>Owner:</th> + <td><tt>[% INCLUDE maybeEditString param="owner" value=(project.owner.username || c.user.username) edit=(edit && c.check_user_roles('admin')) %]</tt></td> + </tr> + <tr> + <th>Enabled:</th> + <td> + [% INCLUDE renderSelection param="enabled" curValue=project.enabled options={"1" = "Yes", "0" = "No"} %] + </td> + </tr> + </table> + + [% IF edit %] + + <p><button type="submit"><img src="/static/images/success.gif" />[%IF create %]Create[% ELSE %]Apply changes[% END %]</button></p> + + </form> + + [% IF !create %] + + <form action="[% c.uri_for('/project' project.name 'delete') %]" method="post"> + <p><button id="delete-project" type="submit"><img src="/static/images/failure.gif" />Delete this project</button></p> + </form> + + <script type="text/javascript"> + $("#delete-project").click(function() { + return confirm("Are you sure you want to delete this project?"); + }); + </script> + + [% END %] + + [% END %] + + </div> -<table class="layoutTable"> - [% IF edit %] - <tr> - <th>Identifier:</th> - <td><tt>[% INCLUDE maybeEditString param="name" value=project.name %]</tt></td> - </tr> - [% END %] - <tr> - <th>Display name:</th> - <td>[% INCLUDE maybeEditString param="displayname" value=project.displayname %]</td> - </tr> - <tr> - <th>Description:</th> - <td>[% INCLUDE maybeEditString param="description" value=project.description %]</td> - </tr> - <tr> - <th>Homepage:</th> - <td> - [% IF edit %] - [% INCLUDE maybeEditString param="homepage" value=project.homepage %] - [% ELSE %] - [% IF project.homepage %] - <a [% HTML.attributes(href => project.homepage) %]>[% HTML.escape(project.homepage) %]</a> - [% ELSE %] - <em>(not specified)</em> - [% END %] - [% END %] - </td> - </tr> - <tr> - <th>Owner:</th> - <td><tt>[% INCLUDE maybeEditString param="owner" value=(project.owner.username || c.user.username) edit=(edit && c.check_user_roles('admin')) %]</tt></td> - </tr> - <tr> - <th>Enabled:</th> - <td> - [% INCLUDE renderSelection param="enabled" curValue=project.enabled options={"1" = "Yes", "0" = "No"} %] - </td> - </tr> -</table> + [% IF !edit %] + <div id="tabs-views"> + + <h2>Views</h2> + + [% IF views.size > 0 %] + + <p>Project <tt>[% project.name %]</tt> has the following views:</p> + + <ul> + [% FOREACH view IN views %] + <li> + <a href="[% c.uri_for('/view' project.name view.name) %]"><tt>[% view.name %]</tt></a> + [<a href="[% c.uri_for('/view' project.name view.name "edit") %]">Edit</a>] + </li> + [% END %] + </ul> + + [% ELSE %] + + <p>Project <tt>[% project.name %]</tt> has no views.</p> + + [% END %] + + <p><a href="[% c.uri_for('/project' project.name 'create-view') %]">[Create a new view]</a></p> + + </div> + <div id="tabs-channels"> + + <h2>Channels</h2> + + <p>This project provides the following Nix channels:</p> + + <ul> + <li> + <a href="[% c.uri_for('/project' project.name 'channel' 'latest') %]"><tt>latest</tt></a> — + contains the latest successful build of every job in this project. + </li> + <li> + <a href="[% c.uri_for('/project' project.name 'channel' 'all') %]"><tt>all</tt></a> — + contains every successful, non-garbage-collected build of every + job in this project. + </li> + </ul> + + </div> + <div id="tabs-statistics"> + <h2>Statistics</h2> + + [% INCLUDE showBuildStats %] + + </div> + [% END %] +</div> - - -[% IF !edit %] - - -<h2>Jobsets</h2> - -[% IF project.jobsets.size > 0 %] - -<p>This project has the following jobsets:</p> - -<table class="tablesorter"> - <thead> - <tr> - <th>Id</th> - <th>Description</th> - <th>Last evaluated</th> - </tr> - </thead> - <tbody> - [% FOREACH j IN project.jobsets %] - <tr class="clickable [% IF odd %] odd [% END; odd = !odd %]" - onclick="window.location = '[% c.uri_for('/jobset' project.name j.name) %]'"> - <td>[% INCLUDE renderJobsetName project = project.name jobset = j.name %]</td> - <td>[% HTML.escape(j.description) %]</td> - <td>[% INCLUDE renderDateTime timestamp = j.lastcheckedtime %]</td> - </tr> - [% END %] - </tbody> -</table> - - -[% ELSE %] - -<p>No jobsets have been defined yet.</p> - -[% END %] - -<p><a href="[% c.uri_for(c.controller('Project').action_for('create_jobset'), [project.name]) %]">[Create a new jobset]</a></p> - - -<h2>Views</h2> - -[% IF views.size > 0 %] - -<p>Project <tt>[% project.name %]</tt> has the following views:</p> - -<ul> - [% FOREACH view IN views %] - <li> - <a href="[% c.uri_for('/view' project.name view.name) %]"><tt>[% view.name %]</tt></a> - [<a href="[% c.uri_for('/view' project.name view.name "edit") %]">Edit</a>] - </li> - [% END %] -</ul> - -[% ELSE %] - -<p>Project <tt>[% project.name %]</tt> has no views.</p> - -[% END %] - -<p><a href="[% c.uri_for('/project' project.name 'create-view') %]">[Create a new view]</a></p> - - -<h2>Channels</h2> - -<p>This project provides the following Nix channels:</p> - -<ul> - <li> - <a href="[% c.uri_for('/project' project.name 'channel' 'latest') %]"><tt>latest</tt></a> — - contains the latest successful build of every job in this project. - </li> - <li> - <a href="[% c.uri_for('/project' project.name 'channel' 'all') %]"><tt>all</tt></a> — - contains every successful, non-garbage-collected build of every - job in this project. - </li> -</ul> - - -<h2>Statistics</h2> - -[% INCLUDE showBuildStats %] - - -[% END %] - - -[% IF edit %] - - <p><button type="submit"><img src="/static/images/success.gif" />[%IF create %]Create[% ELSE %]Apply changes[% END %]</button></p> - - </form> - - [% IF !create %] - - <form action="[% c.uri_for('/project' project.name 'delete') %]" method="post"> - <p><button id="delete-project" type="submit"><img src="/static/images/failure.gif" />Delete this project</button></p> - </form> - - <script type="text/javascript"> - $("#delete-project").click(function() { - return confirm("Are you sure you want to delete this project?"); - }); - </script> - - [% END %] - -[% END %] +<script type="text/javascript"> + $("#generic-tabs").tabs(); +</script> [% END %] diff --git a/src/root/static/css/hydra.css b/src/root/static/css/hydra.css index af543c21..1ad18163 100644 --- a/src/root/static/css/hydra.css +++ b/src/root/static/css/hydra.css @@ -1,7 +1,22 @@ +#generic-tabs li { + height : 30px; + font-size : 90%; +} +#generic-tabs { + min-height: 30em; +} +#generic-tabs div { + font-size : 90%; +} + #logo img { width: 8em; } +.statusTable td, .statusTable th { + border-style: none; +} + tr.clickable:hover { background-color: #a0a0f0; cursor: pointer; @@ -9,12 +24,21 @@ tr.clickable:hover { .layoutTable td, .layoutTable th { border-style: none; + text-align: left; } .layoutTable th { vertical-align: top; } +.activeJobsStatus td, .activeJobsStatus th { + border-style: 1px dotted #CCCCCC; +} + +.activeJobsStatus tbody tr td { + align: center; +} + a.smallLink { font-size: 60%; vertical-align: top; @@ -125,7 +149,6 @@ th.releaseSetJobName { padding: 0 0 0 0; } - /* Editing */ input.string { diff --git a/src/root/static/css/nixos-site.css b/src/root/static/css/nixos-site.css index 67b30370..6b8ac851 100644 --- a/src/root/static/css/nixos-site.css +++ b/src/root/static/css/nixos-site.css @@ -93,14 +93,18 @@ ul.short-menu li a:hover { /* The left menu. */ div#main { position: relative; - min-height: 10em; + min-height: 20em; +} + +div#content { + min-height: 20em; } div#left-bar { position: absolute; left: 0em; width: 8em; - min-height: 10em; + min-height: 20em; } div#left-title { diff --git a/src/root/static/images/checkmark_128.png b/src/root/static/images/checkmark_128.png new file mode 100644 index 00000000..26d8f08a Binary files /dev/null and b/src/root/static/images/checkmark_128.png differ diff --git a/src/root/static/images/checkmark_256.png b/src/root/static/images/checkmark_256.png new file mode 100644 index 00000000..c3f34cd8 Binary files /dev/null and b/src/root/static/images/checkmark_256.png differ diff --git a/src/root/static/images/checkmark_32.png b/src/root/static/images/checkmark_32.png new file mode 100644 index 00000000..0165e493 Binary files /dev/null and b/src/root/static/images/checkmark_32.png differ diff --git a/src/root/static/images/checkmark_64.png b/src/root/static/images/checkmark_64.png new file mode 100644 index 00000000..7807532c Binary files /dev/null and b/src/root/static/images/checkmark_64.png differ diff --git a/src/root/static/images/error_128.png b/src/root/static/images/error_128.png new file mode 100644 index 00000000..4bf26aae Binary files /dev/null and b/src/root/static/images/error_128.png differ diff --git a/src/root/static/images/error_256.png b/src/root/static/images/error_256.png new file mode 100644 index 00000000..31629ce5 Binary files /dev/null and b/src/root/static/images/error_256.png differ diff --git a/src/root/static/images/error_32.png b/src/root/static/images/error_32.png new file mode 100644 index 00000000..ef59ef79 Binary files /dev/null and b/src/root/static/images/error_32.png differ diff --git a/src/root/static/images/error_64.png b/src/root/static/images/error_64.png new file mode 100644 index 00000000..fefb8d7a Binary files /dev/null and b/src/root/static/images/error_64.png differ diff --git a/src/root/static/images/forbidden_128.png b/src/root/static/images/forbidden_128.png new file mode 100644 index 00000000..46c9bf7d Binary files /dev/null and b/src/root/static/images/forbidden_128.png differ diff --git a/src/root/static/images/forbidden_256.png b/src/root/static/images/forbidden_256.png new file mode 100644 index 00000000..85345988 Binary files /dev/null and b/src/root/static/images/forbidden_256.png differ diff --git a/src/root/static/images/forbidden_32.png b/src/root/static/images/forbidden_32.png new file mode 100644 index 00000000..98c07c26 Binary files /dev/null and b/src/root/static/images/forbidden_32.png differ diff --git a/src/root/static/images/forbidden_64.png b/src/root/static/images/forbidden_64.png new file mode 100644 index 00000000..8993807a Binary files /dev/null and b/src/root/static/images/forbidden_64.png differ diff --git a/src/root/static/images/help_128.png b/src/root/static/images/help_128.png new file mode 100644 index 00000000..92e56d3f Binary files /dev/null and b/src/root/static/images/help_128.png differ diff --git a/src/root/static/images/help_256.png b/src/root/static/images/help_256.png new file mode 100644 index 00000000..3322e2c4 Binary files /dev/null and b/src/root/static/images/help_256.png differ diff --git a/src/root/static/images/help_32.png b/src/root/static/images/help_32.png new file mode 100644 index 00000000..96eade9e Binary files /dev/null and b/src/root/static/images/help_32.png differ diff --git a/src/root/static/images/help_64.png b/src/root/static/images/help_64.png new file mode 100644 index 00000000..d57c66bf Binary files /dev/null and b/src/root/static/images/help_64.png differ diff --git a/src/root/static/images/information_128.png b/src/root/static/images/information_128.png new file mode 100644 index 00000000..ab39dda0 Binary files /dev/null and b/src/root/static/images/information_128.png differ diff --git a/src/root/static/images/information_256.png b/src/root/static/images/information_256.png new file mode 100644 index 00000000..8e516c3a Binary files /dev/null and b/src/root/static/images/information_256.png differ diff --git a/src/root/static/images/information_32.png b/src/root/static/images/information_32.png new file mode 100644 index 00000000..ced7588c Binary files /dev/null and b/src/root/static/images/information_32.png differ diff --git a/src/root/static/images/information_64.png b/src/root/static/images/information_64.png new file mode 100644 index 00000000..b5e41fc7 Binary files /dev/null and b/src/root/static/images/information_64.png differ diff --git a/src/root/static/images/warning_128.png b/src/root/static/images/warning_128.png new file mode 100644 index 00000000..397fbe1c Binary files /dev/null and b/src/root/static/images/warning_128.png differ diff --git a/src/root/static/images/warning_256.png b/src/root/static/images/warning_256.png new file mode 100644 index 00000000..8c989369 Binary files /dev/null and b/src/root/static/images/warning_256.png differ diff --git a/src/root/static/images/warning_32.png b/src/root/static/images/warning_32.png new file mode 100644 index 00000000..7ceca1da Binary files /dev/null and b/src/root/static/images/warning_32.png differ diff --git a/src/root/static/images/warning_64.png b/src/root/static/images/warning_64.png new file mode 100644 index 00000000..fe649fb0 Binary files /dev/null and b/src/root/static/images/warning_64.png differ diff --git a/src/root/topbar.tt b/src/root/topbar.tt index 71c8d61b..2c363bb9 100644 --- a/src/root/topbar.tt +++ b/src/root/topbar.tt @@ -33,6 +33,9 @@ [% INCLUDE makeLink uri = c.uri_for(c.controller('Root').action_for('errors')) title = "Errors" %] + [% INCLUDE makeLink + uri = "http://nixos.org/hydra" + title = "About" %] [% IF c.user_exists %] [% INCLUDE makeLink uri = c.uri_for(c.controller('Root').action_for('logout'))