feat: add PWA support with dynamic base URL (#4608)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Amir Raminfar
2026-04-10 13:09:13 -07:00
committed by GitHub
parent f5f736b6ff
commit af4412f667
5 changed files with 89 additions and 10 deletions
+4
View File
@@ -7,3 +7,7 @@ Object.values(import.meta.glob<{ install: (app: VueApp) => void }>("./modules/*.
i.install?.(app),
);
app.mount("#app");
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register(withBase("/sw.js"));
}
+29
View File
@@ -0,0 +1,29 @@
package web
import (
"encoding/json"
"net/http"
)
func (h *handler) manifest(w http.ResponseWriter, req *http.Request) {
base := ""
if h.config.Base != "/" {
base = h.config.Base
}
manifest := map[string]any{
"name": "Dozzle",
"short_name": "Dozzle",
"start_url": base + "/",
"display": "standalone",
"lang": "en",
"scope": base + "/",
"description": "A log viewer for containers",
"icons": []map[string]string{
{"src": base + "/apple-touch-icon.png", "sizes": "512x512", "type": "image/png"},
},
}
w.Header().Set("Content-Type", "application/manifest+json")
json.NewEncoder(w).Encode(manifest)
}
+2
View File
@@ -199,6 +199,8 @@ func createRouter(h *handler) *chi.Mux {
})
r.Get("/healthcheck", h.healthcheck)
r.Get("/manifest.webmanifest", h.manifest)
r.Get("/sw.js", h.serviceWorker)
defaultHandler := http.StripPrefix(strings.Replace(base+"/", "//", "/", 1), http.HandlerFunc(h.index))
r.With(Brotli).Get("/*", func(w http.ResponseWriter, req *http.Request) {
+54
View File
@@ -0,0 +1,54 @@
package web
import (
"fmt"
"net/http"
)
const serviceWorkerTemplate = `
const CACHE_NAME = "dozzle-%s";
self.addEventListener("install", (event) => {
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((names) =>
Promise.all(
names.filter((name) => name !== CACHE_NAME).map((name) => caches.delete(name))
)
)
);
self.clients.claim();
});
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
// Cache immutable hashed assets (Vite adds hashes to filenames)
if (url.pathname.match(/\/assets\/.*\.[a-f0-9]{8}\./)) {
event.respondWith(
caches.match(event.request).then((cached) => {
if (cached) return cached;
return fetch(event.request).then((response) => {
if (response.ok) {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
}
return response;
});
})
);
return;
}
// Network-first for everything else (API calls, HTML, etc.)
});
`
func (h *handler) serviceWorker(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/javascript")
w.Header().Set("Cache-Control", "no-cache")
fmt.Fprintf(w, serviceWorkerTemplate, h.config.Version)
}
-10
View File
@@ -1,10 +0,0 @@
{
"name": "Dozzle",
"short_name": "Dozzle",
"start_url": "/",
"display": "standalone",
"lang": "en",
"scope": "/",
"description": "A log viewer for containers",
"icons": [{ "src": "/apple-touch-icon.png", "sizes": "512x512", "type": "image/png" }]
}