feature (fastboot): optimise boot sequence

This commit is contained in:
MickaelK 2025-08-20 21:52:02 +10:00
parent 6e0037bfa7
commit e72cbe3c35
5 changed files with 216 additions and 313 deletions

1
Jenkinsfile vendored
View file

@ -28,6 +28,7 @@ pipeline {
sh "make build_frontend"
}
docker.image("golang:1.24-bookworm").inside("--user=root") {
sh "apt update -y && apt install -y libbrotli-dev brotli"
sh "sed -i 's|plg_image_c|plg_image_golang|' server/plugin/index.go"
sh "make build_init"
sh "make build_backend"

View file

@ -1,27 +1,5 @@
let VERSION = null;
/*
* This Service Worker is an optional optimisation to load the app faster.
* Whenever using raw es module without any build, we had a large number
* of assets getting through the network. When we looked through the
* developer console -> network, and look at the timing, 98% of the time
* was spent "waiting for the server response".
* HTTP2/3 should solve that issue but we don't control the proxy side of
* things of how people install Filestash, hence the idea to bulk download
* as much as we can through SSE, store it onto a cache and get our
* service worker to inject the response.
* This approach alone make the app a lot faster to load but relies on
* the server being able to bundle our assets via SSE.
*
* TODO:
* - wait until browser support DecompressionStream("brotli") natively
* and use that. As of 2025, downloading a brotli decompress library
* make the gain br / gz negative for our app
* - wait until Firefox support SSE within service worker. As of 2025,
* someone was implementing it in Firefox but it's not everywhere yet
* Once that's done, we want to be 100% sure everything is working great
*/
self.addEventListener("install", (event) => {
if (!self.EventSource) throw new Error("turboload not supported on this platform");
@ -58,104 +36,27 @@ self.addEventListener("message", (event) => {
);
});
async function handlePreloadMessage(chunks, clear, version, resolve, reject) {
async function handlePreloadMessage(imports, clear, version, resolve, reject) {
VERSION = version;
const cleanup = [];
try {
let execHTTP = true;
await caches.keys().then(async(names) => {
for (let i=0; i<names.length; i++) {
if (names[i] === VERSION && !clear) {
execHTTP = false;
return;
}
for (let i = 0; i<names.length; i++) {
await caches.delete(names[i]);
}
});
if (execHTTP) {
const cache = await caches.open(VERSION);
chunks = await Promise.all(chunks.map(async(urls) => {
const missing = [];
await Promise.all(urls.map(async(url) => {
if (!await cache.match(location.origin + url)) missing.push(url);
}));
return missing;
}));
if (chunks.filter((urls) => urls.length > 0).length > 0) {
await Promise.all(chunks.map((urls) => {
return preload({ urls, cache, cleanup });
}));
}
const cache = await caches.open(VERSION);
for (const path in imports) {
let mime = "application/octet-stream";
if (path.endsWith(".css")) mime = "text/css";
else if (path.endsWith(".js")) mime = "application/javascript";
await cache.put(location.origin + path, new Response(
new Blob([imports[path]]),
{ headers: { "Content-Type": mime } },
));
}
resolve();
} catch (err) {
console.log("ERR", err);
reject(err);
} finally {
cleanup.forEach((fn) => fn());
}
};
async function preload({ urls, cache, cleanup }) {
const evtsrc = new self.EventSource("/assets/bundle?" + urls.map((url) => `url=${url}`).join("&"));
cleanup.push(() => evtsrc.close());
let i = 0;
const messageHandler = async(resolve, event, decoder) => {
const url = event.lastEventId;
let mime = "application/octet-stream";
if (url.endsWith(".css")) mime = "text/css";
else if (url.endsWith(".js")) mime = "application/javascript";
i += 1;
await cache.put(
location.origin + url,
new Response(
decoder(new Blob([base128Decode(event.data)]).stream()),
{ headers: { "Content-Type": mime } },
),
);
if (i === urls.length) {
resolve();
}
};
const errorHandler = (reject, err) => {
reject(err);
};
await new Promise((resolve, reject) => {
evtsrc.addEventListener("static::raw", (event) => messageHandler(
resolve,
event,
(stream) => stream,
));
evtsrc.addEventListener("static::gzip", (event) => messageHandler(
resolve,
event,
(stream) => stream.pipeThrough(new DecompressionStream("gzip")),
));
evtsrc.onerror = (err) => {
if (i === urls.length) return;
errorHandler(reject, err);
};
});
}
function base128Decode(s) { // encoder is in server/ctrl/static.go -> encodeB128
const out = new Uint8Array(Math.floor((s.length * 7) / 8) + 1);
let acc = 0;
let bits = 0;
let oi = 0;
for (let i = 0; i < s.length; i++) {
const ch = s.charCodeAt(i);
const digit = ch & 0x7F; // undo 0x80 masking for NUL/LF/CR
acc = (acc << 7) | digit;
bits += 7;
while (bits >= 8) {
bits -= 8;
out[oi++] = (acc >> bits) & 0xFF;
acc &= (1 << bits) - 1;
}
}
return out.subarray(0, oi);
}

View file

@ -19,7 +19,6 @@
<link rel="stylesheet" href="custom.css">
<link rel="stylesheet" href="./assets/{{ .version }}/css/designsystem.css">
</template>
<template id="body">
<script type="module" src="./assets/{{ .version }}/components/loader.js"></script>
<script type="module">
@ -30,36 +29,47 @@
beforeStart: import("{{ .base }}assets/{{ .version }}/boot/ctrl_boot_frontoffice.js"),
});
</script>
<component-modal></component-modal>
<script type="module" src="./assets/{{ .version }}/components/modal.js" defer></script>
<component-notification></component-notification>
<script type="module" src="./assets/{{ .version }}/components/notification.js" defer></script>
</template>
<script id="preload" type="application/json">{{ .preload }}</script>
<script type="module">
function boot() {
function liftoff() {
document.head.appendChild(document.querySelector("template#head").content);
document.body.appendChild(document.querySelector("template#body").content);
}
if ("serviceWorker" in navigator) {
const URLS = JSON.parse(document.getElementById("preload").textContent);
async function ignitionSequence() {
if (!("serviceWorker" in navigator)) return
try {
const register = await navigator.serviceWorker.register("sw.js");
await new Promise((resolve) => {
register.active ?
resolve() :
window.bundler = (function () {
let modules = [];
return {
register: (path, code) => modules[path] = code,
state: () => modules,
};
})();
const [register] = await Promise.all([
navigator.serviceWorker.register("sw.js").then((register) => new Promise((resolve) => {
register.active ?
resolve(register) :
navigator.serviceWorker.addEventListener("controllerchange", () => {
resolve();
resolve(register);
});
});
})),
new Promise((resolve, reject) => {
const $script = document.createElement("script");
$script.type = "module";
$script.src = "./assets/bundle.js?version={{ slice .version 0 7 }}::{{ .hash }}";
document.head.appendChild($script);
$script.onload = resolve;
$script.onerror = reject;
}),
]);
register.active.postMessage({
"type": "preload",
"payload": URLS,
"payload": bundler.state(),
"version": "{{ slice .version 0 7 }}::{{ .hash }}",
"clear": {{ .clear }},
});
@ -71,14 +81,40 @@
}));
} catch (err) { console.error(err); }
}
boot();
//
//
//
//
// /\
// / \
// || / \
// || /______\
// ||| |
// | | |
// | | |
// |__|________|
// |___________|
// | | |
// |__| || |\
// ||| || | \
// /||| || | \
// /_|||...||...|___\
// |||::::::::|
// || \::::::/
// || ||__||
// || ||
// || \\_______________
// _______________||______`---------------
// |
// | |
await ignitionSequence() // |
// |
liftoff() // |
// | |
// |_____________________________________________|
</script>
<noscript>
<div style="text-align:center;font-family:monospace;margin-top:5%;font-size:15px;">
<h2>Error: Javascript is off</h2>
<p>You need to enable Javascript to run this application</p>
</div>
</noscript>
<noscript><div style="text-align:center;font-family:monospace;margin-top:5%;font-size:15px;"><h2>Error: Javascript is off</h2></div></noscript>
</body>
</html>

View file

@ -19,6 +19,7 @@ import (
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/bluekeyes/go-gitdiff/gitdiff"
"github.com/google/brotli/go/cbrotli"
)
var (
@ -234,7 +235,6 @@ func ServeIndex(indexPath string) func(*App, http.ResponseWriter, *http.Request)
"base": base,
"version": BUILD_REF,
"license": LICENSE,
"preload": preload(),
"clear": clear,
"hash": sign,
"favicon": favicon(),
@ -257,71 +257,158 @@ func ServeIndex(indexPath string) func(*App, http.ResponseWriter, *http.Request)
}
}
func ServeBundle(ctx *App, res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "text/event-stream")
res.Header().Set("Cache-Control", "no-cache")
res.Header().Set("Connection", "keep-alive")
res.WriteHeader(http.StatusOK)
func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
paths := []string{
"/assets/" + BUILD_REF + "/boot/ctrl_boot_frontoffice.js",
"/assets/" + BUILD_REF + "/boot/router_frontoffice.js",
"/assets/" + BUILD_REF + "/boot/common.js",
urls := req.URL.Query()["url"]
for i := 0; i < len(urls); i++ {
curPath := "/assets/" + strings.TrimPrefix(urls[i], "/assets/"+BUILD_REF+"/")
var file io.ReadCloser
var err error
if f := applyPatch(curPath); f != nil {
file = io.NopCloser(f)
fmt.Fprintf(res, "event: %s\n", "static::raw")
} else {
file, err = WWWPublic.Open(curPath + ".gz")
"/assets/" + BUILD_REF + "/css/designsystem.css",
"/assets/" + BUILD_REF + "/css/designsystem_input.css",
"/assets/" + BUILD_REF + "/css/designsystem_textarea.css",
"/assets/" + BUILD_REF + "/css/designsystem_inputgroup.css",
"/assets/" + BUILD_REF + "/css/designsystem_checkbox.css",
"/assets/" + BUILD_REF + "/css/designsystem_formbuilder.css",
"/assets/" + BUILD_REF + "/css/designsystem_button.css",
"/assets/" + BUILD_REF + "/css/designsystem_icon.css",
"/assets/" + BUILD_REF + "/css/designsystem_dropdown.css",
"/assets/" + BUILD_REF + "/css/designsystem_container.css",
"/assets/" + BUILD_REF + "/css/designsystem_box.css",
"/assets/" + BUILD_REF + "/css/designsystem_darkmode.css",
"/assets/" + BUILD_REF + "/css/designsystem_skeleton.css",
"/assets/" + BUILD_REF + "/css/designsystem_utils.css",
"/assets/" + BUILD_REF + "/css/designsystem_alert.css",
"/assets/" + BUILD_REF + "/components/decorator_shell_filemanager.css",
"/assets/" + BUILD_REF + "/components/loader.js",
"/assets/" + BUILD_REF + "/components/modal.js",
"/assets/" + BUILD_REF + "/components/modal.css",
"/assets/" + BUILD_REF + "/components/notification.js",
"/assets/" + BUILD_REF + "/components/notification.css",
"/assets/" + BUILD_REF + "/components/sidebar.js",
"/assets/" + BUILD_REF + "/components/sidebar.css",
"/assets/" + BUILD_REF + "/components/dropdown.js",
"/assets/" + BUILD_REF + "/components/decorator_shell_filemanager.js",
"/assets/" + BUILD_REF + "/components/form.js",
"/assets/" + BUILD_REF + "/components/icon.js",
"/assets/" + BUILD_REF + "/components/breadcrumb.js",
"/assets/" + BUILD_REF + "/components/breadcrumb.css",
"/assets/" + BUILD_REF + "/components/skeleton.js",
"/assets/" + BUILD_REF + "/helpers/loader.js",
"/assets/" + BUILD_REF + "/helpers/log.js",
"/assets/" + BUILD_REF + "/helpers/sdk.js",
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs.min.js",
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-ajax.min.js",
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-shared.min.js",
"/assets/" + BUILD_REF + "/lib/store.js",
"/assets/" + BUILD_REF + "/lib/form.js",
"/assets/" + BUILD_REF + "/lib/path.js",
"/assets/" + BUILD_REF + "/lib/random.js",
"/assets/" + BUILD_REF + "/lib/settings.js",
"/assets/" + BUILD_REF + "/lib/skeleton/index.js",
"/assets/" + BUILD_REF + "/lib/rx.js",
"/assets/" + BUILD_REF + "/lib/ajax.js",
"/assets/" + BUILD_REF + "/lib/animate.js",
"/assets/" + BUILD_REF + "/lib/assert.js",
"/assets/" + BUILD_REF + "/lib/dom.js",
"/assets/" + BUILD_REF + "/lib/skeleton/router.js",
"/assets/" + BUILD_REF + "/lib/skeleton/lifecycle.js",
"/assets/" + BUILD_REF + "/lib/error.js",
"/assets/" + BUILD_REF + "/locales/index.js",
"/assets/" + BUILD_REF + "/model/config.js",
"/assets/" + BUILD_REF + "/model/chromecast.js",
"/assets/" + BUILD_REF + "/model/session.js",
"/assets/" + BUILD_REF + "/model/plugin.js",
"/assets/" + BUILD_REF + "/pages/ctrl_connectpage.js",
"/assets/" + BUILD_REF + "/pages/ctrl_connectpage.css",
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form.css",
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form.js",
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_forkme.js",
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_poweredby.js",
"/assets/" + BUILD_REF + "/pages/connectpage/model_backend.js",
"/assets/" + BUILD_REF + "/pages/connectpage/model_config.js",
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form_state.js",
"/assets/" + BUILD_REF + "/pages/ctrl_logout.js",
"/assets/" + BUILD_REF + "/pages/ctrl_error.js",
"/assets/" + BUILD_REF + "/pages/ctrl_filespage.js",
"/assets/" + BUILD_REF + "/pages/ctrl_filespage.css",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.css",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_filesystem.css",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.css",
"/assets/" + BUILD_REF + "/pages/filespage/model_acl.js",
"/assets/" + BUILD_REF + "/pages/filespage/cache.js",
"/assets/" + BUILD_REF + "/pages/filespage/thing.js",
"/assets/" + BUILD_REF + "/pages/filespage/thing.css",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_filesystem.js",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.js",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.js",
"/assets/" + BUILD_REF + "/pages/filespage/state_config.js",
"/assets/" + BUILD_REF + "/pages/filespage/helper.js",
"/assets/" + BUILD_REF + "/pages/filespage/model_files.js",
"/assets/" + BUILD_REF + "/pages/filespage/model_virtual_layer.js",
"/assets/" + BUILD_REF + "/pages/filespage/modal.css",
"/assets/" + BUILD_REF + "/pages/filespage/modal_tag.js",
"/assets/" + BUILD_REF + "/pages/filespage/modal_tag.css",
"/assets/" + BUILD_REF + "/pages/filespage/modal_share.js",
"/assets/" + BUILD_REF + "/pages/filespage/modal_share.css",
"/assets/" + BUILD_REF + "/pages/filespage/modal_rename.js",
"/assets/" + BUILD_REF + "/pages/filespage/modal_delete.js",
"/assets/" + BUILD_REF + "/pages/filespage/state_selection.js",
"/assets/" + BUILD_REF + "/pages/filespage/state_newthing.js",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.js",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.css",
"/assets/" + BUILD_REF + "/pages/ctrl_viewerpage.js",
"/assets/" + BUILD_REF + "/pages/ctrl_viewerpage.css",
"/assets/" + BUILD_REF + "/pages/viewerpage/mimetype.js",
"/assets/" + BUILD_REF + "/pages/viewerpage/model_files.js",
"/assets/" + BUILD_REF + "/pages/viewerpage/common.js",
"/assets/" + BUILD_REF + "/pages/viewerpage/application_downloader.js",
"/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.js",
"/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.css",
}
var bundlePlain bytes.Buffer
for _, path := range paths {
curPath := "/assets/" + strings.TrimPrefix(path, "/assets/"+BUILD_REF+"/")
f := applyPatch(curPath)
if f == nil {
file, err := WWWPublic.Open(curPath)
if err != nil {
file, err = WWWPublic.Open(curPath)
if err != nil {
Log.Warning("static::sse failed to find file %s", curPath)
return
}
fmt.Fprintf(res, "event: %s\n", "static::raw")
} else {
fmt.Fprintf(res, "event: %s\n", "static::gzip")
Log.Warning("static::bundler failed to find file %s", err.Error())
continue
}
f = new(bytes.Buffer)
if _, err := io.Copy(f, file); err != nil {
Log.Warning("static::bundler msg=copy_error err=%s", err.Error())
continue
}
file.Close()
}
fmt.Fprintf(res, "id: %s\n", urls[i])
fmt.Fprintf(res, "data: ")
b, _ := io.ReadAll(file)
res.Write([]byte(encodeB128(b)))
fmt.Fprintf(res, "\n\n")
res.(http.Flusher).Flush()
file.Close()
code, err := json.Marshal(f.String())
if err != nil {
Log.Warning("static::bundle msg=marshal_failed path=%s err=%s", path, err.Error())
continue
}
fmt.Fprintf(&bundlePlain, "bundler.register(%q, %s);\n", path, code)
}
fmt.Fprint(res, "\n")
res.(http.Flusher).Flush()
}
bundleBr, _ := cbrotli.Encode(bundlePlain.Bytes(), cbrotli.WriterOptions{Quality: 8})
func encodeB128(src []byte) string { // decoder is in public/assets/sw.js
if len(src) == 0 {
return ""
}
out := make([]rune, 0, len(src)+len(src)/7) // N + N/7 runes (+tail)
var bits uint32
var n int // bits held in 'bits'
emit := func(d byte) {
if d == 0x00 || d == 0x0A || d == 0x0D { // NUL, LF, CR
out = append(out, rune(0x80|d)) // 2-byte UTF-8, still decodable as d&0x7F
return func(ctx *App, res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "application/javascript")
if strings.Contains(req.Header.Get("Accept-Encoding"), "br") {
res.Header().Set("Content-Encoding", "br")
res.Write(bundleBr)
return
}
out = append(out, rune(d)) // ASCII, 1-byte UTF-8
res.Write(bundlePlain.Bytes())
}
for _, b := range src {
bits = (bits << 8) | uint32(b)
n += 8
for n >= 7 {
n -= 7
emit(byte((bits >> n) & 0x7F))
}
}
if n > 0 { // tail
emit(byte((bits << (7 - n)) & 0x7F))
}
return string(out)
}
func applyPatch(filePath string) (file *bytes.Buffer) {
@ -373,128 +460,6 @@ func applyPatch(filePath string) (file *bytes.Buffer) {
return nil
}
func preload() string {
out, _ := json.Marshal([][]string{
{
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs.min.js",
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-ajax.min.js",
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-shared.min.js",
"/assets/" + BUILD_REF + "/css/designsystem.css",
"/assets/" + BUILD_REF + "/css/designsystem_input.css",
"/assets/" + BUILD_REF + "/css/designsystem_textarea.css",
"/assets/" + BUILD_REF + "/css/designsystem_inputgroup.css",
"/assets/" + BUILD_REF + "/css/designsystem_checkbox.css",
"/assets/" + BUILD_REF + "/css/designsystem_formbuilder.css",
"/assets/" + BUILD_REF + "/css/designsystem_button.css",
"/assets/" + BUILD_REF + "/css/designsystem_icon.css",
"/assets/" + BUILD_REF + "/css/designsystem_dropdown.css",
"/assets/" + BUILD_REF + "/css/designsystem_container.css",
"/assets/" + BUILD_REF + "/css/designsystem_box.css",
"/assets/" + BUILD_REF + "/css/designsystem_darkmode.css",
"/assets/" + BUILD_REF + "/css/designsystem_skeleton.css",
"/assets/" + BUILD_REF + "/css/designsystem_utils.css",
"/assets/" + BUILD_REF + "/css/designsystem_alert.css",
"/assets/" + BUILD_REF + "/pages/filespage/modal.css",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.css",
"/assets/" + BUILD_REF + "/pages/filespage/modal_share.css",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_filesystem.css",
"/assets/" + BUILD_REF + "/pages/ctrl_filespage.css",
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form.css",
"/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.css",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.css",
"/assets/" + BUILD_REF + "/components/decorator_shell_filemanager.css",
},
{
"/assets/" + BUILD_REF + "/boot/ctrl_boot_frontoffice.js",
"/assets/" + BUILD_REF + "/boot/router_frontoffice.js",
"/assets/" + BUILD_REF + "/boot/common.js",
"/assets/" + BUILD_REF + "/components/loader.js",
"/assets/" + BUILD_REF + "/components/modal.js",
"/assets/" + BUILD_REF + "/components/modal.css",
"/assets/" + BUILD_REF + "/components/notification.js",
"/assets/" + BUILD_REF + "/components/notification.css",
"/assets/" + BUILD_REF + "/components/sidebar.js",
"/assets/" + BUILD_REF + "/components/sidebar.css",
"/assets/" + BUILD_REF + "/components/dropdown.js",
"/assets/" + BUILD_REF + "/components/decorator_shell_filemanager.js",
"/assets/" + BUILD_REF + "/helpers/loader.js",
"/assets/" + BUILD_REF + "/helpers/log.js",
"/assets/" + BUILD_REF + "/helpers/sdk.js",
"/assets/" + BUILD_REF + "/locales/index.js",
"/assets/" + BUILD_REF + "/lib/store.js",
"/assets/" + BUILD_REF + "/lib/form.js",
"/assets/" + BUILD_REF + "/lib/path.js",
"/assets/" + BUILD_REF + "/lib/random.js",
"/assets/" + BUILD_REF + "/model/config.js",
"/assets/" + BUILD_REF + "/model/chromecast.js",
"/assets/" + BUILD_REF + "/model/session.js",
"/assets/" + BUILD_REF + "/pages/filespage/model_acl.js",
"/assets/" + BUILD_REF + "/pages/filespage/cache.js",
"/assets/" + BUILD_REF + "/pages/filespage/thing.css",
"/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.js",
},
{
"/assets/" + BUILD_REF + "/components/form.js",
"/assets/" + BUILD_REF + "/components/icon.js",
"/assets/" + BUILD_REF + "/lib/settings.js",
"/assets/" + BUILD_REF + "/lib/skeleton/index.js",
"/assets/" + BUILD_REF + "/lib/rx.js",
"/assets/" + BUILD_REF + "/lib/ajax.js",
"/assets/" + BUILD_REF + "/lib/animate.js",
"/assets/" + BUILD_REF + "/lib/assert.js",
"/assets/" + BUILD_REF + "/lib/dom.js",
"/assets/" + BUILD_REF + "/lib/skeleton/router.js",
"/assets/" + BUILD_REF + "/lib/skeleton/lifecycle.js",
"/assets/" + BUILD_REF + "/lib/error.js",
"/assets/" + BUILD_REF + "/pages/ctrl_connectpage.js",
"/assets/" + BUILD_REF + "/pages/ctrl_connectpage.css",
"/assets/" + BUILD_REF + "/pages/ctrl_error.js",
"/assets/" + BUILD_REF + "/pages/ctrl_filespage.js",
"/assets/" + BUILD_REF + "/pages/ctrl_viewerpage.js",
"/assets/" + BUILD_REF + "/pages/ctrl_viewerpage.css",
"/assets/" + BUILD_REF + "/pages/ctrl_logout.js",
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form.js",
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_forkme.js",
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_poweredby.js",
"/assets/" + BUILD_REF + "/pages/connectpage/model_backend.js",
"/assets/" + BUILD_REF + "/pages/connectpage/model_config.js",
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form_state.js",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_filesystem.js",
},
{
"/assets/" + BUILD_REF + "/components/breadcrumb.js",
"/assets/" + BUILD_REF + "/components/breadcrumb.css",
"/assets/" + BUILD_REF + "/components/skeleton.js",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.js",
"/assets/" + BUILD_REF + "/pages/filespage/state_config.js",
"/assets/" + BUILD_REF + "/pages/filespage/helper.js",
"/assets/" + BUILD_REF + "/pages/filespage/model_files.js",
"/assets/" + BUILD_REF + "/pages/filespage/model_virtual_layer.js",
"/assets/" + BUILD_REF + "/pages/filespage/modal_tag.js",
"/assets/" + BUILD_REF + "/pages/filespage/modal_tag.css",
"/assets/" + BUILD_REF + "/pages/filespage/modal_rename.js",
"/assets/" + BUILD_REF + "/pages/filespage/modal_delete.js",
"/assets/" + BUILD_REF + "/pages/filespage/state_selection.js",
"/assets/" + BUILD_REF + "/pages/filespage/state_newthing.js",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.js",
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.css",
"/assets/" + BUILD_REF + "/pages/viewerpage/mimetype.js",
"/assets/" + BUILD_REF + "/pages/viewerpage/model_files.js",
"/assets/" + BUILD_REF + "/pages/viewerpage/common.js",
},
{
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.js",
"/assets/" + BUILD_REF + "/pages/filespage/thing.js",
"/assets/" + BUILD_REF + "/pages/filespage/modal_share.js",
"/assets/" + BUILD_REF + "/pages/viewerpage/application_downloader.js",
"/assets/" + BUILD_REF + "/model/plugin.js",
},
})
return string(out)
}
func signature() string {
text := BUILD_REF
patches := Hooks.Get.StaticPatch()

View file

@ -93,7 +93,7 @@ func Build(r *mux.Router, a App) {
r.HandleFunc(WithBase("/api/plugin"), NewMiddlewareChain(PluginExportHandler, append(middlewares, PublicCORS), a)).Methods("GET", "OPTIONS")
r.HandleFunc(WithBase("/api/config"), NewMiddlewareChain(PublicConfigHandler, append(middlewares, PublicCORS), a)).Methods("GET", "OPTIONS")
middlewares = []Middleware{StaticHeaders, SecureHeaders, PublicCORS, PluginInjector}
r.PathPrefix(WithBase("/assets/bundle")).Handler(http.HandlerFunc(NewMiddlewareChain(ServeBundle, middlewares, a))).Methods("GET", "OPTIONS")
r.PathPrefix(WithBase("/assets/bundle.js")).Handler(http.HandlerFunc(NewMiddlewareChain(ServeBundle(), middlewares, a))).Methods("GET", "OPTIONS")
r.HandleFunc(WithBase("/assets/"+BUILD_REF+"/plugin/{name}.zip/{path:.+}"), NewMiddlewareChain(PluginStaticHandler, middlewares, a)).Methods("GET", "OPTIONS", "HEAD")
r.HandleFunc(WithBase("/assets/"+BUILD_REF+"/plugin/{name}.zip"), NewMiddlewareChain(PluginDownloadHandler, middlewares, a)).Methods("GET")
r.HandleFunc(WithBase("/assets/plugin/{name}.zip"), NewMiddlewareChain(PluginDownloadHandler, middlewares, a)).Methods("GET")