[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[no subject]
From: |
Ludovic Courtès |
Date: |
Tue, 24 Oct 2023 18:44:07 -0400 (EDT) |
branch: master
commit 2ab4df3fdd998247e270843aac284a02b9ac6e6c
Author: Ludovic Courtès <ludo@gnu.org>
AuthorDate: Wed Oct 25 00:02:23 2023 +0200
templates: Add /build/ID/log, with client-side build log highlighting.
* src/static/js/build-log.js: New file.
* Makefile.am (dist_js_DATA): Add it.
* src/cuirass/http.scm (%file-white-list): Add it.
(url-handler): Add “/build/ID/log” handler.
* src/cuirass/templates.scm (pretty-build-log): New procedure.
---
Makefile.am | 1 +
src/cuirass/http.scm | 14 +++++
src/cuirass/templates.scm | 21 +++++++
src/static/js/build-log.js | 141 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 177 insertions(+)
diff --git a/Makefile.am b/Makefile.am
index 8a6f87a..4ef2164 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -128,6 +128,7 @@ dist_images_DATA = \
src/static/images/icon.png
dist_js_DATA = \
src/static/js/bootstrap.min.js \
+ src/static/js/build-log.js \
src/static/js/cuirass.js \
src/static/js/chart.js \
src/static/js/choices.min.js \
diff --git a/src/cuirass/http.scm b/src/cuirass/http.scm
index 6d92e80..1ea32ff 100644
--- a/src/cuirass/http.scm
+++ b/src/cuirass/http.scm
@@ -89,6 +89,7 @@
"images/guix.png"
"js/chart.js"
"js/cuirass.js"
+ "js/build-log.js"
"js/d3.v6.min.js"
"js/datatables.min.js"
"js/jquery-3.3.1.min.js"
@@ -864,6 +865,19 @@ passed, only display JOBS targeting this SYSTEM."
(if (and log (file-exists? log))
(respond-compressed-file log)
(respond-not-found (uri->string (request-uri request))))))
+ (('GET "build" (= string->number id) "log")
+ (let* ((build (and id (db-get-build id)))
+ (log (and build (build-log build))))
+ (if (and log (file-exists? log))
+ (respond-html
+ (html-page
+ (string-append "Build log of build #" (number->string id))
+ (pretty-build-log id)
+ `(((#:name . ,(string-append "Build #" (number->string id)))
+ (#:link
+ . ,(string-append "/build/" (number->string id)
+ "/details"))))))
+ (respond-not-found (uri->string (request-uri request))))))
(('GET "output" id)
(let ((output (db-get-output
(string-append (%store-prefix) "/" id))))
diff --git a/src/cuirass/templates.scm b/src/cuirass/templates.scm
index 7a1cd86..170339b 100644
--- a/src/cuirass/templates.scm
+++ b/src/cuirass/templates.scm
@@ -52,6 +52,7 @@
workers-status
machine-status
evaluation-dashboard
+ pretty-build-log
badge-svg
javascript-licenses))
@@ -2057,6 +2058,26 @@ text-dark d-flex position-absolute w-100"))
'("Free store disk space percentage")
#:colors (list "#3e95cd"))))))))
+(define (pretty-build-log id)
+ "Return HTML for a pretty rendering of the log of build ID."
+ (let ((url (string-append "/build/" (number->string id)
+ "/log/raw")))
+ `((noscript
+ "This page requires JavaScript but you can "
+ (a (@ (href ,url)) "view the raw build log")
+ " instead.")
+
+ (script (@ (type "text/javascript")
+ (src "/static/js/build-log.js"))
+ "")
+ (pre (code (@ (id "build-log") (class "build-log"))
+ ;; Placeholder filled in by 'build-log.js'.
+ "⚙️ Loading build log…"))
+
+ (div (@ (class "text-secondary d-flex flex-row mb-3"))
+ (span (@ (class "oi oi-external-link")))
+ (a (@ (href ,url)) "raw build log")))))
+
(define* (evaluation-dashboard evaluation systems
#:key
(checkouts (evaluation-checkouts evaluation))
diff --git a/src/static/js/build-log.js b/src/static/js/build-log.js
new file mode 100644
index 0000000..1e27007
--- /dev/null
+++ b/src/static/js/build-log.js
@@ -0,0 +1,141 @@
+/**
+ *
+ * @source:
+ *
https://git.savannah.gnu.org/cgit/guix/guix-cuirass.git/tree/src/static/js/build-log.js
+ *
+ * @licstart The following is the entire license notice for the
+ * JavaScript code in this page.
+ *
+ * Copyright (C) 2023 Ludovic Courtès <ludo@gnu.org>
+ *
+ *
+ * The JavaScript code in this page is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GNU GPL) as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option)
+ * any later version. The code is distributed WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
+ *
+ * As additional permission under GNU GPL version 3 section 7, you
+ * may distribute non-source (e.g., minimized or compacted) forms of
+ * that code without the copy of the GNU GPL normally required by
+ * section 4, provided you include this license notice and a URL
+ * through which recipients can access the Corresponding Source.
+ *
+ * @licend The above is the entire license notice
+ * for the JavaScript code in this page.
+ *
+ */
+
+$(document).ready(function() {
+ const highlights = [
+ { regexp: new RegExp("^@ build-failed ([^ ]+) (.*)$"),
+ style: "text-danger"
+ },
+ { regexp: new RegExp("^@ build-succeeded ([^ ]+) (.*)$"),
+ style: "text-success"
+ },
+ { regexp: new RegExp("^@ build-started (.*)$"),
+ style: "text-info"
+ },
+ { regexp: new RegExp("^phase `(.*)' succeeded .*$"),
+ style: "text-success"
+ },
+ { regexp: new RegExp("^phase `(.*)' failed .*$"),
+ style: "text-danger"
+ },
+ { regexp: new RegExp("\\berror\\b"),
+ style: "text-danger"
+ },
+ { regexp: new RegExp("\\bwarning\\b"),
+ style: "text-warning"
+ },
+ { regexp: new RegExp("\\bPASS\\b"),
+ style: "text-success"
+ },
+ { regexp: new RegExp("\\bFAIL\\b"),
+ style: "text-danger"
+ }
+ ];
+
+ const phaseStartRx = new RegExp("^starting phase [`'](.*)'$");
+ const phaseEndRx = new RegExp("^phase [`'](.*)' (failed|succeeded) .*$");
+
+ const logURL = document.URL + '/raw';
+
+ async function readLog(response) {
+ const decoder = new TextDecoder("utf-8");
+ var body = "";
+
+ for await (const chunk of response.body) {
+ // FIXME: Should decode once we have the whole body as a single
+ // Uint8Array.
+ body += decoder.decode(chunk);
+ }
+
+ // Clear the build log area.
+ $('#build-log').html("");
+
+ // Add a button to collapse/expand phases.
+ var openness = true;
+ const button =
+ $('<div>', { class: "d-flex mb-3 text-info oi oi-collapse-down",
+ title: "Toggle phase visibility." })
+ .on("click", _ => {
+ openness = !openness;
+ $("details").each(function(index) {
+ $(this).attr('open', openness);
+ });
+ });
+ $('#build-log').prepend(button);
+
+ // Format the build log, line by line.
+ const lines = body.split('\n');
+ var parent = $('#build-log');
+ for (const line of lines) {
+ if (phaseStartRx.exec(line)) {
+ // Open a <details> tag.
+ const match = phaseStartRx.exec(line);
+ parent = $('<details>', { open: "open"
}).appendTo($('#build-log'));
+ $('<summary>', { class: "text-info", text: match[1]
}).appendTo(parent);
+ }
+
+ var matched = false;
+ for (const highlight of highlights) {
+ const match = highlight.regexp.exec(line);
+ if (match) {
+ parent.append(line.substring(0, match.index));
+ $('<span>', { class: highlight.style, text: match[0] })
+ .appendTo(parent);
+ parent.append(line.substring(match.index +
match[0].length) + '\n');
+ matched = true;
+ break;
+ }
+ }
+
+ if (!matched) {
+ parent.append(line + '\n');
+ }
+
+ if (phaseEndRx.exec(line)) {
+ // Close the <details> tag.
+ parent = $('#build-log');
+ }
+ }
+ }
+
+ fetch(logURL)
+ .then((response) => {
+ const length = parseInt(response.headers.get("content-length"));
+ console.log("build log size", length);
+
+ // If the log is too big (we're looking at the gzipped size here),
+ // redirect to the raw build log.
+ if (length > 2**20) {
+ location.replace(logURL);
+ } else {
+ readLog(response);
+ }
+ });
+});