diff options
Diffstat (limited to 'web')
| -rw-r--r-- | web/dist/v3/assets/index-BmeGit54.css | 1 | ||||
| -rw-r--r-- | web/dist/v3/assets/index-DfsH-YDt.js | 17 | ||||
| -rw-r--r-- | web/dist/v3/index.html | 14 | ||||
| -rw-r--r-- | web/dist/v3/vite.svg | 1 | ||||
| -rw-r--r-- | web/frontend.go | 69 | ||||
| -rw-r--r-- | web/web.go | 9 | ||||
| -rw-r--r-- | web/web_test.go | 8 |
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)) } @@ -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) } |
