aboutsummaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/dist/v3/assets/index-BmeGit54.css1
-rw-r--r--web/dist/v3/assets/index-DfsH-YDt.js17
-rw-r--r--web/dist/v3/index.html14
-rw-r--r--web/dist/v3/vite.svg1
-rw-r--r--web/frontend.go69
-rw-r--r--web/web.go9
-rw-r--r--web/web_test.go8
7 files changed, 79 insertions, 40 deletions
diff --git a/web/dist/v3/assets/index-BmeGit54.css b/web/dist/v3/assets/index-BmeGit54.css
new file mode 100644
index 0000000..a9ec929
--- /dev/null
+++ b/web/dist/v3/assets/index-BmeGit54.css
@@ -0,0 +1 @@
+:root{font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#535bf2}body{margin:0;display:flex;place-items:center;min-width:320px;min-height:100vh}h1{font-size:3.2em;line-height:1.1}#app{max-width:1280px;margin:0 auto;padding:2rem;text-align:center}.logo{height:6em;padding:1.5em;will-change:filter;transition:filter .3s}.logo:hover{filter:drop-shadow(0 0 2em #646cffaa)}.logo.vanilla:hover{filter:drop-shadow(0 0 2em #3178c6aa)}.card{padding:2em}.read-the-docs{color:#888}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#1a1a1a;cursor:pointer;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}@media(prefers-color-scheme:light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}}
diff --git a/web/dist/v3/assets/index-DfsH-YDt.js b/web/dist/v3/assets/index-DfsH-YDt.js
new file mode 100644
index 0000000..681443b
--- /dev/null
+++ b/web/dist/v3/assets/index-DfsH-YDt.js
@@ -0,0 +1,17 @@
+(function(){const c=document.createElement("link").relList;if(c&&c.supports&&c.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))o(e);new MutationObserver(e=>{for(const t of e)if(t.type==="childList")for(const s of t.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&o(s)}).observe(document,{childList:!0,subtree:!0});function r(e){const t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?t.credentials="include":e.crossOrigin==="anonymous"?t.credentials="omit":t.credentials="same-origin",t}function o(e){if(e.ep)return;e.ep=!0;const t=r(e);fetch(e.href,t)}})();const n="data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20aria-hidden='true'%20role='img'%20class='iconify%20iconify--logos'%20width='32'%20height='32'%20preserveAspectRatio='xMidYMid%20meet'%20viewBox='0%200%20256%20256'%3e%3cpath%20fill='%23007ACC'%20d='M0%20128v128h256V0H0z'%3e%3c/path%3e%3cpath%20fill='%23FFF'%20d='m56.612%20128.85l-.081%2010.483h33.32v94.68h23.568v-94.68h33.321v-10.28c0-5.69-.122-10.444-.284-10.566c-.122-.162-20.4-.244-44.983-.203l-44.74.122l-.121%2010.443Zm149.955-10.742c6.501%201.625%2011.459%204.51%2016.01%209.224c2.357%202.52%205.851%207.111%206.136%208.208c.08.325-11.053%207.802-17.798%2011.988c-.244.162-1.22-.894-2.317-2.52c-3.291-4.795-6.745-6.867-12.028-7.233c-7.76-.528-12.759%203.535-12.718%2010.321c0%201.992.284%203.17%201.097%204.795c1.707%203.536%204.876%205.649%2014.832%209.956c18.326%207.883%2026.168%2013.084%2031.045%2020.48c5.445%208.249%206.664%2021.415%202.966%2031.208c-4.063%2010.646-14.14%2017.879-28.323%2020.276c-4.388.772-14.79.65-19.504-.203c-10.28-1.828-20.033-6.908-26.047-13.572c-2.357-2.6-6.949-9.387-6.664-9.874c.122-.163%201.178-.813%202.356-1.504c1.138-.65%205.446-3.129%209.509-5.485l7.355-4.267l1.544%202.276c2.154%203.29%206.867%207.801%209.712%209.305c8.167%204.307%2019.383%203.698%2024.909-1.26c2.357-2.153%203.332-4.388%203.332-7.68c0-2.966-.366-4.266-1.91-6.501c-1.99-2.845-6.054-5.242-17.595-10.24c-13.206-5.69-18.895-9.224-24.096-14.832c-3.007-3.25-5.852-8.452-7.03-12.8c-.975-3.617-1.22-12.678-.447-16.335c2.723-12.76%2012.353-21.659%2026.25-24.3c4.51-.853%2014.994-.528%2019.424.569Z'%3e%3c/path%3e%3c/svg%3e",l="/v3/vite.svg";function a(i){let c=0;const r=o=>{c=o,i.innerHTML=`count is ${c}`};i.addEventListener("click",()=>r(c+1)),r(0)}document.querySelector("#app").innerHTML=`
+ <div>
+ <a href="https://vite.dev" target="_blank">
+ <img src="${l}" class="logo" alt="Vite logo" />
+ </a>
+ <a href="https://www.typescriptlang.org/" target="_blank">
+ <img src="${n}" class="logo vanilla" alt="TypeScript logo" />
+ </a>
+ <h1>Vite + TypeScript</h1>
+ <div class="card">
+ <button id="counter" type="button"></button>
+ </div>
+ <p class="read-the-docs">
+ Click on the Vite and TypeScript logos to learn more
+ </p>
+ </div>
+`;a(document.querySelector("#counter"));
diff --git a/web/dist/v3/index.html b/web/dist/v3/index.html
new file mode 100644
index 0000000..bd98fe7
--- /dev/null
+++ b/web/dist/v3/index.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <link rel="icon" type="image/svg+xml" href="/v3/vite.svg" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>frontend-vanilla</title>
+ <script type="module" crossorigin src="/v3/assets/index-DfsH-YDt.js"></script>
+ <link rel="stylesheet" crossorigin href="/v3/assets/index-BmeGit54.css">
+ </head>
+ <body>
+ <div id="app"></div>
+ </body>
+</html>
diff --git a/web/dist/v3/vite.svg b/web/dist/v3/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/web/dist/v3/vite.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg> \ No newline at end of file
diff --git a/web/frontend.go b/web/frontend.go
index d77c974..00074ec 100644
--- a/web/frontend.go
+++ b/web/frontend.go
@@ -8,46 +8,49 @@ import (
"strings"
)
-func ServeFrontend(w http.ResponseWriter, r *http.Request) {
- // Use fs.Sub to treat dist/v2 as the root
- box, err := fs.Sub(frontendFiles, "dist/v2")
- if err != nil {
- http.Error(w, "frontend not found", http.StatusNotFound)
- return
- }
+func ServeFrontend(distDir string) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ // Use fs.Sub to treat distDir as the root
+ box, err := fs.Sub(frontendFiles, distDir)
+ if err != nil {
+ http.Error(w, "frontend not found", http.StatusNotFound)
+ return
+ }
- // Get the file path from the URL
- path := r.URL.Path
- path = strings.TrimPrefix(path, "/")
+ // Get the file path from the URL
+ path := r.URL.Path
+ path = strings.TrimPrefix(path, "/")
- // If path is empty, it's index.html
- if path == "" {
- path = "index.html"
- }
+ // If path is empty, it's index.html
+ if path == "" {
+ path = "index.html"
+ }
- // Try to open the file
- f, err := box.Open(path)
- if err != nil {
- // If file not found, serve index.html for client-side routing
- if !strings.Contains(filepath.Base(path), ".") {
- f, err = box.Open("index.html")
- if err != nil {
- http.Error(w, "frontend not found", http.StatusNotFound)
+ // Try to open the file
+ f, err := box.Open(path)
+ if err != nil {
+ // If file not found, serve index.html for client-side routing
+ // but only if it looks like a route (no extension)
+ if !strings.Contains(filepath.Base(path), ".") {
+ f, err = box.Open("index.html")
+ if err != nil {
+ http.Error(w, "frontend not found", http.StatusNotFound)
+ return
+ }
+ path = "index.html"
+ } else {
+ http.Error(w, "not found", http.StatusNotFound)
return
}
- path = "index.html"
- } else {
- http.Error(w, "not found", http.StatusNotFound)
+ }
+ defer func() { _ = f.Close() }()
+
+ d, err := f.Stat()
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
return
}
- }
- defer func() { _ = f.Close() }()
- d, err := f.Stat()
- if err != nil {
- http.Error(w, "internal error", http.StatusInternalServerError)
- return
+ http.ServeContent(w, r, path, d.ModTime(), f.(io.ReadSeeker))
}
-
- http.ServeContent(w, r, path, d.ModTime(), f.(io.ReadSeeker))
}
diff --git a/web/web.go b/web/web.go
index 1f1bd62..bb00773 100644
--- a/web/web.go
+++ b/web/web.go
@@ -35,7 +35,7 @@ var (
//go:embed static/*
staticFiles embed.FS
- //go:embed dist/v2/*
+ //go:embed dist/v2/* dist/v3/*
frontendFiles embed.FS
)
@@ -225,9 +225,12 @@ func NewRouter(cfg *config.Settings) http.Handler {
// New Frontend (React/Vite) from web/dist/v2
// Default route
- mux.Handle("/", GzipMiddleware(http.HandlerFunc(ServeFrontend)))
+ mux.Handle("/", GzipMiddleware(ServeFrontend("dist/v2")))
// Also keep /v2/ for explicit access
- mux.Handle("/v2/", GzipMiddleware(http.StripPrefix("/v2/", http.HandlerFunc(ServeFrontend))))
+ mux.Handle("/v2/", GzipMiddleware(http.StripPrefix("/v2/", ServeFrontend("dist/v2"))))
+
+ // Vanilla JS (v3)
+ mux.Handle("/v3/", GzipMiddleware(http.StripPrefix("/v3/", ServeFrontend("dist/v3"))))
// Legacy UI at /v1/
mux.Handle("/v1/", GzipMiddleware(http.StripPrefix("/v1/", AuthWrap(http.HandlerFunc(indexHandler)))))
diff --git a/web/web_test.go b/web/web_test.go
index ad79581..a208155 100644
--- a/web/web_test.go
+++ b/web/web_test.go
@@ -422,7 +422,7 @@ func TestServeFrontend(t *testing.T) {
rr := httptest.NewRecorder()
// Mimic the routing in Serve()
- handler := http.StripPrefix("/v2/", http.HandlerFunc(ServeFrontend))
+ handler := http.StripPrefix("/v2/", ServeFrontend("dist/v2"))
handler.ServeHTTP(rr, req)
// We expect 200 if built, or maybe panic if box not found (rice.MustFindBox)
@@ -517,7 +517,7 @@ func TestServeFrontendEdgeCases(t *testing.T) {
// 1. Missing file with extension should 404
req := httptest.NewRequest("GET", "/v2/missing.js", nil)
rr := httptest.NewRecorder()
- handler := http.StripPrefix("/v2/", http.HandlerFunc(ServeFrontend))
+ handler := http.StripPrefix("/v2/", ServeFrontend("dist/v2"))
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusNotFound {
t.Errorf("Expected %d for missing asset, got %d", http.StatusNotFound, rr.Code)
@@ -632,7 +632,7 @@ func TestImageProxyHandlerInvalidBase64(t *testing.T) {
func TestServeFrontendNotFound(t *testing.T) {
req := httptest.NewRequest("GET", "/not-actually-a-file", nil)
rr := httptest.NewRecorder()
- ServeFrontend(rr, req)
+ ServeFrontend("dist/v2")(rr, req)
// Should fallback to index.html if it's not a dot-extension file
if rr.Code != http.StatusOK {
t.Errorf("Expected %d (fallback to index.html), got %d", http.StatusOK, rr.Code)
@@ -665,7 +665,7 @@ func TestImageProxyHeaders(t *testing.T) {
func TestServeFrontendAssetNotFound(t *testing.T) {
req := httptest.NewRequest("GET", "/static/missing.js", nil)
rr := httptest.NewRecorder()
- ServeFrontend(rr, req)
+ ServeFrontend("dist/v2")(rr, req)
if rr.Code != http.StatusNotFound {
t.Errorf("Expected %d for missing asset, got %d", http.StatusNotFound, rr.Code)
}