Merge branch 'master' into persona

Conflicts:
	src/lib/Hydra/Helper/CatalystUtils.pm
	src/root/layout.tt
	src/root/topbar.tt
	src/root/user.tt
This commit is contained in:
Eelco Dolstra
2013-11-05 11:11:48 +01:00
114 changed files with 3593 additions and 1883 deletions

View File

@ -7,6 +7,7 @@
[% project = build.project %]
[% jobset = build.jobset %]
[% job = build.job %]
[% isAggregate = constituents.size > 0 %]
[% BLOCK renderOutputs %]
[% start=1; FOREACH output IN outputs %]
@ -22,7 +23,7 @@
<tbody>
[% FOREACH step IN build.buildsteps %]
[% IF ( type == "All" ) || ( type == "Failed" && step.status != 0 ) || ( type == "Running" && step.busy == 1 ) %]
[% has_log = log_exists(step.drvpath);
[% has_log = buildStepLogExists(step);
log = c.uri_for('/build' build.id 'nixlog' step.stepnr); %]
<tr>
<td>[% step.stepnr %]</td>
@ -67,7 +68,40 @@
[% END %]
<ul class="nav nav-tabs">
<li class="dropdown">
<a class="dropdown-toggle actions" data-toggle="dropdown" href="#">
Actions
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
[% IF build.nixexprinput %]
<li><a href="#reproduce" data-toggle="modal">Reproduce locally</a></li>
[% END %]
[% IF c.user_exists %]
[% IF available %]
[% IF build.keep %]
<li><a href="[% c.uri_for('/build' build.id 'keep' 0) %]">Unkeep</a></li>
[% ELSE %]
<li><a href="[% c.uri_for('/build' build.id 'keep' 1) %]">Keep</a></li>
[% END %]
[% END %]
[% IF build.finished %]
<li><a href="[% c.uri_for('/build' build.id 'restart') %]">Restart</a></li>
[% ELSE %]
<li><a href="[% c.uri_for('/build' build.id 'cancel') %]">Cancel</a></li>
[% END %]
[% IF available && project.releases %]
[% INCLUDE menuItem
uri = "#add-to-release"
title = "Add to release"
modal = 1 %]
[% END %]
[% END %]
</ul>
</li>
<li class="active"><a href="#tabs-summary" data-toggle="tab">Summary</a></li>
[% IF isAggregate %]<li><a href="#tabs-constituents" data-toggle="tab">Constituents</a></li>[% END %]
<li><a href="#tabs-details" data-toggle="tab">Details</a></li>
<li><a href="#tabs-buildinputs" data-toggle="tab">Inputs</a></li>
[% IF build.buildsteps %]<li><a href="#tabs-buildsteps" data-toggle="tab">Build steps</a></li>[% END %]
@ -81,26 +115,6 @@
<div id="tabs-summary" class="tab-pane active">
[% IF build.nixexprinput %]
[% WRAPPER makePopover title="Reproduce locally" classes="btn-info pull-right" placement="left" %]
[% url = c.uri_for('/build' build.id 'reproduce') %]
<p>You can reproduce this build on your own machine by
downloading <a [% HTML.attributes(href => url) %]>a script</a>
that checks out all inputs of the build and then invokes Nix
to perform the build. This script requires that you have Nix
on your system.</p>
<p>To download and execute the script from the command line,
run the following command:</p>
<pre>
<span class="shell-prompt">$ </span>bash <(curl <a [% HTML.attributes(href => url) %]>[% HTML.escape(url) %]</a>)
</pre>
[% END %]
[% END %]
<table>
<tr>
<td>
@ -114,7 +128,28 @@
</tr>
<tr>
<th>Status:</th>
<td>[% INCLUDE renderStatus build=build icon=0 %]</td>
<td>
[% INCLUDE renderStatus build=build icon=0 %]
[% IF isAggregate;
nrConstituents = 0;
nrFinished = 0;
nrFailedConstituents = 0;
FOREACH b IN constituents;
nrConstituents = nrConstituents + 1;
IF b.finished; nrFinished = nrFinished + 1; END;
IF b.finished && b.buildstatus != 0; nrFailedConstituents = nrFailedConstituents + 1; END;
END;
%];
[%+ IF nrFinished == nrMembers && nrFailedConstituents == 0 %]
all [% nrConstituents %] constituent builds succeeded
[% ELSE %]
[% nrFailedConstituents %] out of [% nrConstituents %] constituent builds failed
[% IF nrFinished < nrConstituents %]
([% nrConstituents - nrFinished %] still pending)
[% END %]
[% END %]
[% END %]
</td>
</tr>
<tr>
<th>System:</th>
@ -146,7 +181,7 @@
<td>[% IF cachedBuild; INCLUDE renderFullBuildLink build=cachedBuild; ELSE %]<em>unknown</em>[% END %]</td>
</tr>
[% END %]
[% IF build.finished %]
[% IF !isAggregate && build.finished %]
<tr>
<th>Duration:</th>
<td>[% actualBuild = build.iscachedbuild ? cachedBuild : build;
@ -154,7 +189,7 @@
finished at [% INCLUDE renderDateTime timestamp = actualBuild.stoptime %]</td>
</tr>
[% END %]
[% IF log_exists(build.drvpath) %]
[% IF !isAggregate && buildLogExists(build) %]
<tr>
<th>Logfile:</th>
<td>
@ -169,20 +204,7 @@
</tr>
</table>
[% IF c.user_exists && available %]
<br/>
<form class="form-horizontal" action="[% c.uri_for('/build' build.id 'add-to-release') %]" method="post">
<div class="control-group">
<label class="control-label">Add to release</label>
<div class="controls">
<input type="text" class="input" name="name"></input>
<button type="submit" class="btn btn-success">Apply</button>
</div>
</div>
</form>
[% END %]
[% IF build.buildproducts %]
[% IF build.buildproducts && !isAggregate %]
<h3>Build products</h3>
@ -251,6 +273,18 @@
</div>
[% IF isAggregate %]
<div id="tabs-constituents" class="tab-pane">
<p>This build is an aggregate of the following builds:</p>
[% INCLUDE renderBuildList builds=constituents hideProjectName=1 hideJobsetName=1 %]
</div>
[% END %]
<div id="tabs-details" class="tab-pane">
<table class="info-table">
@ -380,8 +414,8 @@
<div id="placeholder" style="width:800px;height:400px;"></div>
<div id="overview" style="margin-left:50px;margin-top:20px;width:600px;height:50px"></div>
<script src="/static/js/flot/jquery.flot.js" type="text/javascript"></script>
<script src="/static/js/flot/jquery.flot.selection.js" type="text/javascript"></script>
<script src="[% c.uri_for("/static/js/flot/jquery.flot.js") %]" type="text/javascript"></script>
<script src="[% c.uri_for("/static/js/flot/jquery.flot.selection.js") %]" type="text/javascript"></script>
<script type="text/javascript">
$(function() {
var d = [];
@ -524,4 +558,58 @@
</div>
[% IF c.user_exists && available && project.releases %]
<div id="add-to-release" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true">
<form class="form-horizontal" action="[% c.uri_for('/build' build.id 'add-to-release') %]" method="post">
<div class="modal-body">
<div class="control-group">
<label class="control-label">Add to release</label>
<div class="controls">
<select class="span2" name="name">
[% FOREACH r IN project.releases %]
<option>[% HTML.escape(r.name) %]</option>
[% END %]
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Add</button>
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
</div>
</form>
</div>
[% END %]
<div id="reproduce" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true">
[% url = c.uri_for('/build' build.id 'reproduce') %]
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Reproduce this build</h3>
</div>
<div class="modal-body">
<p>You can reproduce this build on your own machine by downloading
<a [% HTML.attributes(href => url) %]>a script</a> that checks out
all inputs of the build and then invokes Nix to perform the build.
This script requires that you have Nix on your system.</p>
<p>To download and execute the script from the command line, run the
following command:</p>
<pre>
<span class="shell-prompt">$ </span>bash <(curl <a [% HTML.attributes(href => url) %]>[% HTML.escape(url) %]</a>)
</pre>
</div>
<div class="modal-footer">
<a href="#" class="btn btn-primary" data-dismiss="modal">Close</a>
</div>
</div>
[% END %]

View File

@ -60,7 +60,6 @@ install the package simply by clicking on the packages below.</p>
[% ELSE %]
[% HTML.escape(b.description) %]
[% END %]
[% IF pkg.outName != 'out' %] [[% pkg.outName %]][% END %]
</td>
</tr>

View File

@ -1,51 +0,0 @@
[% WRAPPER layout.tt title="Clone build ${build.id}" %]
[% PROCESS common.tt %]
[% USE HTML %]
[% edit=1 %]
<p>Cloning allows you to perform a build with modified inputs.</p>
<form action="[% c.uri_for('/build' build.id 'clone' 'submit') %]" method="post">
<h2>Nix expression</h2>
<p>Evaluate job <tt><input type="text" class="string"
name="jobname" [% HTML.attributes(value => build.job.name) %]
/></tt> in Nix expression <tt><input type="text" class="string"
name="nixexprpath" [% HTML.attributes(value => build.nixexprpath) %]
/></tt> in input <tt><input type="text" class="string"
name="nixexprinput" [% HTML.attributes(value => build.nixexprinput)
%] /></tt>.</p>
<h2>Build inputs</h2>
<table class="table table-condensed table-striped">
<thead>
<tr><th>Name</th><th>Type</th><th>Value</th></tr>
</thead>
<tbody>
[% FOREACH input IN build.inputs %]
<tr>
<td><tt>[% input.name %]<input type="hidden" [% HTML.attributes(name => "input-$input.name-name" value => input.name) %] /></tt></td>
<td>
[% INCLUDE renderSelection curValue=input.type param="input-$input.name-type" options=inputTypes %]
</td>
<td>
<tt><input type="text" class="string" name="input-[% input.name %]-value"
[% IF input.type == "build" || input.type == "sysbuild" %]
[% build = input.dependency %]
[% HTML.attributes(value => build.project.name _ ':' _ build.jobset.name _ ':' _ build.job.name _ '[id="'_ build.id _ '"]' ) %]
[% ELSE %]
[% HTML.attributes(value => input.value || input.uri) %]
[% END %] /></tt>
</td>
</tr>
[% END %]
</tbody>
</table>
<p><button type="submit"><img alt="Add" src="/static/images/success.gif" />Add to queue</button></p>
</form>
[% END %]

View File

@ -1,24 +0,0 @@
[% WRAPPER layout.tt title="Clone jobset $jobset.project.name:$jobset.name" %]
[% PROCESS common.tt %]
[% USE HTML %]
[% edit=1 %]
<form class="form-horizontal" action="[% c.uri_for('/jobset' jobset.project.name jobset.name 'clone' 'submit') %]" method="post">
<fieldset>
<div class="control-group">
<label class="control-label">New name</label>
<div class="controls">
<input type="text" class="span3" name="newjobset" value=""></input>
</div>
</div>
<div class="form-actions">
<input type="submit" value="Submit" class="btn btn-primary" />
</div>
</fieldset>
</form>
[% END %]

View File

@ -20,7 +20,7 @@ BLOCK renderJobsetName %]
BLOCK renderJobName %]
<a [% IF inRow %]class="row-link"[% END %] href="[% c.uri_for('/job' project jobset job) %]"><tt>[% job %]</tt></a>
<a [% IF inRow %]class="row-link"[% END %] href="[% c.uri_for('/job' project jobset job) %]">[% job %]</a>
[% END;
@ -40,9 +40,9 @@ END;
BLOCK renderDuration;
IF duration >= 24 * 60 * 60; duration div (24 * 60 * 60) %]d [% END;
IF duration >= 60 * 60; duration div (60 * 60) % 24 %]h [% END;
IF duration >= 60; duration div 60 % 60 %]m [% END;
IF duration >= 24 * 60 * 60; duration div (24 * 60 * 60) %]d&nbsp;[% END;
IF duration >= 60 * 60; duration div (60 * 60) % 24 %]h&nbsp;[% END;
IF duration >= 60; duration div 60 % 60 %]m&nbsp;[% END;
duration % 60 %]s[%
END;
@ -64,12 +64,9 @@ BLOCK renderBuildListHeader %]
[% IF !hideJobName %]
<th>Job</th>
[% END %]
<th>Release Name</th>
<th>Release name</th>
<th>System</th>
<th>[% IF showSchedulingInfo %]Queued at[% ELSE %]Finished at[% END %]</th>
[% IF showStatusChange %]
<th class="headerSortUp">Last status change</th>
[% END %]
[% IF showDescription %]
<th>Description</th>
[% END %]
@ -99,25 +96,14 @@ BLOCK renderBuildListBody;
[% END %]
<td>[% !showSchedulingInfo and build.get_column('releasename') ? build.get_column('releasename') : build.nixname %]</td>
<td class="nowrap"><tt>[% build.system %]</tt></td>
<td class="nowrap">[% date.format(showSchedulingInfo ? build.timestamp : build.stoptime, '%Y-%m-%d %H:%M:%S') %]</td>
[% IF showStatusChange %]
<td>
[% IF build.get_column('statusChangeTime') %]
<a href="[% c.uri_for('/build' build.get_column('statusChangeId')) %]">
[% date.format(build.get_column('statusChangeTime'), '%Y-%m-%d %H:%M:%S') %]
</a>
[% ELSE %]
<em>never</em>
[% END %]
</td>
[% END %]
<td class="nowrap">[% t = showSchedulingInfo ? build.timestamp : build.stoptime; IF t; date.format(showSchedulingInfo ? build.timestamp : build.stoptime, '%Y-%m-%d %H:%M:%S'); ELSE; "-"; END %]</td>
[% IF showDescription %]
<td>[% build.description %]</td>
[% END %]
</tr>
[% END;
IF linkToAll %]
<td class="centered" colspan="5"><a href="[% linkToAll %]"><em>More...</em></a></td></tr>
<tr><td class="centered" colspan="5"><a href="[% linkToAll %]"><em>More...</em></a></td></tr>
[% END;
END;
@ -144,7 +130,7 @@ END;
BLOCK maybeLink;
IF uri %]<a [% HTML.attributes(href => uri, class => class) %][% IF confirmmsg %]onclick="javascript:return confirm('[% confirmmsg %]')"[% END %]>[% content %]</a>[% ELSE; content; END;
IF uri %]<a [% HTML.attributes(href => uri, class => class); IF confirmmsg +%] onclick="javascript:return confirm('[% confirmmsg %]')"[% END %]>[% content %]</a>[% ELSE; content; END;
END;
@ -164,7 +150,7 @@ BLOCK renderSelection;
[% ELSE %]
<select [% HTML.attributes(id => param, name => param) %]>
[% FOREACH name IN options.keys.sort %]
<option [% HTML.attributes(value => name) %] [% IF name == curValue; "selected='selected'"; END %]>[% options.$name %]</option>
<option [% IF name == curValue; "selected='selected'"; END; " "; HTML.attributes(value => name) %]>[% options.$name %]</option>
[% END %]
</select>
[% END;
@ -195,24 +181,24 @@ BLOCK renderBuildStatusIcon;
buildstatus = buildstatus != undef ? buildstatus : build.buildstatus;
IF finished;
IF buildstatus == 0 %]
<img src="/static/images/checkmark_[% size %].png" alt="Succeeded" />
<img src="[% c.uri_for("/static/images/checkmark_${size}.png") %]" alt="Succeeded" />
[% ELSIF buildstatus == 1 %]
<img src="/static/images/error_[% size %].png" alt="Failed" />
[% ELSIF buildstatus == 2 %]
<img src="/static/images/dependency_[% size %].png" alt="Dependency failed" />
<img src="[% c.uri_for("/static/images/error_${size}.png") %]" alt="Failed" />
[% ELSIF buildstatus == 2 || buildstatus == 5 %]
<img src="[% c.uri_for("/static/images/dependency_${size}.png") %]" alt="Dependency failed" />
[% ELSIF buildstatus == 3 %]
<img src="[% c.uri_for("/static/images/warning_${size}.png") %]" alt="Aborted" />
[% ELSIF buildstatus == 4 %]
<img src="/static/images/cancelled_[% size %].png" alt="Cancelled" />
[% ELSIF buildstatus == 5 %]
<img src="/static/images/error_[% size %].png" alt="Failed" />
<img src="[% c.uri_for("/static/images/forbidden_${size}.png") %]" alt="Cancelled" />
[% ELSIF buildstatus == 6 %]
<img src="/static/images/error_[% size %].png" alt="Failed (with result)" />
<img src="[% c.uri_for("/static/images/error_${size}.png") %]" alt="Failed (with result)" />
[% ELSE %]
<img src="/static/images/error_[% size %].png" alt="Failed" />
<img src="[% c.uri_for("/static/images/error_${size}.png") %]" alt="Failed" />
[% END;
ELSIF busy %]
<img src="/static/images/help_[% size %].png" alt="Busy" />
<img src="[% c.uri_for("/static/images/help_${size}.png") %]" alt="Busy" />
[% ELSE %]
<img src="/static/images/help_[% size %].png" alt="Scheduled" />
<img src="[% c.uri_for("/static/images/help_${size}.png") %]" alt="Scheduled" />
[% END;
END;
@ -225,17 +211,15 @@ BLOCK renderStatus;
<strong>Success</strong>
[% ELSIF buildstatus == 1 %]
<span class="error">Build returned a non-zero exit code</span>
[% ELSIF buildstatus == 2 %]
[% ELSIF buildstatus == 2 || buildstatus == 5 %]
<span class="error">A dependency of the build failed</span>
[% ELSIF buildstatus == 4 %]
<span class="error">Cancelled by user</span>
[% ELSIF buildstatus == 5 %]
<span class="error">Build inhibited because a dependency previously failed to build</span>
[% ELSIF buildstatus == 6 %]
<span class="error">Build failed (with result)</span>
[% ELSE %]
<span class="error">Build failed</span>
(see <a href="#nix-error">below</a>)
<span class="error">Aborted</span>
(Hydra failure; see <a href="#nix-error">below</a>)
[% END;
ELSIF build.busy %]
<strong>Build in progress</strong>
@ -246,24 +230,15 @@ BLOCK renderStatus;
END;
BLOCK renderInputValue;
IF input.type == "build" || input.type == "sysbuild";
INCLUDE renderFullBuildLink build=input.dependency;
ELSIF input.type == "string" || input.type == "boolean" %]
<tt>"[% input.value %]"</tt>
[% ELSE %]
<tt>[% input.uri %][% IF input.revision %] (r[% input.revision %])[% END %]</tt>
[% END;
END;
BLOCK renderShortInputValue;
IF input.type == "build" || input.type == "sysbuild" %]
<a href="[% c.uri_for('/build' input.dependency.id) %]">[% input.dependency.id %]</a>
[% ELSIF input.type == "string" || input.type == "boolean" %]
<tt>"[% input.value %]"</tt>
[% ELSIF input.type == "string" %]
<tt>"[% HTML.escape(input.value) %]"</tt>
[% ELSIF input.type == "nix" || input.type == "boolean" %]
<tt>[% HTML.escape(input.value) %]</tt>
[% ELSE %]
<tt>[% input.uri %][% IF input.revision %] (r[% input.revision %])[% END %]</tt>
<tt>[% HTML.escape(input.uri) %][% IF input.revision %] (r[% HTML.escape(input.revision) %])[% END %]</tt>
[% END %]
[% END;
@ -275,7 +250,7 @@ BLOCK renderDiffUri;
url = bi1.uri;
path = url.replace(base, '');
IF url.match(base) %]
<a target="_new" href="[% m.uri.replace('_path_', path).replace('_1_', bi1.revision).replace('_2_', bi2.revision) %]">[% contents %]</a>
<a target="_blank" href="[% m.uri.replace('_path_', path).replace('_1_', bi1.revision).replace('_2_', bi2.revision) %]">[% contents %]</a>
[% nouri = 0;
END;
END;
@ -284,7 +259,7 @@ BLOCK renderDiffUri;
url = res.0;
branch = res.1;
IF bi1.type == "hg" || bi1.type == "git" %]
<a target="_new" href="[% HTML.escape("/api/scmdiff?uri=$url&rev1=$bi1.revision&rev2=$bi2.revision&type=$bi1.type&branch=$branch") %]">[% contents %]</a>
<a target="_blank" href="[% HTML.escape("/api/scmdiff?uri=$url&rev1=$bi1.revision&rev2=$bi2.revision&type=$bi1.type&branch=$branch") %]">[% contents %]</a>
[% ELSE;
contents;
END;
@ -305,13 +280,15 @@ BLOCK renderInputs; %]
<td>
[% IF input.type == "build" || input.type == "sysbuild" %]
[% INCLUDE renderFullBuildLink build=input.dependency %]
[% ELSIF input.type == "string" || input.type == "boolean" %]
<tt>"[% input.value %]"</tt>
[% ELSIF input.type == "string" %]
<tt>"[% HTML.escape(input.value) %]"</tt>
[% ELSIF input.type == "nix" || input.type == "boolean" %]
<tt>[% HTML.escape(input.value) %]</tt>
[% ELSE %]
<tt>[% input.uri %]</tt>
<tt>[% HTML.escape(input.uri) %]</tt>
[% END %]
</td>
<td>[% IF input.revision %][% input.revision %][% END %]</td>
<td>[% IF input.revision %][% HTML.escape(input.revision) %][% END %]</td>
<td><tt>[% input.path %]</tt></td>
</tr>
[% END %]
@ -372,10 +349,10 @@ BLOCK renderInputDiff; %]
BLOCK renderPager %]
<ul class="pager">
<li [% IF page == 1 %]class="disabled"[% END %]><a href="[% "$baseUri?page=1" %]">&laquo; First</a></li>
<li [% IF page == 1 %]class="disabled"[% END %]><a href="[% "$baseUri?page="; (page - 1) %]">&lsaquo; Previous</a></li>
<li [% IF page * resultsPerPage >= total %]class="disabled"[% END %]><a href="[% "$baseUri?page="; (page + 1) %]">Next &rsaquo;</a></li>
<li [% IF page * resultsPerPage >= total %]class="disabled"[% END %]><a href="[% "$baseUri?page="; (total - 1) div resultsPerPage + 1 %]">Last &raquo;</a></li>
<li [% IF page == 1 %]class="disabled"[% END %]><a href="[% "$baseUri?page=1" %]">« First</a></li>
<li [% IF page == 1 %]class="disabled"[% END %]><a href="[% "$baseUri?page="; (page - 1) %]"> Previous</a></li>
<li [% IF page * resultsPerPage >= total %]class="disabled"[% END %]><a href="[% "$baseUri?page="; (page + 1) %]">Next </a></li>
<li [% IF page * resultsPerPage >= total %]class="disabled"[% END %]><a href="[% "$baseUri?page="; (total - 1) div resultsPerPage + 1 %]">Last »</a></li>
</ul>
[% END;
@ -458,22 +435,10 @@ BLOCK renderLogLinks %]
BLOCK makeLazyTab %]
<div id="[% tabName %]" class="tab-pane">
<center><img src="/static/images/ajax-loader.gif" alt="Loading..." /></center>
<center><img src="[% c.uri_for("/static/images/ajax-loader.gif") %]" alt="Loading..." /></center>
</div>
<script>
$(function() {
$('.nav-tabs').bind('show', function(e) {
var pattern = /#.+/gi;
var id = e.target.toString().match(pattern)[0];
if (id == "#[% tabName %]") {
$('#[% tabName %]').load("[% uri %]", function(response, status, xhr) {
if (status == "error") {
$('#[% tabName %]').html("<div class='alert alert-error'>Error loading tab: " + xhr.status + " " + xhr.statusText + "</div>");
}
});
}
});
});
$(function() { makeLazyTab("[% tabName %]", "[% uri %]"); });
</script>
[% END;
@ -485,4 +450,18 @@ BLOCK makePopover %]
[% END;
BLOCK menuItem %]
<li class="[% IF "${root}${curUri}" == uri %]active[% END %]" [% IF confirmmsg %]onclick="javascript:return confirm('[% confirmmsg %]')"[% END %]>
<a [% HTML.attributes(href => uri) %] [%+ IF modal %]data-toggle="modal"[% END %]>
[% IF icon %]<i class="[% icon %] icon-black"></i> [%+ END %]
[% title %]
</a>
</li>
[% END;
BLOCK makeStar %]
<span class="star" data-post="[% starUri %]">[% IF starred; "★"; ELSE; "☆"; END %]</span>
[% END;
%]

42
src/root/dashboard.tt Normal file
View File

@ -0,0 +1,42 @@
[% WRAPPER layout.tt title="Dashboard" %]
[% PROCESS common.tt %]
<ul class="nav nav-tabs">
<li class="active"><a href="#tabs-starred-jobs" data-toggle="tab">Starred jobs</a></li>
</ul>
<div id="generic-tabs" class="tab-content">
<div id="tabs-starred-jobs" class="tab-pane active">
[% IF starredJobs.size > 0 %]
<p>Below are the 20 most recent builds of your starred jobs.</p>
<table class="table table-striped table-condensed">
<thead>
<tr><th>Job</th></tr>
</thead>
<tdata>
[% FOREACH j IN starredJobs %]
<tr>
<td>[% INCLUDE renderFullJobName project=j.job.get_column('project') jobset=j.job.get_column('jobset') job=j.job.name %]</td>
[% FOREACH b IN j.builds %]
<td><a href="[% c.uri_for('/build' b.id) %]">[% INCLUDE renderBuildStatusIcon size=16 build=b %]</a></td>
[% END %]
</tr>
[% END %]
</tdata>
</table>
[% ELSE %]
<div class="alert alert-warning">You have no starred jobs. You can add them by visiting a job page and clicking on the ☆ icon.</div>
[% END %]
</div>
</div>
[% END %]

View File

@ -12,7 +12,7 @@
<span id="[% done.${node.path} %]"><span class="dep-tree-line">
[% IF node.buildStep %]
<a href="[% c.uri_for('/build' node.buildStep.get_column('build')) %]"><tt>[% node.name %]</tt></a> [%
IF log_exists(node.buildStep.drvpath);
IF buildStepLogExists(node.buildStep);
INCLUDE renderLogLinks url=c.uri_for('/build' node.buildStep.get_column('build') 'nixlog' node.buildStep.stepnr);
END %]
[% ELSE %]

View File

@ -1,9 +1,10 @@
[% WRAPPER layout.tt title=(create ? "Create jobset in project $project.name" : "Editing jobset $project.name:$jobset.name") %]
[% WRAPPER layout.tt title=(create ? "Create jobset in project $project.name" : clone ? "Cloning jobset $project.name:$jobset.name" : "Editing jobset $project.name:$jobset.name") %]
[% PROCESS common.tt %]
[% USE format %]
[% BLOCK renderJobsetInputAlt %]
<button type="button" class="btn btn-warning" onclick='$(this).parents(".inputalt").remove()'><i class="icon-trash icon-white"></i></button>
<input type="text" [% HTML.attributes(value => alt.value, name => name) %]></input>
<input type="text" [% HTML.attributes(value => alt.value, name => name) %]/>
<br />
[% END %]
@ -11,51 +12,61 @@
<tr class="input [% extraClass %]" [% IF id %]id="[% id %]"[% END %]>
<td>
<button type="button" class="btn btn-warning" onclick='$(this).parents(".input").remove()'><i class="icon-trash icon-white"></i></button>
<tt><input type="text" id="[% baseName %]-name" name="[% baseName %]-name" [% HTML.attributes(value => input.name) %]></input>
<input type="text" id="[% baseName %]-name" name="[% baseName %]-name" [% HTML.attributes(value => input.name) %]/>
</td>
<td>
[% INCLUDE renderSelection curValue=input.type param="$baseName-type" options=inputTypes %]
</td>
<td class="inputalts" id="[% baseName %]">
[% FOREACH alt IN input.jobsetinputalts %]
[% FOREACH alt IN input.search_related('jobsetinputalts', {}, { order_by => 'altnr' }) %]
<span class="inputalt">
[% INCLUDE renderJobsetInputAlt alt=alt name="$baseName-values" %]
</span>
[% END %]
[% IF edit %]<button type="button" class="add-inputalt btn btn-success" onclick='return false'><i class="icon-plus icon-white"></i></button>[% END %]
</td>
<td>
<input type="checkbox" id="[% baseName %]-emailresponsible" name="[% baseName %]-emailresponsible" [% IF input.emailresponsible; 'checked="checked"'; END %]/>
</td>
</tr>
[% END %]
[% BLOCK renderJobsetInputs %]
<table class="table table-striped table-condensed">
<thead>
<tr><th>Input name</th><th>Type</th><th>Values</th></tr>
<tr><th>Input name</th><th>Type</th><th>Values</th><th>Notify committers</th></tr>
</thead>
<tbody class="inputs">
[% FOREACH input IN jobset.jobsetinputs %]
[% INCLUDE renderJobsetInput input=input baseName="input-$input.name" %]
[% END %]
<tr>
<td colspan="3" style="text-align: center;"><button type="button" class="add-input btn btn-success"><i class="icon-plus icon-white"></i> Add a new input</button></td
<td colspan="4" style="text-align: center;"><button type="button" class="add-input btn btn-success"><i class="icon-plus icon-white"></i> Add a new input</button></td>
</tr>
</tbody>
</table>
[% END %]
<form class="form-horizontal" action="[% IF create %][% c.uri_for('/project' project.name 'create-jobset/submit') %][% ELSE %][% c.uri_for('/jobset' project.name jobset.name 'submit') %][% END %]" method="post">
<form class="form-horizontal">
<fieldset>
<div class="control-group">
<label class="control-label">State</label>
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="enabled" [% IF jobset.enabled; 'checked="checked"'; END %]></input>Enabled
</label>
<div class="btn-group" data-toggle="buttons-radio">
<input type="hidden" name="enabled" value="[% jobset.enabled %]" />
<button type="button" class="btn" value="1">Enabled</button>
<button type="button" class="btn" value="2">One-shot</button>
<button type="button" class="btn" value="0">Disabled</button>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="visible" [% IF !jobset.hidden; 'checked="checked"'; END %]></input>Visible
<input type="checkbox" name="visible" [% IF !jobset.hidden; 'checked="checked"'; END %]/>Visible
</label>
</div>
</div>
@ -63,23 +74,23 @@
<div class="control-group">
<label class="control-label">Identifier</label>
<div class="controls">
<input type="text" class="span3" name="name" [% HTML.attributes(value => jobset.name) %]></input>
<input type="text" class="span3" name="name" [% HTML.attributes(value => clone ? "" : jobset.name) %]/>
</div>
</div>
<div class="control-group">
<label class="control-label">Description</label>
<div class="controls">
<input type="text" class="span3" name="description" [% HTML.attributes(value => jobset.description) %]></input>
<input type="text" class="span3" name="description" [% HTML.attributes(value => jobset.description) %]/>
</div>
</div>
<div class="control-group">
<label class="control-label">Nix expression</label>
<div class="controls">
<input type="text" class="span3" name="nixexprpath" [% HTML.attributes(value => jobset.nixexprpath) %]></input>
<input type="text" class="span3" name="nixexprpath" [% HTML.attributes(value => jobset.nixexprpath) %]/>
in
<input type="text" class="span3" name="nixexprinput" [% HTML.attributes(value => jobset.nixexprinput) %]></input>
<input type="text" class="span3" name="nixexprinput" [% HTML.attributes(value => jobset.nixexprinput) %]/>
</div>
</div>
@ -87,17 +98,29 @@
<label class="control-label">Check interval</label>
<div class="controls">
<div class="input-append">
<input type="number" class="span3" name="checkinterval" [% HTML.attributes(value => jobset.checkinterval) %]></input>
<input type="number" class="span3" name="checkinterval" [% HTML.attributes(value => jobset.checkinterval) %]/>
<span class="add-on">sec</span>
</div>
<span class="help-inline">(0 to disable polling)</span>
</div>
</div>
<div class="control-group">
<label class="control-label">Scheduling shares</label>
<div class="controls">
<div class="input-append">
<input type="number" class="span3" name="schedulingshares" [% HTML.attributes(value => jobset.schedulingshares) %]/>
</div>
[% IF totalShares %]
<span class="help-inline">([% f = format("%.2f"); f(jobset.schedulingshares / totalShares * 100) %]% out of [% totalShares %] shares)</span>
[% END %]
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="enableemail" [% IF jobset.enableemail; 'checked="checked"'; END %]></input>Email notification
<input type="checkbox" name="enableemail" [% IF jobset.enableemail; 'checked="checked"'; END %]/>Email notification
</label>
</div>
</div>
@ -105,33 +128,21 @@
<div class="control-group">
<label class="control-label">Email override</label>
<div class="controls">
<input type="text" class="span3" name="emailoverride" [% HTML.attributes(value => jobset.emailoverride) %]></input>
<input type="text" class="span3" name="emailoverride" [% HTML.attributes(value => jobset.emailoverride) %]/>
</div>
</div>
<div class="control-group">
<label class="control-label">Number of builds to keep</label>
<label class="control-label">Number of evaluations to keep</label>
<div class="controls">
<input type="number" class="span3" name="keepnr" [% HTML.attributes(value => jobset.keepnr) %]></input>
<input type="number" class="span3" name="keepnr" [% HTML.attributes(value => jobset.keepnr) %]/>
</div>
</div>
[% INCLUDE renderJobsetInputs %]
<div class="form-actions">
<button type="submit" class="btn btn-primary"><i class="icon-ok icon-white"></i> [%IF create %]Create[% ELSE %]Apply changes[% END %]</button>
[% IF !create %]
<button id="delete-jobset" type="submit" class="btn btn-danger" name="submit" value="delete">
<i class="icon-trash icon-white"></i>
Delete this jobset
</button>
<script type="text/javascript">
$("#delete-jobset").click(function() {
return confirm("Are you sure you want to delete this jobset?");
});
</script>
[% END %]
<button id="submit-jobset" type="submit" class="btn btn-primary"><i class="icon-ok icon-white"></i> [%IF create || clone %]Create jobset[% ELSE %]Apply changes[% END %]</button>
</div>
</fieldset>
@ -144,26 +155,68 @@
[% INCLUDE renderJobsetInputAlt alt=alt %]
</tt>
<script type="text/javascript">
$(document).ready(function() {
var id = 0;
$(".add-input").click(function() {
var newid = "input-" + id++;
var x = $("#input-template").clone(true).attr("id", "").insertBefore($(this).parents("tr")).show();
$("#input-template-name", x).attr("name", newid + "-name");
$("#input-template-type", x).attr("name", newid + "-type");
$("#input-template", x).attr("id", newid);
return false;
});
$(".add-inputalt").click(function() {
var x = $("#inputalt-template").clone(true).insertBefore($(this)).attr("id", "").show();
$("input", x).attr("name", x.parents(".inputalts").attr("id") + "-values");
});
});
</script>
</form>
<script type="text/javascript">
$(document).ready(function() {
var id = 0;
$(".add-input").click(function() {
var newid = "input-" + id++;
var x = $("#input-template").clone(true).attr("id", "").insertBefore($(this).parents("tr")).show();
$("#input-template-name", x).attr("name", newid + "-name");
$("#input-template-type", x).attr("name", newid + "-type");
$("#input-template-emailresponsible", x).attr("name", newid + "-emailresponsible");
$("#input-template", x).attr("id", newid);
return false;
});
$(".add-inputalt").click(function() {
var x = $("#inputalt-template").clone(true).insertBefore($(this)).attr("id", "").show();
$("input", x).attr("name", x.parents(".inputalts").attr("id") + "-values");
});
});
$("#submit-jobset").click(function() {
var formElements = $(this).parents("form").serializeArray();
var data = { 'inputs': {} };
var inputs = {};
for (var i = 0; i < formElements.length; i++) {
var elem = formElements[i];
var match = elem.name.match(/^input-(\w+)-(\w+)$/);
if (match === null) {
data[elem.name] = elem.value;
} else {
var baseName = match[1];
var param = match[2];
if (baseName === "template") {
continue;
}
if (!(baseName in inputs)) {
inputs[baseName] = {};
}
if (param === "name") {
data.inputs[elem.value] = inputs[baseName];
} else {
inputs[baseName][param] = elem.value;
}
}
}
redirectJSON({
[% IF create || clone %]
url: "[% c.uri_for('/jobset' project.name '.new') %]",
[% ELSE %]
url: "[% c.uri_for('/jobset' project.name jobset.name) %]",
[% END %]
data: JSON.stringify(data),
contentType: 'application/json',
type: 'PUT'
});
return false;
});
</script>
[% END %]

View File

@ -1,19 +1,19 @@
[% WRAPPER layout.tt title=(create ? "New project" : "Editing project $project.name") %]
[% PROCESS common.tt %]
<form class="form-horizontal" action="[% IF create %][% c.uri_for('/create-project/submit') %][% ELSE %][% c.uri_for('/project' project.name 'submit') %][% END %]" method="post">
<form class="form-horizontal">
<fieldset>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="enabled" [% IF project.enabled; 'checked="checked"'; END %]></input>Enabled
<input type="checkbox" name="enabled" [% IF project.enabled; 'checked="checked"'; END %]/>Enabled
</label>
</div>
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="visible" [% IF !project.hidden; 'checked="checked"'; END %]></input>Visible in the list of projects
<input type="checkbox" name="visible" [% IF !project.hidden; 'checked="checked"'; END %]/>Visible in the list of projects
</label>
</div>
</div>
@ -21,58 +21,63 @@
<div class="control-group">
<label class="control-label">Identifier</label>
<div class="controls">
<input type="text" class="span3" name="name" [% HTML.attributes(value => project.name) %]></input>
<input type="text" class="span3" name="name" [% HTML.attributes(value => project.name) %]/>
</div>
</div>
<div class="control-group">
<label class="control-label">Display name</label>
<div class="controls">
<input type="text" class="span3" name="displayname" [% HTML.attributes(value => project.displayname) %]></input>
<input type="text" class="span3" name="displayname" [% HTML.attributes(value => project.displayname) %]/>
</div>
</div>
<div class="control-group">
<label class="control-label">Description</label>
<div class="controls">
<input type="text" class="span3" name="description" [% HTML.attributes(value => project.description) %]></input>
<input type="text" class="span3" name="description" [% HTML.attributes(value => project.description) %]/>
</div>
</div>
<div class="control-group">
<label class="control-label">Homepage</label>
<div class="controls">
<input type="text" class="span3" name="homepage" [% HTML.attributes(value => project.homepage) %]></input>
<input type="text" class="span3" name="homepage" [% HTML.attributes(value => project.homepage) %]/>
</div>
</div>
<div class="control-group">
<label class="control-label">Owner</label>
<div class="controls">
<input type="text" class="span3" name="owner" [% HTML.attributes(value => project.owner.username || c.user.username) %]></input>
<input type="text" class="span3" name="owner" [% HTML.attributes(value => project.owner.username || c.user.username) %]/>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
<button id="submit-project" type="submit" class="btn btn-primary">
<i class="icon-ok icon-white"></i>
[%IF create %]Create[% ELSE %]Apply changes[% END %]
[%IF create %]Create project[% ELSE %]Apply changes[% END %]
</button>
[% IF !create %]
<button id="delete-project" type="submit" class="btn btn-danger" name="submit" value="delete">
<i class="icon-trash icon-white"></i>
Delete this project
</button>
<script type="text/javascript">
$("#delete-project").click(function() {
return confirm("Are you sure you want to delete this project?");
});
</script>
[% END %]
</div>
</fieldset>
</form>
<script type="text/javascript">
$("#submit-project").click(function() {
redirectJSON({
[% IF create %]
url: "[% c.uri_for('/project' '.new') %]",
[% ELSE %]
url: "[% c.uri_for('/project' project.name) %]",
[% END %]
data: $(this).parents("form").serialize(),
type: 'PUT'
});
return false;
});
</script>
[% END %]

View File

@ -9,14 +9,14 @@
<div class="control-group">
<label class="control-label">Identifier</label>
<div class="controls">
<input type="text" class="span3" name="name" [% HTML.attributes(value => release.name) %]></input>
<input type="text" class="span3" name="name" [% HTML.attributes(value => release.name) %]/>
</div>
</div>
<div class="control-group">
<label class="control-label">Description</label>
<div class="controls">
<input type="text" class="span3" name="description" [% HTML.attributes(value => release.description) %]></input>
<input type="text" class="span3" name="description" [% HTML.attributes(value => release.description) %]/>
</div>
</div>
@ -30,7 +30,7 @@
<div class="releaseMember control-group">
<label class="control-label">Build [% m.build.id %] Label</label>
<div class="controls">
<input type="text" class="span3" name="member-[% m.build.id %]-description" [% HTML.attributes(value => m.description) %]></input>
<input type="text" class="span3" name="member-[% m.build.id %]-description" [% HTML.attributes(value => m.description) %]/>
<button class="btn btn-warning" type="button" onclick='$(this).parents(".releaseMember").remove()'><i class="icon-trash icon-white"></i></button>
</div>
</div>

View File

@ -26,14 +26,14 @@
<div class="control-group">
<label class="control-label">Identifier</label>
<div class="controls">
<input type="text" class="span3" name="name" [% HTML.attributes(value => view.name) %]></input>
<input type="text" class="span3" name="name" [% HTML.attributes(value => view.name) %]/>
</div>
</div>
<div class="control-group">
<label class="control-label">Description</label>
<div class="controls">
<input type="text" class="span3" name="description" [% HTML.attributes(value => view.description) %]></input>
<input type="text" class="span3" name="description" [% HTML.attributes(value => view.description) %]/>
</div>
</div>

View File

@ -2,7 +2,7 @@
[% USE HTML %]
[% FOREACH error IN errors %]
<div class="alert alert-error">[% HTML.escape(error) %]</div>
<div class="alert alert-error">[% HTML.escape(error).replace('\n', '<br/>') %]</div>
[% END %]
[% END %]

View File

@ -1,80 +0,0 @@
[% WRAPPER layout.tt title="Errors" %]
[% PROCESS common.tt %]
<p>This page provides a quick way to see how FUBARed your packages
are. It shows job expressions that dont evaluate properly and jobs
that dont build.</p>
[% haveErrors = 0 %]
[% IF brokenJobsets && brokenJobsets.size > 0; haveErrors = 1 %]
<h2>Evaluation errors in jobsets</h2>
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>Name</th>
<th>Error</th>
</tr>
</thead>
<tdata>
[% FOREACH j IN brokenJobsets %]
<tr>
<td>[% INCLUDE renderFullJobsetName project=j.project.name jobset=j.name %]</td>
<td>
<pre class="error">[% HTML.escape(j.errormsg) %]</pre>
</td>
</tr>
[% END %]
</tdata>
</table>
[% END %]
[% IF brokenJobs && brokenJobs.size > 0; haveErrors = 1 %]
<h2>Evaluation errors in jobs</h2>
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>Name</th>
<th>Error</th>
</tr>
</thead>
<tdata>
[% FOREACH j IN brokenJobs %]
<tr>
<td>[% INCLUDE renderFullJobName project=j.project.name jobset=j.jobset.name job=j.name %]</td>
<td>
<pre class="error">[% HTML.escape(j.errormsg) %]</pre>
</td>
</tr>
[% END %]
</tdata>
</table>
[% END %]
[% IF brokenBuilds && brokenBuilds.size > 0; haveErrors = 1 %]
<h2>Broken builds</h2>
[% INCLUDE renderBuildList builds=brokenBuilds showStatusChange=1 hideProjectName=project hideJobsetName=jobset hideJobName=job %]
[% END %]
[% IF !haveErrors %]
<p><strong>There are currently no problems.</strong></p>
[% END %]
[% END %]

View File

@ -1,9 +1,15 @@
[% WRAPPER layout.tt title="Job $project.name:$jobset.name:$job.name" %]
[% WRAPPER layout.tt
title="Job $project.name:$jobset.name:$job.name"
starUri=c.uri_for(c.controller('Job').action_for('star'), c.req.captures)
%]
[% PROCESS common.tt %]
[% hideProjectName=1 hideJobsetName=1 hideJobName=1 %]
<ul class="nav nav-tabs">
<li class="active"><a href="#tabs-status" data-toggle="tab">Status</a></li>
[% IF constituentJobs.size > 0 %]
<li><a href="#tabs-constituents" data-toggle="tab">Constituents</a></li>
[% END %]
<li><a href="#tabs-links" data-toggle="tab">Links</a></li>
</ul>
@ -12,7 +18,7 @@
<div id="tabs-status" class="tab-pane active">
[% IF lastBuilds.size != 0 %]
<h3>Lastest builds</h3>
[% INCLUDE renderBuildList builds=lastBuilds showStatusChange=0
[% INCLUDE renderBuildList builds=lastBuilds
linkToAll=c.uri_for('/job' project.name jobset.name job.name 'all') %]
[% END %]
[% IF queuedBuilds.size != 0 %]
@ -21,12 +27,57 @@
[% END %]
</div>
[% IF constituentJobs.size > 0 %]
<div id="tabs-constituents" class="tab-pane">
<div class="well well-small">This is an <em>aggregate job</em>:
its success or failure is determined entirely by the result of
building its <em>constituent jobs</em>. The table below shows
the status of each constituent job for the [%
aggregates.keys.size %] most recent builds of the
aggregate.</div>
[% aggs = aggregates.keys.nsort.reverse %]
<table class="table table-striped table-condensed table-header-rotated">
<thead>
<tr>
<th>Job</th>
[% FOREACH agg IN aggs %]
<th class="rotate-45">
[% agg_ = aggregates.$agg %]
<div><span class="[% agg_.build.finished == 0 ? "text-info" : (agg_.build.buildstatus == 0 ? "text-success" : "text-warning") %] override-link">
<a href="[% c.uri_for('/build' agg) %]">[% agg %]</a>
</span></div></th>
[% END %]
</tr>
</thead>
<tbody>
[% FOREACH j IN constituentJobs %]
<tr>
<th style="width: 1em;">[% INCLUDE renderJobName project=project.name jobset=jobset.name job=j %]</th>
[% FOREACH agg IN aggs %]
<td>
[% r = aggregates.$agg.constituents.$j; IF r.id %]
<a href="[% c.uri_for('/build' r.id) %]">
[% INCLUDE renderBuildStatusIcon size=16 build=r %]
</a>
[% END %]
</td>
[% END %]
</tr>
[% END %]
</tbody>
</table>
</div>
[% END %]
<div id="tabs-links" class="tab-pane">
<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 %]
<li><a href="[% c.uri_for('/job' project.name jobset.name job.name 'latest-finished') %]">Latest successful build from a finished evaluation</a></li>
</ul>
</div>

View File

@ -4,10 +4,14 @@
<div class="btn-group pull-right">
<a class="btn btn-primary dropdown-toggle" data-toggle="dropdown" href="#"><i class="icon-white icon-eye-open"></i> Compare to...</a>
<ul class="dropdown-menu">
<li><a href="?">Preceding evaluation in the same jobset</tt></a></li>
<li><a href="?">Preceding evaluation in this jobset</a></li>
<li class="divider"></li>
<li><a href="?compare=-[% 24 * 60 * 60 %]">This jobset <strong>one day</strong> earlier</a></li>
<li><a href="?compare=-[% 7 * 24 * 60 * 60 %]">This jobset <strong>one week</strong> earlier</a></li>
<li><a href="?compare=-[% 31 * 24 * 60 * 60 %]">This jobset <strong>one month</strong> earlier</a></li>
[% IF project.jobsets_rs.count > 1 %]
<li class="divider"></li>
[% FOREACH j IN project.jobsets; IF j.name != jobset.name %]
[% FOREACH j IN project.jobsets.sort('name'); IF j.name != jobset.name %]
<li><a href="?compare=[% j.name %]">Jobset <tt>[% project.name %]:[% j.name %]</tt></a></li>
[% END; END %]
[% END %]
@ -19,81 +23,117 @@
project=otherEval.jobset.project.name jobset=otherEval.jobset.name %] evaluation <a href="[%
c.uri_for(c.controller('JobsetEval').action_for('view'),
[otherEval.id]) %]">[% otherEval.id %]</a>.</p>
[% ELSE %]
<div class="alert">Couldn't find an evaluation to compare to.</div>
[% END %]
<form class="form-search">
<input name="filter" type="text" class="input-large search-query" placeholder="Search jobs by name..." [% HTML.attributes(value => filter) %]/>
<input name="compare" type="hidden" [% HTML.attributes(value => otherEval.id) %]/>
<input name="full" type="hidden" [% HTML.attributes(value => full) %]/>
</form>
<ul class="nav nav-tabs">
<li class="active"><a href="#tabs-status" data-toggle="tab">Job status</a></li>
[% IF c.user_exists %]
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
Actions
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a href="[% c.uri_for(c.controller('JobsetEval').action_for('release'), [eval.id]) %]">Create a release from this evaluation</a></li>
<li><a href="[% c.uri_for(c.controller('JobsetEval').action_for('cancel'), [eval.id]) %]">Cancel all scheduled builds</a></li>
<li><a href="[% c.uri_for(c.controller('JobsetEval').action_for('restart_aborted'), [eval.id]) %]">Restart all aborted builds</a></li>
</ul>
</li>
[% END %]
[% IF aborted.size > 0 %]
<li><a href="#tabs-aborted" data-toggle="tab"><span class="text-warning">Aborted jobs ([% aborted.size %])</span></a></li>
[% END %]
[% IF nowFail.size > 0 %]
<li><a href="#tabs-now-fail" data-toggle="tab"><span class="text-warning">Newly-failing jobs ([% nowFail.size %])</span></a></li>
[% END %]
[% IF nowSucceed.size > 0 %]
<li><a href="#tabs-now-succeed" data-toggle="tab"><span class="text-success">Newly-succeeding jobs ([% nowSucceed.size %])</span></a></li>
[% END %]
[% IF new.size > 0 %]
<li><a href="#tabs-new" data-toggle="tab">New jobs ([% new.size %])</a></li>
[% END %]
[% IF removed.size > 0 %]
<li><a href="#tabs-removed" data-toggle="tab">Removed jobs ([% removed.size %])</a></li>
[% END %]
[% IF stillFail.size > 0 %]
<li><a href="#tabs-still-fail" data-toggle="tab">Still-failing jobs ([% stillFail.size %])</a></li>
[% END %]
[% IF stillSucceed.size > 0 %]
<li><a href="#tabs-still-succeed" data-toggle="tab">Still-succeeding jobs ([% stillSucceed.size %])</a></li>
[% END %]
[% IF unfinished.size > 0 %]
<li><a href="#tabs-unfinished" data-toggle="tab">Queued jobs ([% unfinished.size %])</a></li>
[% END %]
<li><a href="#tabs-inputs" data-toggle="tab">Inputs</a></li>
</ul>
[% BLOCK renderSome %]
[% INCLUDE renderBuildListHeader unsortable=1 %]
[% size = builds.size; max = full ? size : 250; %]
[% INCLUDE renderBuildListBody builds=builds.slice(0, (size > max ? max : size) - 1)
hideProjectName=1 hideJobsetName=1 %]
[% IF size > max; params = c.req.params; params.full = 1 %]
<tr><td class="centered" colspan="6"><a href="[% c.uri_for(c.controller('JobsetEval').action_for('view'), [eval.id], params) %]"><em>([% size - max %] more builds omitted)</em></a></td></tr>
[% END %]
[% INCLUDE renderBuildListFooter %]
[% END %]
<div class="tab-content">
<div id="tabs-status" class="tab-pane active">
<div id="tabs-aborted" class="tab-pane">
[% INCLUDE renderSome builds=aborted %]
</div>
[% BLOCK renderSome %]
[% size = builds.size; max = full ? size : 30; %]
[% INCLUDE renderBuildListBody builds=builds.slice(0, (size > max ? max : size) - 1)
hideProjectName=1 hideJobsetName=1 %]
[% IF size > max; params = c.req.params; params.full = 1 %]
<tr><td class="centered" colspan="6"><a href="[% c.uri_for(c.controller('JobsetEval').action_for('view'), [eval.id], params) %]"><em>([% size - max %] more builds omitted)</em></a></td></tr>
[% END %]
[% END %]
<div id="tabs-now-fail" class="tab-pane">
[% INCLUDE renderSome builds=nowFail %]
</div>
[% INCLUDE renderBuildListHeader unsortable=1 %]
<div id="tabs-now-succeed" class="tab-pane">
[% INCLUDE renderSome builds=nowSucceed %]
</div>
[% IF unfinished.size > 0 %]
<tr><th class="subheader" colspan="6"><strong>Queued</strong> jobs</th></tr>
[% INCLUDE renderSome builds=unfinished %]
[% END %]
<div id="tabs-new" class="tab-pane">
[% INCLUDE renderSome builds=new %]
</div>
[% IF new.size > 0 %]
<tr><th class="subheader" colspan="6"><strong>New</strong> jobs</th></tr>
[% INCLUDE renderSome builds=new %]
[% END %]
<div id="tabs-removed" class="tab-pane">
<table class="table table-striped table-condensed clickable-rows">
<thead>
<tr><th>Job</th><th>System</th></tr>
</thead>
<tbody>
[% size = removed.size; max = full ? size : 250; %]
[% FOREACH j IN removed.slice(0,(size > max ? max : size) - 1) %]
<tr>
<td>[% INCLUDE renderJobName project=project.name jobset=jobset.name job=j.job %]</td>
<td><tt>[% j.system %]</tt></td>
</tr>
[% END %]
[% IF size > max; params = c.req.params; params.full = 1 %]
<tr><td class="centered" colspan="2"><a href="[% c.uri_for(c.controller('JobsetEval').action_for('view'), [eval.id], params) %]"><em>([% size - max %] more jobs omitted)</em></a></td></tr>
[% END %]
</tbody>
</table>
</div>
[% IF removed.size > 0 %]
<tr><th class="subheader" colspan="6"><strong>Removed</strong> jobs</th></tr>
[% size = removed.size; max = full ? size : 30; %]
[% FOREACH j IN removed.slice(0,(size > max ? max : size) - 1) %]
<tr>
<td colspan="2"></td>
<td colspan="2">[% INCLUDE renderJobName project=project.name jobset=jobset.name job=j.job %]</td>
<td colspan="2"><tt>[% j.system %]</tt></td>
</tr>
[% END %]
[% IF size > max; params = c.req.params; params.full = 1 %]
<tr><td class="centered" colspan="6"><a href="[% c.uri_for(c.controller('JobsetEval').action_for('view'), [eval.id], params) %]"><em>([% size - max %] more jobs omitted)</em></a></td></tr>
[% END %]
[% END %]
<div id="tabs-still-fail" class="tab-pane">
[% INCLUDE renderSome builds=stillFail %]
</div>
[% IF nowFail.size > 0 %]
<tr><th class="subheader" colspan="6">Jobs that now <strong>fail</strong></th></tr>
[% INCLUDE renderSome builds=nowFail %]
[% END %]
[% IF nowSucceed.size > 0 %]
<tr><th class="subheader" colspan="6">Jobs that now <strong>succeed</strong></th></tr>
[% INCLUDE renderSome builds=nowSucceed %]
[% END %]
[% IF stillFail.size > 0 %]
<tr><th class="subheader" colspan="6">Jobs that still <strong>fail</strong></th></tr>
[% INCLUDE renderSome builds=stillFail %]
[% END %]
[% IF stillSucceed.size > 0 %]
<tr><th class="subheader" colspan="6">Jobs that still <strong>succeed</strong></th></tr>
[% INCLUDE renderSome builds=stillSucceed %]
[% END %]
[% INCLUDE renderBuildListFooter %]
[% IF c.user_exists %]
<p>
<a class="btn" href="[% c.uri_for(c.controller('JobsetEval').action_for('release'), [eval.id]) %]">Release</a>
</p>
[% END %]
<div id="tabs-still-succeed" class="tab-pane">
[% INCLUDE renderSome builds=stillSucceed %]
</div>
<div id="tabs-unfinished" class="tab-pane">
[% INCLUDE renderSome builds=unfinished %]
</div>
<div id="tabs-inputs" class="tab-pane">

View File

@ -1,15 +1,69 @@
[% PROCESS common.tt %]
[% PROCESS common.tt; USE Math %]
<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 %]<br/>[% END %]
</blockquote>
</p>
<form class="form-search" id="filter-jobs">
<div class="input-append">
<input name="filter" type="text" class="input-large search-query" placeholder="Search jobs by name..." [% HTML.attributes(value => filter) %]/>
<button type="button" class="btn btn-info [% IF showInactive %]active[% END %]" id="active-toggle">Show inactive jobs</button>
</div>
&nbsp;
<img src="[% c.uri_for("/static/images/ajax-loader.gif") %]" alt="Loading..." style="display: none;" id="filter-loading" />
</form>
<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 %]<br/>[% END %]
</blockquote>
</p>
<script>
function setFilter(filter) {
$('#filter-loading').show();
if ($('#active-toggle').hasClass('active')) filter += '&amp;showInactive=1';
$('#tabs-jobs').load("[% c.uri_for('/jobset' project.name jobset.name "jobs-tab") %]", filter, function(response, status, xhr) {
if (status == "error") {
$('#[% tabName %]').html("<div class='alert alert-error'>Error loading tab: " + xhr.status + " " + xhr.statusText + "</div>");
}
});
};
$('#filter-jobs').submit(function() {
setFilter($('#filter-jobs').serialize());
return false;
});
$('#active-toggle').click(function() {
$(this).toggleClass('active');
$('#filter-jobs').submit();
});
</script>
[% IF jobs.size == 0 %]
<div class="alert">There are no matching jobs.</div>
[% ELSE %]
[% IF nrJobs > jobs.size %]
<div class="alert">Showing the first [% jobs.size %] jobs. <a href="javascript:setFilter('filter=%')">Show all [% nrJobs %] jobs...</a></div>
[% END %]
[% evalIds = evals.keys.nsort.reverse %]
<table class="table table-striped table-condensed table-header-rotated">
<thead>
<tr>
<th style="width: 1em;">Job</th>
[% FOREACH eval IN evalIds %]
<th class="rotate-45">
<div><span>
<a href="[% c.uri_for('/eval' eval) %]">[% eval %]</a>
</span></div></th>
[% END %]
</tr>
</thead>
<tbody>
[% FOREACH j IN jobs-%]
<tr>
<th><span [% IF inactiveJobs.$j %]class="muted override-link"[% END %]>[% INCLUDE renderJobName project=project.name jobset=jobset.name job=j %]</span></th>
[% FOREACH eval IN evalIds %]
<td>[% r = evals.$eval.$j; IF r.id %]<a href="[% c.uri_for('/build' r.id) %]">[% INCLUDE renderBuildStatusIcon size=16 build=r %]</a>[% END %]</td>
[% END %]
</tr>
[% END %]
</tbody>
</table>
[% END %]

View File

@ -1,23 +0,0 @@
[% PROCESS common.tt %]
<table class="table table-striped table-condensed">
<thead><tr><th>Job</th>[% FOREACH s IN systems %]<th>[% s.system %]</th>[% END %]</tr></thead>
<tbody>
[% FOREACH j IN activeJobsStatus %]
<tr>
<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 class="centered">
[% IF systemStatus != undef %]
<a href="[% c.uri_for('/build' j.get_column(system _ '-build') ) %]">
[% INCLUDE renderBuildStatusIcon buildstatus=systemStatus size=16 %]
</a>
[% END %]
</td>
[% END %]
</tr>
[% END %]
</tbody>
</table>

View File

@ -1,5 +1,6 @@
[% WRAPPER layout.tt title="Jobset $project.name:$jobset.name" %]
[% PROCESS common.tt %]
[% USE format %]
[% BLOCK renderJobsetInput %]
@ -11,7 +12,7 @@
[% INCLUDE renderSelection curValue=input.type param="$baseName-type" options=inputTypes %]
</td>
<td class="inputalts" id="[% baseName %]">
[% FOREACH alt IN input.jobsetinputalts %]
[% FOREACH alt IN input.search_related('jobsetinputalts', {}, { order_by => 'altnr' }) %]
<tt class="inputalt">
[% IF input.type == "string" %]
"[% HTML.escape(alt.value) %]"
@ -41,11 +42,25 @@
<ul class="nav nav-tabs">
<li class="active"><a href="#tabs-evaluations" data-toggle="tab">Evaluations</a></li>
[% IF jobset.errormsg %]
<li><a href="#tabs-errors" data-toggle="tab"><img src="/static/images/error_16.png" /> Evaluation errors</a></li>
[% IF c.user_exists %]
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
Actions
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
[% INCLUDE menuItem title="Edit configuration" icon="icon-edit" uri=c.uri_for(c.controller('Jobset').action_for('edit'), c.req.captures) %]
[% INCLUDE menuItem title="Delete this jobset" icon="icon-trash" uri="javascript:deleteJobset()" %]
[% INCLUDE menuItem title="Clone this jobset" uri=c.uri_for(c.controller('Jobset').action_for('edit'), c.req.captures, { clone => 1 }) %]
[% INCLUDE menuItem title="Evaluate this jobset" uri="javascript:confirmEvaluateJobset()" %]
</ul>
</li>
[% END %]
<li class="active"><a href="#tabs-evaluations" data-toggle="tab">Evaluations</a></li>
[% IF jobset.errormsg || jobset.fetcherrormsg %]
<li><a href="#tabs-errors" data-toggle="tab"><span class="text-warning">Evaluation errors</span></a></li>
[% END %]
<li><a href="#tabs-status" data-toggle="tab">Job status</a></li>
<li><a href="#tabs-jobs" data-toggle="tab">Jobs</a></li>
<li><a href="#tabs-configuration" data-toggle="tab">Configuration</a></li>
</ul>
@ -59,7 +74,7 @@
<th>Last checked:</th>
<td>
[% IF jobset.lastcheckedtime %]
[% INCLUDE renderDateTime timestamp = jobset.lastcheckedtime %], [% IF jobset.errormsg %]<em class="text-warning">with errors!</em>[% ELSE %]<em>no errors</em>[% END %]
[% INCLUDE renderDateTime timestamp = jobset.lastcheckedtime %], [% IF jobset.errormsg || jobset.fetcherrormsg %]<em class="text-warning">with errors!</em>[% ELSE %]<em>no errors</em>[% END %]
[% ELSE %]
<em>never</em>
[% END %]
@ -91,20 +106,20 @@
</div>
[% INCLUDE makeLazyTab tabName="tabs-status" uri=c.uri_for('/jobset' project.name jobset.name "status-tab") %]
[% IF jobset.errormsg %]
[% IF jobset.errormsg || jobset.fetcherrormsg %]
<div id="tabs-errors" class="tab-pane">
<p>Errors occurred at [% INCLUDE renderDateTime timestamp=jobset.errortime %].</p>
<pre class="alert alert-error">[% HTML.escape(jobset.errormsg) %]</pre>
<pre class="alert alert-error">[% HTML.escape(jobset.fetcherrormsg || jobset.errormsg) %]</pre>
</div>
[% END %]
<div id="tabs-configuration" class="tab-pane">
<a class="btn pull-right" href="[% c.uri_for('/jobset' project.name jobset.name "edit") %]"><i class="icon-edit"></i> Edit</a>
<table class="info-table">
<tr>
<th>State:</th>
<td>[% IF jobset.enabled == 0; "Disabled"; ELSIF jobset.enabled == 1; "Enabled"; ELSIF jobset.enabled == 2; "One-shot"; END %]</td>
</tr>
<tr>
<th>Description:</th>
<td>[% HTML.escape(jobset.description) %]</td>
@ -116,14 +131,14 @@
<tt>[% HTML.escape(jobset.nixexprinput) %]</tt>
</td>
</tr>
<tr>
<th>Enabled:</th>
<td>[% jobset.enabled ? "Yes" : "No" %]</td>
</tr>
<tr>
<th>Check interval:</th>
<td>[% jobset.checkinterval || "<em>disabled</em>" %]</td>
</tr>
<tr>
<th>Scheduling shares:</th>
<td>[% jobset.schedulingshares %] [% IF totalShares %] ([% f = format("%.2f"); f(jobset.schedulingshares / totalShares * 100) %]% out of [% totalShares %] shares)[% END %]</td>
</tr>
<tr>
<th>Enable email notification:</th>
<td>[% jobset.enableemail ? "Yes" : "No" %]</td>
@ -133,7 +148,7 @@
<td>[% HTML.escape(jobset.emailoverride) %]</td>
</tr>
<tr>
<th>Number of builds to keep:</th>
<th>Number of evaluations to keep:</th>
<td>[% jobset.keepnr %]</td>
</tr>
</table>
@ -145,4 +160,32 @@
</div>
<script>
function confirmEvaluateJobset() {
bootbox.confirm(
'Are you sure you want to force evaluation of this jobset?',
function(c) {
if (!c) return;
requestJSON({
url: "[% HTML.escape(c.uri_for('/api/push', { jobsets = project.name _ ':' _ jobset.name, force = "1" })) %]",
success: function(data) {
bootbox.alert("The jobset has been scheduled for evaluation.");
}
});
});
};
function deleteJobset() {
bootbox.confirm(
'Are you sure you want to delete this jobset?',
function(c) {
if (!c) return;
redirectJSON({
url: "[% c.uri_for(c.controller('Jobset').action_for('jobset'), c.req.captures) %]",
type: 'DELETE'
});
});
};
</script>
[% END %]

View File

@ -1,15 +0,0 @@
[% WRAPPER layout.tt title="Job status" _
(job ? " of job $project.name:$jobset.name:$job.name" :
jobset ? " of jobset $project.name:$jobset.name" :
project ? " of project $project.name" : "") %]
[% PROCESS common.tt %]
<p>Below are the latest builds for each job. It is ordered by the status
change time (the timestamp of the last build that had a different
build result status). That is, it shows the jobs that most recently
changed from failed to successful or vice versa first.</p>
[% INCLUDE renderBuildList builds=latestBuilds showStatusChange=1
hideProjectName=project hideJobsetName=jobset hideJobName=job %]
[% END %]

View File

@ -1,9 +1,6 @@
[% USE date %]
[% USE HTML %]
<?xml version="1.0" encoding="UTF-8"?>
[% PROCESS common.tt %]
<!DOCTYPE html>
<html lang="en">
@ -19,24 +16,25 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script type="text/javascript" src="/static/bootstrap/js/bootstrap.min.js"></script>
<link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
<script type="text/javascript" src="[% c.uri_for("/static/bootstrap/js/bootstrap.min.js") %]"></script>
<link href="[% c.uri_for("/static/bootstrap/css/bootstrap.min.css") %]" rel="stylesheet" />
<!-- hydra.css must be included before bootstrap-responsive to
make the @media rule work. -->
<link rel="stylesheet" href="/static/css/hydra.css" type="text/css" />
<link href="/static/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet" />
<link rel="stylesheet" href="[% c.uri_for("/static/css/hydra.css") %]" type="text/css" />
<link rel="stylesheet" href="[% c.uri_for("/static/css/rotated-th.css") %]" type="text/css" />
<link href="[% c.uri_for("/static/bootstrap/css/bootstrap-responsive.min.css") %]" rel="stylesheet" />
<style>
.popover { max-width: 40%; }
</style>
<script type="text/javascript" src="/static/js/bootbox.min.js"></script>
<script type="text/javascript" src="[% c.uri_for("/static/js/bootbox.min.js") %]"></script>
<link rel="stylesheet" href="/static/css/tree.css" type="text/css" />
<link rel="stylesheet" href="/static/css/logfile.css" type="text/css" />
<link rel="stylesheet" href="[% c.uri_for("/static/css/tree.css") %]" type="text/css" />
<link rel="stylesheet" href="[% c.uri_for("/static/css/logfile.css") %]" type="text/css" />
<script type="text/javascript" src="/static/js/common.js"></script>
<script type="text/javascript" src="[% c.uri_for("/static/js/common.js") %]"></script>
[% tracker %]
@ -68,24 +66,27 @@
<div class="container">
[% IF !hideHeader %]
<div class="page-header">
<h1><small>[% HTML.escape(title) %]</small></h1>
</div>
[% ELSE %]
<br />
[% END %]
[% IF flashMsg %]
<br />
<div class="alert alert-info">[% flashMsg %]</div>
[% END %]
[% IF successMsg %]
<br />
<div class="alert alert-success">[% successMsg %]</div>
[% END %]
[% IF errorMsg %]
<div class="alert alert-error">Error: [% errorMsg %]</div>
<br />
<div class="alert alert-warning">Error: [% errorMsg %]</div>
[% END %]
[% IF !hideHeader %]
<div class="page-header">
<h1><small>[% IF c.user_exists && starUri; INCLUDE makeStar; " "; END; HTML.escape(title) %]</small></h1>
</div>
[% ELSE %]
<br />
[% END %]
[% content %]
@ -93,7 +94,7 @@
<footer class="navbar">
<hr />
<small>
<em><a href="http://nixos.org/hydra" target="_new">Hydra</a> [% HTML.escape(version) %] (using [% HTML.escape(nixVersion) %]).</em>
<em><a href="http://nixos.org/hydra" target="_blank">Hydra</a> [% HTML.escape(version) %] (using [% HTML.escape(nixVersion) %]).</em>
[% IF c.user_exists %]
You are signed in as <tt>[% HTML.escape(c.user.username) %]</tt>.
[% END %]

View File

@ -13,14 +13,14 @@ You are already signed in as <tt>[% HTML.escape(c.user.username) %]</tt>.
<div class="control-group">
<label class="control-label">User name</label>
<div class="controls">
<input type="text" class="span3" name="username" value=""></input>
<input type="text" class="span3" name="username" value=""/>
</div>
</div>
<div class="control-group">
<label class="control-label">Password</label>
<div class="controls">
<input type="password" class="span3" name="password" value=""></input>
<input type="password" class="span3" name="password" value=""/>
</div>
</div>

View File

@ -40,7 +40,7 @@
<tr class="product">
<td>
<a href="[% contents %]">
<img src="/static/images/error_32.png" alt="Source" />
<img src="[% c.uri_for("/static/images/error_32.png") %]" alt="Source" />
Failed build produced output. Click here to inspect the output.
</a>
[% WRAPPER makePopover title="Help" classes="btn-mini" %]
@ -59,7 +59,7 @@
<td>
[% uri = c.uri_for('/build' build.id 'nix' 'pkg' "${build.nixname}-${build.system}.nixpkg") %]
<a href="[% uri %]">
<img src="/static/images/nix-build.png" alt="Source" />
<img src="[% c.uri_for("/static/images/nix-build.png") %]" alt="Source" />
One-click install of Nix package <tt>[% build.nixname %]</tt>
</a>
[% WRAPPER makePopover title="Help" classes="btn-mini" %]
@ -87,7 +87,7 @@
[% uri = c.uri_for('/build' build.id 'nix' 'closure' filename ) %]
<a href="[% uri %]">
<img src="/static/images/nix-build.png" alt="Source" />
<img src="[% c.uri_for("/static/images/nix-build.png") %]" alt="Source" />
Nix closure of path <tt>[% product.path %]</tt>
</a>
@ -127,17 +127,17 @@
<a href="[% uri %]">
[% SWITCH product.subtype %]
[% CASE "source-dist" %]
<img src="/static/images/source-dist.png" alt="Source" /> Source distribution <tt>[% product.name %]</tt>
<img src="[% c.uri_for("/static/images/source-dist.png") %]" alt="Source" /> Source distribution <tt>[% product.name %]</tt>
[% CASE "rpm" %]
<img src="/static/images/rpm.png" alt="RPM" /> RPM package <tt>[% product.name %]</tt>
<img src="[% c.uri_for("/static/images/rpm.png") %]" alt="RPM" /> RPM package <tt>[% product.name %]</tt>
[% CASE "srpm" %]
<img src="/static/images/rpm.png" alt="Source RPM" /> Source RPM package <tt>[% product.name %]</tt>
<img src="[% c.uri_for("/static/images/rpm.png") %]" alt="Source RPM" /> Source RPM package <tt>[% product.name %]</tt>
[% CASE "deb" %]
<img src="/static/images/debian.png" alt="RPM" /> Debian package <tt>[% product.name %]</tt>
<img src="[% c.uri_for("/static/images/debian.png") %]" alt="RPM" /> Debian package <tt>[% product.name %]</tt>
[% CASE "iso" %]
<img src="/static/images/iso.png" alt="ISO" /> ISO-9660 CD/DVD image <tt>[% product.name %]</tt>
<img src="[% c.uri_for("/static/images/iso.png") %]" alt="ISO" /> ISO-9660 CD/DVD image <tt>[% product.name %]</tt>
[% CASE "binary-dist" %]
<img src="/static/images/binary-dist.png" alt="Binary distribution" /> Binary distribution <tt>[% product.name %]</tt>
<img src="[% c.uri_for("/static/images/binary-dist.png") %]" alt="Binary distribution" /> Binary distribution <tt>[% product.name %]</tt>
[% CASE DEFAULT %]
File <tt>[% product.name %]</tt> of type <tt>[% product.subtype %]</tt>
[% END %]
@ -160,7 +160,7 @@
<tr class="product">
<td>
<a href="[% uri %]">
<img src="/static/images/report.png" alt="Report" />
<img src="[% c.uri_for("/static/images/report.png") %]" alt="Report" />
[% SWITCH product.subtype %]
[% CASE "coverage" %]
Code coverage analysis report
@ -177,9 +177,9 @@
<td>
<a href="[% uri %]">
[% IF product.type == "doc-pdf" %]
<img src="/static/images/pdf.png" alt="PDF document" />
<img src="[% c.uri_for("/static/images/pdf.png") %]" alt="PDF document" />
[% ELSE %]
<img src="/static/images/document.png" alt="Document" />
<img src="[% c.uri_for("/static/images/document.png") %]" alt="Document" />
[% END %]
[% SWITCH product.subtype %]
[% CASE "readme" %]

View File

@ -2,16 +2,33 @@
[% PROCESS common.tt %]
<ul class="nav nav-tabs">
[% IF c.user_exists %]
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
Actions
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
[% INCLUDE menuItem title="Edit configuration" icon="icon-edit" uri=c.uri_for(c.controller('Project').action_for('edit'), c.req.captures) %]
[% INCLUDE menuItem title="Delete this project" icon="icon-trash" uri="javascript:deleteProject()" %]
[% INCLUDE menuItem title="Create jobset" icon="icon-plus" uri=c.uri_for(c.controller('Project').action_for('create_jobset'), c.req.captures) %]
[% INCLUDE menuItem title="Create release" icon="icon-plus" uri=c.uri_for(c.controller('Project').action_for('create_release'), c.req.captures) %]
</ul>
</li>
[% END %]
<li class="active"><a href="#tabs-project" data-toggle="tab">Jobsets</a></li>
<li><a href="#tabs-configuration" data-toggle="tab">Configuration</a></li>
<li><a href="#tabs-releases" data-toggle="tab">Releases</a></li>
<li><a href="#tabs-views" data-toggle="tab">Views</a></li>
[% IF views.size > 0 %]
<li><a href="#tabs-views" data-toggle="tab">Views</a></li>
[% END %]
</ul>
<div class="tab-content">
<div id="tabs-project" class="tab-pane active">
[% IF project.jobsets.size > 0 %]
[% IF project.jobsets %]
<p>This project has the following jobsets:</p>
<table class="table table-striped table-condensed clickable-rows">
@ -30,18 +47,18 @@
<tr>
<td>
[% IF j.get_column('nrscheduled') > 0 %]
<img src="/static/images/help_16.png" alt="Scheduled" />
<img src="[% c.uri_for("/static/images/help_16.png") %]" alt="Scheduled" />
[% ELSIF j.get_column('nrfailed') == 0 %]
<img src="/static/images/checkmark_16.png" alt="Succeeded" />
<img src="[% c.uri_for("/static/images/checkmark_16.png") %]" alt="Succeeded" />
[% ELSIF j.get_column('nrfailed') > 0 && j.get_column('nrsucceeded') > 0 %]
<img src="/static/images/error_some_16.png" alt="Some Failed" />
<img src="[% c.uri_for("/static/images/error_some_16.png") %]" alt="Some Failed" />
[% ELSE %]
<img src="/static/images/error_16.png" alt="All Failed" />
<img src="[% c.uri_for("/static/images/error_16.png") %]" alt="All Failed" />
[% END %]
</td>
<td><span class="[% IF !j.enabled %]disabled-jobset[% END %] [%+ IF j.hidden %]hidden-jobset[% END %]">[% INCLUDE renderJobsetName project=project.name jobset=j.name inRow=1 %]</span></td>
<td>[% HTML.escape(j.description) %]</td>
<td>[% INCLUDE renderDateTime timestamp = j.lastcheckedtime %]</td>
<td>[% IF j.lastcheckedtime; INCLUDE renderDateTime timestamp = j.lastcheckedtime; ELSE; "-"; END %]</td>
[% IF j.get_column('nrtotal') > 0 %]
[% successrate = ( j.get_column('nrsucceeded') / j.get_column('nrtotal') )*100 %]
[% IF j.get_column('nrscheduled') > 0 %]
@ -78,7 +95,6 @@
</div>
<div id="tabs-configuration" class="tab-pane">
<a class="btn pull-right" href="[% c.uri_for('/project' project.name "edit") %]"><i class="icon-edit"></i> Edit</a>
<table class="info-table">
<tr>
<th>Display name:</th>
@ -138,12 +154,6 @@
[% END %]
[% IF c.user_exists %]
<p><a class="btn" href="[% c.uri_for('/project' project.name 'create-release') %]">
<i class="icon-plus"></i> Create a release
</a></p>
[% END %]
</div>
<div id="tabs-views" class="tab-pane">
@ -176,4 +186,18 @@
</div>
<script>
function deleteProject() {
bootbox.confirm(
'Are you sure you want to delete this project?',
function(c) {
if (!c) return;
redirectJSON({
url: "[% c.uri_for('/project' project.name) %]",
type: 'DELETE'
});
});
};
</script>
[% END %]

View File

@ -141,11 +141,14 @@ fi
args+=(--arg '[% input.name %]' "{ outPath = $inputDir; rev = \"[% input.revision %]\"; }")
[% ELSIF input.type == "string" %]
args+=(--arg '[% input.name %]' '"[% input.value %]"')
args+=(--arg '[% input.name %]' '"[% input.value %]"') # FIXME: escape
[% ELSIF input.type == "boolean" %]
args+=(--arg '[% input.name %]' '[% input.value %]')
[% ELSIF input.type == "nix" %]
args+=(--arg '[% input.name %]' '[% input.value %]') # FIXME: escape
[% ELSE %]
echo "$0: input [% input.name %] has unsupported type [% input.type %]" >&2
exit 1

View File

@ -91,3 +91,20 @@ div.news-item:not(:first-child) {
td.nowrap {
white-space: nowrap;
}
.override-link a {
color: inherit;
}
.actions {
font-weight: bold;
}
.star {
color: black;
font-size: 110%;
}
.star:hover {
cursor: pointer;
}

View File

@ -0,0 +1,52 @@
/* Rotated table headers, borrowed from http://jimmybonney.com/articles/column_header_rotation_css */
.tab-content {
margin-right: 5em;
overflow: visible;
}
td.centered {
text-align: center;
}
.table-header-rotated th.rotate-45{
height: 80px;
width: 40px;
min-width: 40px;
max-width: 40px;
position: relative;
vertical-align: bottom;
padding: 0;
font-size: 100%;
line-height: 0.9;
}
.table-header-rotated th.rotate-45 > div {
position: relative;
top: 0px;
left: 40px; /* 80 * tan(45) / 2 = 40 where 80 is the height on the cell and 45 is the transform angle*/
height: 100%;
-ms-transform:skew(-45deg,0deg);
-moz-transform:skew(-45deg,0deg);
-webkit-transform:skew(-45deg,0deg);
-o-transform:skew(-45deg,0deg);
transform:skew(-45deg,0deg);
overflow: hidden;
border-left: 1px solid #dddddd;
}
.table-header-rotated th.rotate-45 span {
-ms-transform:skew(45deg,0deg) rotate(315deg);
-moz-transform:skew(45deg,0deg) rotate(315deg);
-webkit-transform:skew(45deg,0deg) rotate(315deg);
-o-transform:skew(45deg,0deg) rotate(315deg);
transform:skew(45deg,0deg) rotate(315deg);
position: absolute;
bottom: 30px; /* 40 cos(45) = 28 with an additional 2px margin*/
left: -25px; /*Because it looked good, but there is probably a mathematical link here as well*/
display: inline-block;
// width: 100%;
width: 85px; /* 80 / cos(45) - 40 cos (45) = 85 where 80 is the height of the cell, 40 the width of the cell and 45 the transform angle*/
text-align: left;
// white-space: nowrap; /*whether to display in one line or not*/
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

View File

@ -50,18 +50,98 @@ $(document).ready(function() {
$(".hydra-popover").popover({});
/* Ensure that pressing the back button on another page
navigates back to the previously selected tab on this
page. */
$(function() {
if (window.location.hash) {
$(".nav-tabs a[href='" + window.location.hash + "']").tab('show');
}
/* If no tab is active, show the first one. */
$(".nav-tabs").each(function() {
if ($("li.active", this).length > 0) return;
$("a", $(this).children("li:not(.dropdown)").first()).tab('show');
});
/* Ensure that pressing the back button on another page
navigates back to the previously selected tab on this
page. */
$('.nav-tabs').bind('show', function(e) {
var pattern = /#.+/gi;
var id = e.target.toString().match(pattern)[0];
history.replaceState(null, "", id);
});
});
/* Automatically set Bootstrap radio buttons from hidden form controls. */
$('div[data-toggle="buttons-radio"] input[type="hidden"]').map(function(){
$('button[value="' + $(this).val() + '"]', $(this).parent()).addClass('active');
});
/* Automatically update hidden form controls from Bootstrap radio buttons. */
$('div[data-toggle="buttons-radio"] .btn').click(function(){
$('input', $(this).parent()).val($(this).val());
});
$(".star").click(function(event) {
var star = $(this);
var active = star.text() != '★';
requestJSON({
url: star.attr("data-post"),
data: active ? "star=1" : "star=0",
type: 'POST',
success: function(res) {
if (active) {
star.text('★');
} else {
star.text('☆');
}
}
});
});
});
var tabsLoaded = {};
function makeLazyTab(tabName, uri) {
$('.nav-tabs').bind('show', function(e) {
var pattern = /#.+/gi;
var id = e.target.toString().match(pattern)[0];
history.replaceState(null, "", id);
});
$(function() {
if (window.location.hash) {
$(".nav a[href='" + window.location.hash + "']").tab('show');
if (id == '#' + tabName && !tabsLoaded[id]) {
tabsLoaded[id] = 1;
$('#' + tabName).load(uri, function(response, status, xhr) {
if (status == "error") {
$('#' + tabName).html("<div class='alert alert-error'>Error loading tab: " + xhr.status + " " + xhr.statusText + "</div>");
}
});
}
})
});
});
};
function escapeHTML(s) {
return $('<div/>').text(s).html();
};
function requestJSON(args) {
args.dataType = 'json';
args.error = function(data) {
json = {};
try {
if (data.responseText)
json = $.parseJSON(data.responseText);
} catch (err) {
}
if (json.error)
bootbox.alert(escapeHTML(json.error));
else if (data.responseText)
bootbox.alert("Server error: " + escapeHTML(data.responseText));
else
bootbox.alert("Unknown server error!");
};
return $.ajax(args);
};
function redirectJSON(args) {
args.success = function(data) {
window.location = data.redirect;
};
return requestJSON(args);
};

View File

@ -1,19 +1,17 @@
[% BLOCK menuItem %]
<li class="[% IF "${root}${curUri}" == uri %]active[% END %]" [% IF confirmmsg %]onclick="javascript:return confirm('[% confirmmsg %]')"[% END %]>
<a [% HTML.attributes(href => uri) %]>[% title %]</a>
</li>
[% END %]
[% BLOCK makeSubMenu %]
<li class="dropdown">
<a class="dropdown-toggle" href="#" data-toggle="dropdown">[% title %]<b class="caret"></b></a>
<ul id="left-menu" class="dropdown-menu">
<ul class="dropdown-menu">
[% content %]
</ul>
</li>
[% END %]
<ul class="nav pull-left" id="top-menu">
<ul class="nav pull-left">
[% IF c.user_exists %]
[% INCLUDE menuItem uri = c.uri_for(c.controller('User').action_for('dashboard'), [c.user.username]) title = "Dashboard" %]
[% END %]
[% WRAPPER makeSubMenu title="Status" %]
[% INCLUDE menuItem
@ -39,15 +37,7 @@
<li class="divider"></li>
[% INCLUDE menuItem uri = c.uri_for(c.controller('Project').action_for('project'), [project.name]) title = "Overview" %]
[% INCLUDE menuItem uri = c.uri_for(c.controller('Project').action_for('all'), [project.name]) title = "Latest builds" %]
[% INCLUDE menuItem uri = c.uri_for(c.controller('Project').action_for('jobstatus'), [project.name]) title = "Job status" %]
[% INCLUDE menuItem uri = c.uri_for(c.controller('Project').action_for('errors'), [project.name]) title = "Errors" %]
<li class="divider"></li>
[% INCLUDE menuItem uri = c.uri_for('/project' project.name 'channel' 'latest') title = "Channel" %]
[% IF c.user_exists %]
<li class="divider"></li>
[% INCLUDE menuItem uri = c.uri_for('/project' project.name 'edit') title="Edit" %]
[% INCLUDE menuItem uri = c.uri_for(c.controller('Project').action_for('create_jobset'), [project.name]) title = "Create jobset" %]
[% END %]
[% END %]
[% END %]
@ -64,40 +54,7 @@
[% INCLUDE menuItem
uri = c.uri_for(c.controller('Jobset').action_for('all'), [project.name, jobset.name])
title = "Latest builds" %]
[% INCLUDE menuItem
uri = c.uri_for(c.controller('Jobset').action_for('jobstatus'), [project.name, jobset.name])
title = "Job status" %]
[% INCLUDE menuItem
uri = c.uri_for(c.controller('Jobset').action_for('errors'), [project.name, jobset.name])
title = "Errors" %]
<li class="divider"></li>
[% INCLUDE menuItem uri = c.uri_for('/jobset' project.name jobset.name 'channel' 'latest') title = "Channel" %]
[% IF c.user_exists %]
<li class="divider"></li>
[% INCLUDE menuItem uri = c.uri_for('/jobset' project.name jobset.name 'edit') title="Edit" %]
[% INCLUDE menuItem uri = c.uri_for('/jobset' project.name jobset.name 'clone') title="Clone" %]
<script>
function confirmEvaluateJobset() {
bootbox.confirm(
'Are you sure you want to force evaluation of this jobset?',
function(c) {
if (!c) return;
$.post("[% c.uri_for('/api/push', { jobsets = project.name _ ':' _ jobset.name, force = "1" }) %]")
.done(function(data) {
if (data.error)
bootbox.alert("Unable to schedule the jobset for evaluation: " + data.error);
else
bootbox.alert("The jobset has been scheduled for evaluation.");
})
.fail(function() { bootbox.alert("Server request failed!"); });
});
return;
};
</script>
[% INCLUDE menuItem title="Evaluate" uri = "javascript:confirmEvaluateJobset()" %]
[% END %]
[% END %]
[% END %]
@ -111,54 +68,10 @@
[% INCLUDE menuItem
uri = c.uri_for(c.controller('Job').action_for('all'), [project.name, jobset.name, job.name])
title = "Latest builds" %]
[% INCLUDE menuItem
uri = c.uri_for(c.controller('Job').action_for('jobstatus'), [project.name, jobset.name, job.name])
title = "Job status" %]
[% INCLUDE menuItem
uri = c.uri_for(c.controller('Job').action_for('errors'), [project.name, jobset.name, job.name])
title = "Errors" %]
<li class="divider"></li>
[% INCLUDE menuItem uri = c.uri_for('/job' project.name jobset.name job.name 'channel' 'latest') title = "Channel" %]
[% END %]
[% END %]
[% IF build %]
[% WRAPPER makeSubMenu title="Build" %]
<li class="nav-header">#[% build.id %]</li>
<li class="divider"></li>
[% INCLUDE menuItem
uri = c.uri_for('/build' build.id)
title = "Overview" %]
[% IF c.user_exists %]
<li class="divider"></li>
[% INCLUDE menuItem
uri = c.uri_for('/build' build.id 'clone')
title = "Clone build" %]
[% IF available %]
[% IF build.keep %]
[% INCLUDE menuItem
uri = c.uri_for('/build' build.id 'keep' 0)
title = "Unkeep build" %]
[% ELSE %]
[% INCLUDE menuItem
uri = c.uri_for('/build' build.id 'keep' 1)
title = "Keep build" %]
[% END %]
[% END %]
[% IF build.finished %]
[% INCLUDE menuItem
uri = c.uri_for('/build' build.id 'restart')
title = "Restart build" %]
[% END %]
[% IF !build.finished %]
[% INCLUDE menuItem
uri = c.uri_for('/build' build.id 'cancel')
title = "Cancel build" %]
[% END %]
[% END %]
[% END %]
[% END %]
[% IF c.user_exists && c.check_user_roles('admin') %]
[% WRAPPER makeSubMenu title="Admin" %]
[% IF c.check_user_roles('admin') %]
@ -182,18 +95,23 @@
class = "" %]
[% INCLUDE menuItem
uri = c.uri_for(c.controller('Admin').action_for('clear_queue_non_current'))
title = "Clear all non-running old builds from queue"
title = "Clear scheduled non-current builds from queue"
confirmmsg = "Are you sure you want to clear the queue?"
class = "" %]
[% INCLUDE menuItem
uri = c.uri_for(c.controller('Admin').action_for('clearvcscache'))
title = "Clear VCS caches"
confirmmsg = "Are you sure you want to clear the VCS cache?"
class = "" %]
[% END %]
[% END %]
</ul>
<ul class="nav pull-right" id="top-menu">
<ul class="nav pull-right">
<form class="navbar-search" action="[% c.uri_for('/search') %]">
<input name="query" type="text" class="search-query span2" placeholder="Search" [% HTML.attributes(value => c.req.params.query) %]></input>
<input name="query" type="text" class="search-query span2" placeholder="Search" [% HTML.attributes(value => c.req.params.query) %]/>
</form>
[% IF c.user_exists %]

View File

@ -9,7 +9,7 @@
[% BREAK IF checked %]
[% END %]
[% IF checked %]
SELECTED
selected="selected"
[% END %]
>[% role %]</option>
[% END %]
@ -22,7 +22,7 @@
<div class="control-group">
<label class="control-label">User name</label>
<div class="controls">
<input type="text" class="span3" name="username" [% HTML.attributes(value => username) %]></input>
<input type="text" class="span3" name="username" [% HTML.attributes(value => username) %]/>
</div>
</div>
[% END %]
@ -30,7 +30,7 @@
<div class="control-group">
<label class="control-label">Full name</label>
<div class="controls">
<input type="text" class="span3" name="fullname" [% HTML.attributes(value => fullname) %]></input>
<input type="text" class="span3" name="fullname" [% HTML.attributes(value => fullname) %]/>
</div>
</div>
@ -38,14 +38,14 @@
<div class="control-group">
<label class="control-label">Password</label>
<div class="controls">
<input type="password" class="span3" name="password" value=""></input>
<input type="password" class="span3" name="password" value=""/>
</div>
</div>
<div class="control-group">
<label class="control-label">Confirm password</label>
<div class="controls">
<input type="password" class="span3" name="password2" value=""></input>
<input type="password" class="span3" name="password2" value=""/>
</div>
</div>
[% END %]
@ -54,7 +54,7 @@
<div class="control-group">
<label class="control-label">Email</label>
<div class="controls">
<input type="text" class="span3" name="emailaddress" [% HTML.attributes(value => user.emailaddress) %]></input>
<input type="text" class="span3" name="emailaddress" [% HTML.attributes(value => user.emailaddress) %]/>
</div>
</div>
-->
@ -63,7 +63,7 @@
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="emailonerror" [% IF emailonerror; 'checked="checked"'; END %]></input>Receive evaluation error notifications
<input type="checkbox" name="emailonerror" [% IF emailonerror; 'checked="checked"'; END %]/>Receive evaluation error notifications
</label>
</div>
</div>
@ -73,7 +73,7 @@
<div class="control-group">
<label class="control-label">Roles</label>
<div class="controls">
<select multiple name="roles" class="span3" [% IF !c.check_user_roles('admin') %]disabled="1"[% END %]>
<select multiple="multiple" name="roles" class="span3" [% IF !c.check_user_roles('admin') %]disabled="1"[% END %]>
[% INCLUDE roleoption role="admin" %]
[% INCLUDE roleoption role="create-project" %]
</select>
@ -91,7 +91,7 @@
<div class="control-group">
<label class="control-label">Type the digits shown in the image above</label>
<div class="controls">
<input type="text" class="span3" name="captcha" value=""></input>
<input type="text" class="span3" name="captcha" value=""/>
</div>
</div>
[% END %]
@ -136,8 +136,9 @@
});
</script>
[% END %]
</div>
</p>
</div>
</fieldset>
</form>

View File

@ -27,11 +27,11 @@
<tr>
<td>
[% IF result.status == 0 %]
<img src="/static/images/checkmark_16.png" />
<img src="[% c.uri_for("/static/images/checkmark_16.png") %]" />
[% ELSIF result.status == 1 %]
<img src="/static/images/error_16.png" />
<img src="[% c.uri_for("/static/images/error_16.png") %]" />
[% ELSIF result.status == 2 %]
<img src="/static/images/help_16.png" />
<img src="[% c.uri_for("/static/images/help_16.png") %]" />
[% END %]
</td>
<td><a class="row-link" href="[% c.uri_for('/view' project.name view.name result.id) %]">[% result.id %]</a></td>
@ -48,9 +48,9 @@
[% IF j.build %]
<a href="[% c.uri_for('/build' j.build.id) %]">
[% IF j.build.get_column('buildstatus') == 0 %]
<img src="/static/images/checkmark_16.png" />
<img src="[% c.uri_for("/static/images/checkmark_16.png") %]" />
[% ELSE %]
<img src="/static/images/error_16.png" />
<img src="[% c.uri_for("/static/images/error_16.png") %]" />
[% END %]
</a>
[% END %]