From 0dd0e1c9d833fd84d9863bb6c97a600a59855cfb Mon Sep 17 00:00:00 2001 From: Cameron Eagans Date: Tue, 3 Mar 2020 11:05:39 -0700 Subject: [PATCH] Add the ability to customize index.html --- UserManual.md | 4 ++++ src/http.cpp | 26 +++++++++++++++++++++----- src/laminar.cpp | 8 ++++++++ src/laminar.h | 4 ++++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/UserManual.md b/UserManual.md index c3b928f..432b1e3 100644 --- a/UserManual.md +++ b/UserManual.md @@ -622,6 +622,10 @@ If it exists, the file `/var/lib/laminar/custom/style.css` will be served by lam This directory is also a good place to add any extra assets needed for this customization, but note that in this case you will need to serve this directory directly from your [HTTP reverse proxy](#Service-configuration) (highly recommended). +## Custom HTML template + +If it exists, the file `/var/lib/laminar/custom/index.html` will be served by laminar instead of the default markup that is bundled into the Laminar binary. This file can be used to change any aspect of Laminar's WebUI, including adding custom menu links, stylesheets, or anything else. Any additional assets that are needed will need to be served directly from your [HTTP reverse proxy](#Service-configuration) (highly recommended). + --- # Badges diff --git a/src/http.cpp b/src/http.cpp index ee4833c..08fd338 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -211,14 +211,16 @@ kj::Promise Http::request(kj::HttpMethod method, kj::StringPtr url, const return writeEvents(p,s); }).attach(kj::mv(stream)).attach(kj::mv(peer)); } - } else if(url.startsWith("/archive/")) { + } + if(url.startsWith("/archive/")) { KJ_IF_MAYBE(file, laminar.getArtefact(url.slice(strlen("/archive/")))) { auto array = (*file)->mmap(0, (*file)->stat().size); responseHeaders.add("Content-Transfer-Encoding", "binary"); auto stream = response.send(200, "OK", responseHeaders, array.size()); return stream->write(array.begin(), array.size()).attach(kj::mv(array)).attach(kj::mv(file)).attach(kj::mv(stream)); } - } else if(parseLogEndpoint(url, name, num)) { + } + if(parseLogEndpoint(url, name, num)) { auto lw = kj::heap>(logWatchers); lw->job = name; lw->run = num; @@ -236,19 +238,33 @@ kj::Promise Http::request(kj::HttpMethod method, kj::StringPtr url, const return writeLogChunk(c, s); }).attach(kj::mv(output)).attach(kj::mv(stream)).attach(kj::mv(lw)); } - } else if(url == "/custom/style.css") { + } + if(url == "/custom/style.css") { responseHeaders.set(kj::HttpHeaderId::CONTENT_TYPE, "text/css; charset=utf-8"); responseHeaders.add("Content-Transfer-Encoding", "binary"); std::string css = laminar.getCustomCss(); auto stream = response.send(200, "OK", responseHeaders, css.size()); return stream->write(css.data(), css.size()).attach(kj::mv(css)).attach(kj::mv(stream)); - } else if(resources->handleRequest(url.cStr(), &start, &end, &content_type)) { + } + // If there is custom html defined, serve it. Otherwise, the default html + // will be served by the next block. + if(url == "/index.html" || url == "/") { + responseHeaders.set(kj::HttpHeaderId::CONTENT_TYPE, "text/html; charset=utf-8"); + responseHeaders.add("Content-Transfer-Encoding", "binary"); + std::string html = laminar.getCustomIndexHtml(); + if (!html.empty()) { + auto stream = response.send(200, "OK", responseHeaders, html.size()); + return stream->write(html.data(), html.size()).attach(kj::mv(html)).attach(kj::mv(stream)); + } + } + if(resources->handleRequest(url.cStr(), &start, &end, &content_type)) { responseHeaders.set(kj::HttpHeaderId::CONTENT_TYPE, content_type); responseHeaders.add("Content-Encoding", "gzip"); responseHeaders.add("Content-Transfer-Encoding", "binary"); auto stream = response.send(200, "OK", responseHeaders, end-start); return stream->write(start, end-start).attach(kj::mv(stream)); - } else if(url.startsWith("/badge/") && url.endsWith(".svg") && laminar.handleBadgeRequest(std::string(url.begin()+7, url.size()-11), badge)) { + } + if(url.startsWith("/badge/") && url.endsWith(".svg") && laminar.handleBadgeRequest(std::string(url.begin()+7, url.size()-11), badge)) { responseHeaders.set(kj::HttpHeaderId::CONTENT_TYPE, "image/svg+xml"); responseHeaders.add("Cache-Control", "no-cache"); auto stream = response.send(200, "OK", responseHeaders, badge.size()); diff --git a/src/laminar.cpp b/src/laminar.cpp index acf12fa..9a5a830 100644 --- a/src/laminar.cpp +++ b/src/laminar.cpp @@ -796,3 +796,11 @@ std::string Laminar::getCustomCss() { return std::string(); } } + +std::string Laminar::getCustomIndexHtml() { + KJ_IF_MAYBE(htmlFile, fsHome->tryOpenFile(kj::Path{"custom","index.html"})) { + return (*htmlFile)->readAllText().cStr(); + } else { + return std::string(); + } +} diff --git a/src/laminar.h b/src/laminar.h index e316ba2..02cf2be 100644 --- a/src/laminar.h +++ b/src/laminar.h @@ -95,6 +95,10 @@ public: // which handles this url. std::string getCustomCss(); + // Fetches the content of $LAMINAR_HOME/custom/index.html or an empty + // string. This is used for custom template overrides. + std::string getCustomIndexHtml(); + // Aborts a single job bool abort(std::string job, uint buildNum);