From 1657f6fff491cf21a0e1446c47faab534a907064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:01:08 +0200 Subject: [PATCH 01/12] hydra-queue-runner: Fix crash when < > are in hydra-build-products This prevents a forever-hanging build (don't know why) when < or > are in the path of hydra-build-products. This is not to prevent any XSS (see next commits), just to prevent the DOS (if you can even call it that). --- src/hydra-queue-runner/build-result.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index b0695e8b..a3fc3c38 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -51,8 +51,8 @@ BuildOutput getBuildOutput( "[[:space:]]+" "([a-zA-Z0-9_-]+)" // subtype (e.g. "readme") "[[:space:]]+" - "(\"[^\"]+\"|[^[:space:]\"]+)" // path (may be quoted) - "([[:space:]]+([^[:space:]]+))?" // entry point + "(\"[^\"]+\"|[^[:space:]<>\"]+)" // path (may be quoted) + "([[:space:]]+([^[:space:]<>]+))?" // entry point , std::regex::extended); for (auto & output : outputs) { From 85b330be41b72a7baf07fb0cdd0889302a3fca5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:02:48 +0200 Subject: [PATCH 02/12] hydra-queue-runner: Fix potential UB Removing two characters from a string when it starts with " can lead to a substring call with -1 --- src/hydra-queue-runner/build-result.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index a3fc3c38..1874b085 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -78,7 +78,7 @@ BuildOutput getBuildOutput( product.type = match[1]; product.subtype = match[2]; std::string s(match[3]); - product.path = s[0] == '"' ? std::string(s, 1, s.size() - 2) : s; + product.path = s[0] == '"' && s.back() == '"' ? std::string(s, 1, s.size() - 2) : s; product.defaultPath = match[5]; /* Ensure that the path exists and points into the Nix From 552ca356aeb9224f1bc94595a940cd6d09d50dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:05:33 +0200 Subject: [PATCH 03/12] hydra-queue-runner: Verify product names in hydra-build-products --- src/hydra-queue-runner/build-result.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index 1874b085..f77e7dde 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -93,6 +93,8 @@ BuildOutput getBuildOutput( if (file == narMembers.end()) continue; product.name = product.path == store->printStorePath(output) ? "" : baseNameOf(product.path); + if (!std::regex_match(product.name, std::regex("[a-zA-Z0-9.@:_ -]*"))) + product.name = ""; if (file->second.type == SourceAccessor::Type::tRegular) { product.isRegular = true; From a0ba36db79aaf49fffdf3c45522d54e06bcc3d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:06:13 +0200 Subject: [PATCH 04/12] hydra-queue-runner: Validate release name --- src/hydra-queue-runner/build-result.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index f77e7dde..a5dec9c5 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -129,8 +129,9 @@ BuildOutput getBuildOutput( if (file == narMembers.end() || file->second.type != SourceAccessor::Type::tRegular) continue; - res.releaseName = trim(file->second.contents.value()); - // FIXME: validate release name + auto contents = trim(file->second.contents.value()); + if (std::regex_match(contents, std::regex("[a-zA-Z0-9.@:_-]+"))) + res.releaseName = contents; } /* Get metrics. */ From 0d3842aa2fa7f0f497678eb3278ecdc84803b6c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:07:25 +0200 Subject: [PATCH 05/12] hydra-queue-runner: Validate metric name in hydra-metrics --- src/hydra-queue-runner/build-result.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index a5dec9c5..937cdd94 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -143,8 +143,10 @@ BuildOutput getBuildOutput( for (auto & line : tokenizeString(file->second.contents.value(), "\n")) { auto fields = tokenizeString>(line); if (fields.size() < 2) continue; + if (!std::regex_match(fields[0], std::regex("[a-zA-Z0-9._-]+"))) + continue; BuildMetric metric; - metric.name = fields[0]; // FIXME: validate + metric.name = fields[0]; metric.value = atof(fields[1].c_str()); // FIXME metric.unit = fields.size() >= 3 ? fields[2] : ""; res.metrics[metric.name] = metric; From 7c4f0ab01ad71851795bd257683373c1e5f0b973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:09:02 +0200 Subject: [PATCH 06/12] hydra-queue-runner: Validate hydra-metrics unit --- src/hydra-queue-runner/build-result.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index 937cdd94..73bf79f0 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -149,6 +149,8 @@ BuildOutput getBuildOutput( metric.name = fields[0]; metric.value = atof(fields[1].c_str()); // FIXME metric.unit = fields.size() >= 3 ? fields[2] : ""; + if (!std::regex_match(metric.unit, std::regex("[a-zA-Z0-9._%-]+"))) + metric.unit = ""; res.metrics[metric.name] = metric; } } From 5f226f3b6f49e5b10a2d25302e07f017c9a53213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:17:35 +0200 Subject: [PATCH 07/12] hydra-queue-runner: Validate metric type --- src/hydra-queue-runner/build-result.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index 73bf79f0..aa98acbb 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -147,7 +147,11 @@ BuildOutput getBuildOutput( continue; BuildMetric metric; metric.name = fields[0]; - metric.value = atof(fields[1].c_str()); // FIXME + try { + metric.value = std::stod(fields[1]); + } catch (...) { + continue; // skip this metric + } metric.unit = fields.size() >= 3 ? fields[2] : ""; if (!std::regex_match(metric.unit, std::regex("[a-zA-Z0-9._%-]+"))) metric.unit = ""; From 33b5c6fb413ad65f345c58fb1987f2a15af798eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:26:34 +0200 Subject: [PATCH 08/12] product-list: Escape untrusted values --- src/root/product-list.tt | 56 ++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/root/product-list.tt b/src/root/product-list.tt index 4d545b3e..97fe6141 100644 --- a/src/root/product-list.tt +++ b/src/root/product-list.tt @@ -1,17 +1,17 @@ [% BLOCK renderProductLinks %] URL: - [% uri %] + uri) %]>[% uri | html %] [% IF latestRoot %] Links to latest: [% uri2 = "${c.uri_for(latestRoot.join('/') 'download-by-type' product.type product.subtype)}" %] - [% uri2 %] + uri2) %]>[% uri2 | html %]
[% uri2 = "${c.uri_for(latestRoot.join('/') 'download' product.productnr)}" %] - [% uri2 %] + uri2) %]>[% uri2 | html %] [% END %] @@ -49,7 +49,7 @@ Error - + contents) %]> Failed build produced output. Click here to inspect the output. @@ -58,9 +58,9 @@

If you have Nix installed on your machine, this failed build output and all its dependencies can be unpacked into your local Nix store by doing:

-
$ curl [% uri %] | gunzip | nix-store --import
+
$ curl [% HTML.escape(uri) %] | gunzip | nix-store --import
-

The build output can then be found in the path [% product.path %].

+

The build output can then be found in the path [% product.path | html %].

[% END %] @@ -74,7 +74,7 @@ Nix package - [% HTML.escape(build.nixname) %] + [% build.nixname | html %] [% WRAPPER makePopover title="Help" classes="btn-secondary btn-sm" @@ -84,7 +84,7 @@
$ nix-env -i [%HTML.escape(product.path)%][% IF binaryCachePublicUri %] --option binary-caches [% HTML.escape(binaryCachePublicUri) %][% END %]
[% END %] [% IF localStore %] - Contents + contents) %]>Contents [% END %] @@ -100,8 +100,8 @@ [% filename = build.nixname _ (product.subtype ? "-" _ product.subtype : "") _ ".closure.gz" %] [% uri = c.uri_for('/build' build.id 'nix' 'closure' filename ) %] - - [% product.path %] + uri) %]> + [% product.path | html %] @@ -110,16 +110,16 @@ all its dependencies can be unpacked into your local Nix store by doing:

-
$ gunzip < [% filename %] | nix-store --import
+
$ gunzip < [% HTML.escape(filename) %] | nix-store --import

or to download and unpack in one command:

-
$ curl [% uri %] | gunzip | nix-store --import
+
$ curl [% HTML.escape(uri) %] | gunzip | nix-store --import

The package can then be found in the path [% - product.path %]. You’ll probably also want to do

+ product.path | html %]. You’ll probably also want to do

-
$ nix-env -i [% product.path %]
+
$ nix-env -i [% HTML.escape(product.path) %]

to actually install the package in your Nix user environment.

@@ -174,16 +174,16 @@ Channel expression tarball - [% IF product.subtype != "-" %]for [% product.subtype %][% END %] + [% IF product.subtype != "-" %]for [% product.subtype | html %][% END %] [% ELSE %] File - [% product.subtype %] + [% HTML.escape(product.subtype) %] [% END %] [% END %] - - [% product.name %] + uri) %]> + [% product.name | html %] @@ -191,12 +191,12 @@ [% INCLUDE renderProductLinks %] - - + +
File size:[% product.filesize %] bytes ([% mibs(product.filesize / (1024 * 1024)) %] MiB)
SHA-256 hash:[% product.sha256hash %]
Full path:[% product.path %]
SHA-256 hash:[% product.sha256hash | html %]
Full path:[% product.path | html %]
[% END %] [% IF localStore %] - Contents + contents) %]>Contents [% END %] @@ -211,15 +211,15 @@ [% CASE "coverage" %] Code coverage - + uri) %]> Analysis report [% CASE DEFAULT %] Report - - [% product.subtype %] + uri) %]> + [% product.subtype | html %] [% END %] @@ -240,7 +240,7 @@ Documentation - + uri) %]> [% SWITCH product.subtype %] [% CASE "readme" %] Read Me! @@ -249,7 +249,7 @@ [% CASE "release-notes" %] Release notes [% CASE DEFAULT %] - [% product.subtype %] + [% HTML.escape(product.subtype) %] [% END %] @@ -266,12 +266,12 @@ - [% product.type %] + [% product.type | html %] - [% product %] + [% HTML.escape(product) %] From 99a6656b40fae4a5a962b780776ef63d01a3d433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 16:26:26 +0200 Subject: [PATCH 09/12] build: Properly escape all input values --- src/root/build.tt | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/root/build.tt b/src/root/build.tt index 93629427..cf8e454d 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -37,7 +37,7 @@ END; seen.${step.drvpath} = 1; log = c.uri_for('/build' build.id 'nixlog' step.stepnr); %] - [% step.stepnr %] + [% HTML.escape(step.stepnr) %] [% IF step.type == 0 %] Build of [% INCLUDE renderOutputs outputs=step.buildstepoutputs %] @@ -151,7 +151,7 @@ END; - + @@ -168,9 +168,9 @@ END; END; %]; [%+ IF nrFinished == nrConstituents && nrFailedConstituents == 0 %] - all [% nrConstituents %] constituent builds succeeded + all [% HTML.escape(nrConstituents) %] constituent builds succeeded [% ELSE %] - [% nrFailedConstituents %] out of [% nrConstituents %] constituent builds failed + [% HTML.escape(nrFailedConstituents) %] out of [% HTML.escape(nrConstituents) %] constituent builds failed [% IF nrFinished < nrConstituents %] ([% nrConstituents - nrFinished %] still pending) [% END %] @@ -180,24 +180,24 @@ END; - + [% IF build.releasename %] - + [% ELSE %] - + [% END %] [% IF eval %] @@ -336,12 +336,12 @@ END; [% IF eval.nixexprinput %] - + [% END %] - + @@ -361,11 +361,11 @@ END; - + - + @@ -412,9 +412,9 @@ END; [% FOREACH metric IN build.buildmetrics %] - - - + + + [% END %] @@ -456,8 +456,8 @@ END; [% FOREACH input IN build.dependents %] - - + + [% END %] @@ -484,7 +484,7 @@ END; [% ELSIF runcommandlogProblem == "disabled-jobset" %] This jobset does not enable Dynamic RunCommand support. [% ELSE %] - Dynamic RunCommand is not enabled: [% runcommandlogProblem %]. + Dynamic RunCommand is not enabled: [% HTML.escape(runcommandlogProblem) %]. [% END %] [% END %] @@ -503,7 +503,7 @@ END;
-
[% runcommandlog.command | html%]
+
[% runcommandlog.command | html %]
[% IF not runcommandlog.is_running() %] [% IF runcommandlog.did_fail_with_signal() %] From 615798a51eba182b02a8747f86a4d1e61b5477b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 16:56:04 +0200 Subject: [PATCH 10/12] templates: Use HTML.attributes for all links --- src/root/build.tt | 30 +++++++++++------------ src/root/channel-contents.tt | 2 +- src/root/common.tt | 42 ++++++++++++++++----------------- src/root/dashboard.tt | 2 +- src/root/deps.tt | 2 +- src/root/job.tt | 12 +++++----- src/root/jobset-channels-tab.tt | 6 ++--- src/root/jobset-eval.tt | 20 ++++++++-------- src/root/jobset-jobs-tab.tt | 4 ++-- src/root/jobset.tt | 2 +- src/root/layout.tt | 2 +- src/root/log.tt | 8 +++---- src/root/machine-status.tt | 4 ++-- src/root/overview.tt | 2 +- src/root/runcommand-log.tt | 4 ++-- src/root/steps.tt | 4 ++-- src/root/style.tt | 10 ++++---- src/root/users.tt | 4 ++-- 18 files changed, 80 insertions(+), 80 deletions(-) diff --git a/src/root/build.tt b/src/root/build.tt index cf8e454d..818763bd 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -112,16 +112,16 @@ END; [% IF c.user_exists %] [% IF available %] [% IF build.keep %] - Unkeep + c.uri_for('/build' build.id 'keep' 0)) %]>Unkeep [% ELSE %] - Keep + c.uri_for('/build' build.id 'keep' 1)) %]>Keep [% END %] [% END %] [% IF build.finished %] - Restart + c.uri_for('/build' build.id 'restart')) %]>Restart [% ELSE %] - Cancel - Bump up + c.uri_for('/build' build.id 'cancel')) %]>Cancel + c.uri_for('/build' build.id 'bump')) %]>Bump up [% END %] [% END %]
@@ -197,8 +197,8 @@ END;
[% END %] @@ -226,9 +226,9 @@ END; [% END %] @@ -376,14 +376,14 @@ END; + ( chartsURL) %]>history) [% END %] [% IF build.finished && build.closuresize %] + ( chartsURL) %]>history) [% END %] [% IF build.finished && build.buildproducts %] @@ -532,9 +532,9 @@ END; [% IF runcommandlog.uuid != undef %] [% runLog = c.uri_for('/build', build.id, 'runcommandlog', runcommandlog.uuid) %] [% END %] diff --git a/src/root/channel-contents.tt b/src/root/channel-contents.tt index 083d6ae5..ff79dd0f 100644 --- a/src/root/channel-contents.tt +++ b/src/root/channel-contents.tt @@ -49,7 +49,7 @@ installed, you can subscribe to this channel by once executing

[% b = pkg.build %] - + [% END %] - + [% IF !hideJobName %] [% END; IF linkToAll %] - + [% END; END; @@ -176,7 +176,7 @@ BLOCK renderBuildList; END; -BLOCK renderLink %][% title %][% END; +BLOCK renderLink %] uri) %]>[% title %][% END; BLOCK maybeLink; @@ -216,12 +216,12 @@ BLOCK editString; %] BLOCK renderFullBuildLink; - INCLUDE renderFullJobNameOfBuild build=build %] build [% build.id %][% + INCLUDE renderFullJobNameOfBuild build=build %] c.uri_for('/build' build.id)) %]>build [% build.id %][% END; BLOCK renderBuildIdLink; %] -build [% id %] + c.uri_for('/build' id)) %]>build [% id %] [% END; @@ -320,7 +320,7 @@ END; BLOCK renderShortInputValue; IF input.type == "build" || input.type == "sysbuild" %] - [% input.dependency.id %] + c.uri_for('/build' input.dependency.id)) %]>[% input.dependency.id %] [% ELSIF input.type == "string" %] "[% HTML.escape(input.value) %]" [% ELSIF input.type == "nix" || input.type == "boolean" %] @@ -338,7 +338,7 @@ BLOCK renderDiffUri; url = bi1.uri; path = url.replace(base, ''); IF url.match(base) %] - [% contents %] + m.uri.replace('_path_', path).replace('_1_', bi1.revision).replace('_2_', bi2.revision)) %]>[% contents %] [% nouri = 0; END; END; @@ -347,13 +347,13 @@ BLOCK renderDiffUri; url = res.0; branch = res.1; IF bi1.type == "hg" || bi1.type == "git" %] - c.uri_for('/api/scmdiff', { uri = url, rev1 = bi1.revision, rev2 = bi2.revision, type = bi1.type, branch = branch - })) %]">[% contents %] + })) %]>[% contents %] [% ELSE; contents; END; @@ -443,10 +443,10 @@ BLOCK renderInputDiff; %] BLOCK renderPager %] - + [% IF !jobset && !build %] [% END %] @@ -540,7 +540,7 @@ BLOCK renderEvals %] [% END; IF linkToAll %] - + [% END %]
Build ID:[% build.id %][% HTML.escape(build.id) %]
Status:
System:[% build.system %][% build.system | html %]
Release name:[% HTML.escape(build.releasename) %][% build.releasename | html %]
Nix name:[% build.nixname %][% build.nixname | html %]
Part of: - evaluation [% eval.id %] + evaluation [% HTML.escape(eval.id) %] [% IF nrEvals > 1 +%] (and [% nrEvals - 1 %] others)[% END %]
Nix expression:file [% HTML.escape(eval.nixexprpath) %] in input [% HTML.escape(eval.nixexprinput) %]file [% eval.nixexprpath | html %] in input [% eval.nixexprinput | html %]
Nix name:[% build.nixname %][% build.nixname | html %]
Short description:
System:[% build.system %][% build.system | html %]
Derivation store path:[% build.drvpath %][% build.drvpath | html %]
Output store paths:
c.uri_for('/job' project.name jobset.name job 'metric' metric.name)) %]">[%HTML.escape(metric.name)%][%metric.value%][%metric.unit%] c.uri_for('/job' project.name jobset.name job 'metric' metric.name)) %]">[%metric.name | html%][%HTML.escape(metric.value)%][% HTML.escape(metric.unit) %]
[% INCLUDE renderFullBuildLink build=input.build %][% input.name %][% input.build.system %][% input.name | html %][% input.build.system | html %] [% INCLUDE renderDateTime timestamp = input.build.timestamp %]
Part of: - evaluation [% HTML.escape(eval.id) %] - [% IF nrEvals > 1 +%] (and [% nrEvals - 1 %] others)[% END %] + c.uri_for(c.controller('JobsetEval').action_for('view'), [eval.id])) %]>evaluation [% HTML.escape(eval.id) %] + [% IF nrEvals > 1 +%] (and c.uri_for('/build' build.id 'evals')) %]>[% nrEvals - 1 %] others)[% END %]
Logfile: [% actualLog = cachedBuildStep ? c.uri_for('/build' cachedBuild.id 'nixlog' cachedBuildStep.stepnr) : c.uri_for('/build' build.id 'log') %] - pretty - raw - tail + actualLog) %]>pretty + actualLog _ "/raw") %]>raw + actualLog _ "/tail") %]>tail
Closure size: [% mibs(build.closuresize / (1024 * 1024)) %] MiB - (history)
Output size: [% mibs(build.size / (1024 * 1024)) %] MiB - (history)
[% b.id %] c.uri_for('/build' b.id)) %]>[% b.id %] [% b.get_column('releasename') || b.nixname %] [% b.system %] diff --git a/src/root/common.tt b/src/root/common.tt index 7a93cc95..beee86ca 100644 --- a/src/root/common.tt +++ b/src/root/common.tt @@ -55,17 +55,17 @@ BLOCK renderRelativeDate %] [% END; BLOCK renderProjectName %] -[% project %] + c.uri_for('/project' project)) %]>[% project %] [% END; BLOCK renderJobsetName %] -[% jobset %] + c.uri_for('/jobset' project jobset)) %]>[% jobset %] [% END; BLOCK renderJobName %] -[% job %] + c.uri_for('/job' project jobset job)) %]>[% job %] [% END; @@ -140,10 +140,10 @@ BLOCK renderBuildListBody; [% IF showSchedulingInfo %] [% IF busy %]Started[% ELSE %]Queued[% END %][% build.id %] link) %]>[% build.id %] - [% IF !hideJobsetName %][%build.jobset.get_column("project")%]:[%build.jobset.get_column("name")%]:[% END %][%build.get_column("job")%] + link) %]>[% IF !hideJobsetName %][%build.jobset.get_column("project")%]:[%build.jobset.get_column("name")%]:[% END %][%build.get_column("job")%] [% IF showStepName %] [% INCLUDE renderDrvInfo step=build.buildsteps releasename=build.nixname %] [% END %] @@ -158,7 +158,7 @@ BLOCK renderBuildListBody;
More...
linkToAll) %]>More...
[% eval.id %] link) %]>[% eval.id %][% INCLUDE renderFullJobsetName project=eval.jobset.project.name jobset=eval.jobset.name %]
More...
linkToAll) %]>More...
@@ -548,7 +548,7 @@ BLOCK renderEvals %] BLOCK renderLogLinks %] -(log, raw, tail) +( url) %]>log, "$url/raw") %]>raw, "$url/tail") %]>tail) [% END; diff --git a/src/root/dashboard.tt b/src/root/dashboard.tt index 06b8de15..0daf3dad 100644 --- a/src/root/dashboard.tt +++ b/src/root/dashboard.tt @@ -24,7 +24,7 @@ [% INCLUDE renderFullJobName project=j.job.get_column('project') jobset=j.job.get_column('jobset') job=j.job.job %] [% FOREACH b IN j.builds %] - [% INCLUDE renderBuildStatusIcon size=16 build=b %] + c.uri_for('/build' b.id)) %]>[% INCLUDE renderBuildStatusIcon size=16 build=b %] [% END %] [% END %] diff --git a/src/root/deps.tt b/src/root/deps.tt index 6daa9725..b9f3ba7e 100644 --- a/src/root/deps.tt +++ b/src/root/deps.tt @@ -11,7 +11,7 @@ [% END %] [% IF node.buildStep %] - [% node.name %] [% + c.uri_for('/build' node.buildStep.get_column('build'))) %]>[% node.name %] [% IF buildStepLogExists(node.buildStep); INCLUDE renderLogLinks url=c.uri_for('/build' node.buildStep.get_column('build') 'nixlog' node.buildStep.stepnr); END %] diff --git a/src/root/job.tt b/src/root/job.tt index 7e475f69..acb874a3 100644 --- a/src/root/job.tt +++ b/src/root/job.tt @@ -10,8 +10,8 @@ [% IF !jobExists(jobset, job) %]
This job is not a member of the latest evaluation of its jobset. This means it was +[% HTML.attributes(href => c.uri_for('/jobset' project.name jobset.name +'evals')) %]>latest evaluation of its jobset. This means it was removed or had an evaluation error.
[% END %] @@ -58,7 +58,7 @@ removed or had an evaluation error. [% agg_ = aggregates.$agg %] [% END %] @@ -70,7 +70,7 @@ removed or had an evaluation error. [% FOREACH agg IN aggs %] [% r = aggregates.$agg.constituents.$j; IF r.id %] - + c.uri_for('/build' r.id)) %]> [% INCLUDE renderBuildStatusIcon size=16 build=r %] [% END %] @@ -89,8 +89,8 @@ removed or had an evaluation error. diff --git a/src/root/jobset-channels-tab.tt b/src/root/jobset-channels-tab.tt index 692f2682..3fa4ba69 100644 --- a/src/root/jobset-channels-tab.tt +++ b/src/root/jobset-channels-tab.tt @@ -14,7 +14,7 @@ [% FOREACH eval IN evalIds %] [% END %] @@ -22,9 +22,9 @@ [% FOREACH chan IN channels-%] - [% chan %] + c.uri_for('/channel/custom' project.name jobset.name chan)) %]>[% chan %] [% FOREACH eval IN evalIds %] - [% r = evals.$eval.builds.$chan; IF r.id %][% INCLUDE renderBuildStatusIcon size=16 build=r %][% END %] + [% r = evals.$eval.builds.$chan; IF r.id %] c.uri_for('/build' r.id)) %]>[% INCLUDE renderBuildStatusIcon size=16 build=r %][% END %] [% END %] [% END %] diff --git a/src/root/jobset-eval.tt b/src/root/jobset-eval.tt index 11965ce2..bd29525d 100644 --- a/src/root/jobset-eval.tt +++ b/src/root/jobset-eval.tt @@ -27,9 +27,9 @@ eval.checkouttime %]s and evaluation took [% eval.evaltime %]s.

[% IF otherEval %]

Comparisons are relative to [% INCLUDE renderFullJobsetName -project=otherEval.jobset.project.name jobset=otherEval.jobset.name %] evaluation [% otherEval.id %].

+project=otherEval.jobset.project.name jobset=otherEval.jobset.name %] evaluation c.uri_for(c.controller('JobsetEval').action_for('view'), +[otherEval.id])) %]>[% otherEval.id %].

[% END %]
@@ -45,18 +45,18 @@ c.uri_for(c.controller('JobsetEval').action_for('view'), @@ -99,7 +99,7 @@ c.uri_for(c.controller('JobsetEval').action_for('view'), [% INCLUDE renderBuildListBody builds=builds.slice(0, (size > max ? max : size) - 1) hideProjectName=1 hideJobsetName=1 busy=0 %] [% IF size > max; params = c.req.params; params.full = 1 %] - ([% size - max %] more builds omitted) + c.uri_for(c.controller('JobsetEval').action_for('view'), [eval.id], params) _ tabname) %]>([% size - max %] more builds omitted) [% END %] [% INCLUDE renderBuildListFooter %] [% END %] @@ -136,7 +136,7 @@ c.uri_for(c.controller('JobsetEval').action_for('view'), [% END %] [% IF size > max; params = c.req.params; params.full = 1 %] - ([% size - max %] more jobs omitted) + ([% size - max %] more jobs omitted) [% END %] diff --git a/src/root/jobset-jobs-tab.tt b/src/root/jobset-jobs-tab.tt index 707d329e..2785e802 100644 --- a/src/root/jobset-jobs-tab.tt +++ b/src/root/jobset-jobs-tab.tt @@ -52,7 +52,7 @@ [% FOREACH eval IN evalIds %] [% END %] @@ -62,7 +62,7 @@ [% INCLUDE renderJobName project=project.name jobset=jobset.name job=j %] [% FOREACH eval IN evalIds %] - [% r = evals.$eval.builds.$j; IF r.id %][% INCLUDE renderBuildStatusIcon size=16 build=r %][% END %] + [% r = evals.$eval.builds.$j; IF r.id %] c.uri_for('/build' r.id)) %]>[% INCLUDE renderBuildStatusIcon size=16 build=r %][% END %] [% END %] [% END %] diff --git a/src/root/jobset.tt b/src/root/jobset.tt index 3e594756..1e570cf3 100644 --- a/src/root/jobset.tt +++ b/src/root/jobset.tt @@ -188,7 +188,7 @@ diff --git a/src/root/layout.tt b/src/root/layout.tt index b520b455..37f4cb7f 100644 --- a/src/root/layout.tt +++ b/src/root/layout.tt @@ -24,7 +24,7 @@