guix-commits
[Top][All Lists]
Advanced

[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);
+           }
+       });
+});



reply via email to

[Prev in Thread] Current Thread [Next in Thread]