mirror of
https://github.com/GlueOps/autoglue.git
synced 2026-02-13 04:40:05 +01:00
feat: sdk migration in progress
This commit is contained in:
16
internal/web/devproxy.go
Normal file
16
internal/web/devproxy.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func DevProxy(target string) (http.Handler, error) {
|
||||
u, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := httputil.NewSingleHostReverseProxy(u)
|
||||
return p, nil
|
||||
}
|
||||
2
internal/web/dist/assets/index-BvUUUOIq.css
vendored
Normal file
2
internal/web/dist/assets/index-BvUUUOIq.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
internal/web/dist/assets/index-BvUUUOIq.css.br
vendored
Normal file
BIN
internal/web/dist/assets/index-BvUUUOIq.css.br
vendored
Normal file
Binary file not shown.
BIN
internal/web/dist/assets/index-BvUUUOIq.css.gz
vendored
Normal file
BIN
internal/web/dist/assets/index-BvUUUOIq.css.gz
vendored
Normal file
Binary file not shown.
10
internal/web/dist/assets/index-CFwByDWI.js
vendored
Normal file
10
internal/web/dist/assets/index-CFwByDWI.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
internal/web/dist/assets/index-CFwByDWI.js.br
vendored
Normal file
BIN
internal/web/dist/assets/index-CFwByDWI.js.br
vendored
Normal file
Binary file not shown.
BIN
internal/web/dist/assets/index-CFwByDWI.js.gz
vendored
Normal file
BIN
internal/web/dist/assets/index-CFwByDWI.js.gz
vendored
Normal file
Binary file not shown.
1
internal/web/dist/assets/index-CFwByDWI.js.map
vendored
Normal file
1
internal/web/dist/assets/index-CFwByDWI.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
4
internal/web/dist/assets/react-BZmgNp9X.js
vendored
Normal file
4
internal/web/dist/assets/react-BZmgNp9X.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
internal/web/dist/assets/react-BZmgNp9X.js.br
vendored
Normal file
BIN
internal/web/dist/assets/react-BZmgNp9X.js.br
vendored
Normal file
Binary file not shown.
BIN
internal/web/dist/assets/react-BZmgNp9X.js.gz
vendored
Normal file
BIN
internal/web/dist/assets/react-BZmgNp9X.js.gz
vendored
Normal file
Binary file not shown.
1
internal/web/dist/assets/react-BZmgNp9X.js.map
vendored
Normal file
1
internal/web/dist/assets/react-BZmgNp9X.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
15
internal/web/dist/index.html
vendored
Normal file
15
internal/web/dist/index.html
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ui</title>
|
||||
<script type="module" crossorigin src="/assets/index-CFwByDWI.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/react-BZmgNp9X.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BvUUUOIq.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
internal/web/dist/index.html.br
vendored
Normal file
BIN
internal/web/dist/index.html.br
vendored
Normal file
Binary file not shown.
BIN
internal/web/dist/index.html.gz
vendored
Normal file
BIN
internal/web/dist/index.html.gz
vendored
Normal file
Binary file not shown.
1
internal/web/dist/vite.svg
vendored
Normal file
1
internal/web/dist/vite.svg
vendored
Normal file
@@ -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>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
239
internal/web/static.go
Normal file
239
internal/web/static.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NOTE: Vite outputs to web/dist with assets in dist/assets.
|
||||
// If you add more nested folders in the future, include them here too.
|
||||
|
||||
//go:embed dist
|
||||
var distFS embed.FS
|
||||
|
||||
// spaFileSystem serves embedded dist/ files with SPA fallback to index.html
|
||||
type spaFileSystem struct {
|
||||
fs fs.FS
|
||||
}
|
||||
|
||||
func (s spaFileSystem) Open(name string) (fs.File, error) {
|
||||
// Normalize, strip leading slash
|
||||
if strings.HasPrefix(name, "/") {
|
||||
name = name[1:]
|
||||
}
|
||||
// Try exact file
|
||||
f, err := s.fs.Open(name)
|
||||
if err == nil {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// If the requested file doesn't exist, fall back to index.html for SPA routes
|
||||
// BUT only if it's not obviously a static asset extension
|
||||
ext := strings.ToLower(filepath.Ext(name))
|
||||
switch ext {
|
||||
case ".js", ".css", ".map", ".json", ".txt", ".ico", ".png", ".jpg", ".jpeg",
|
||||
".svg", ".webp", ".gif", ".woff", ".woff2", ".ttf", ".otf", ".eot", ".wasm", ".br", ".gz":
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
|
||||
return s.fs.Open("index.html")
|
||||
}
|
||||
|
||||
func newDistFS() (fs.FS, error) {
|
||||
return fs.Sub(distFS, "dist")
|
||||
}
|
||||
|
||||
// SPAHandler returns an http.Handler that serves the embedded UI (with caching)
|
||||
func SPAHandler() (http.Handler, error) {
|
||||
sub, err := newDistFS()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spa := spaFileSystem{fs: sub}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.URL.Path, "/api/") ||
|
||||
r.URL.Path == "/api" ||
|
||||
strings.HasPrefix(r.URL.Path, "/swagger") ||
|
||||
strings.HasPrefix(r.URL.Path, "/debug/pprof") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
filePath := strings.TrimPrefix(path.Clean(r.URL.Path), "/")
|
||||
if filePath == "" {
|
||||
filePath = "index.html"
|
||||
}
|
||||
|
||||
// Try compressed variants for assets and HTML
|
||||
// NOTE: we only change *Content-Encoding*; Content-Type derives from original ext
|
||||
// Always vary on Accept-Encoding
|
||||
w.Header().Add("Vary", "Accept-Encoding")
|
||||
|
||||
enc := r.Header.Get("Accept-Encoding")
|
||||
if tryServeCompressed(w, r, spa, filePath, enc) {
|
||||
return
|
||||
}
|
||||
|
||||
// Fallback: normal open (or SPA fallback)
|
||||
f, err := spa.Open(filePath)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if strings.HasSuffix(filePath, ".html") {
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
} else {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
}
|
||||
|
||||
info, _ := f.Stat()
|
||||
modTime := time.Now()
|
||||
if info != nil {
|
||||
modTime = info.ModTime()
|
||||
}
|
||||
http.ServeContent(w, r, filePath, modTime, file{f})
|
||||
}), nil
|
||||
}
|
||||
|
||||
func tryServeCompressed(w http.ResponseWriter, r *http.Request, spa spaFileSystem, filePath, enc string) bool {
|
||||
wantsBR := strings.Contains(enc, "br")
|
||||
wantsGZ := strings.Contains(enc, "gzip")
|
||||
|
||||
type cand struct {
|
||||
logical string // MIME/type decision uses this (uncompressed name)
|
||||
physical string // actual file we open (with .br/.gz)
|
||||
enc string
|
||||
}
|
||||
|
||||
var cands []cand
|
||||
|
||||
// 1) direct compressed variant of requested path (rare for SPA routes, but cheap to try)
|
||||
if wantsBR {
|
||||
cands = append(cands, cand{logical: filePath, physical: filePath + ".br", enc: "br"})
|
||||
}
|
||||
if wantsGZ {
|
||||
cands = append(cands, cand{logical: filePath, physical: filePath + ".gz", enc: "gzip"})
|
||||
}
|
||||
|
||||
// 2) SPA route: fall back to compressed index.html
|
||||
if filepath.Ext(filePath) == "" {
|
||||
if wantsBR {
|
||||
cands = append(cands, cand{logical: "index.html", physical: "index.html.br", enc: "br"})
|
||||
}
|
||||
if wantsGZ {
|
||||
cands = append(cands, cand{logical: "index.html", physical: "index.html.gz", enc: "gzip"})
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range cands {
|
||||
f, err := spa.fs.Open(c.physical) // open EXACT path so we don't accidentally get SPA fallback
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Cache headers
|
||||
if strings.HasSuffix(c.logical, ".html") {
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
} else {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
}
|
||||
|
||||
if ct := mimeByExt(path.Ext(c.logical)); ct != "" {
|
||||
w.Header().Set("Content-Type", ct)
|
||||
}
|
||||
w.Header().Set("Content-Encoding", c.enc)
|
||||
w.Header().Add("Vary", "Accept-Encoding")
|
||||
|
||||
info, _ := f.Stat()
|
||||
modTime := time.Now()
|
||||
if info != nil {
|
||||
modTime = info.ModTime()
|
||||
}
|
||||
|
||||
// Serve the precompressed bytes
|
||||
http.ServeContent(w, r, c.physical, modTime, file{f})
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func serveIfExists(w http.ResponseWriter, r *http.Request, spa spaFileSystem, filePath, ext, encoding string) bool {
|
||||
cf := filePath + ext
|
||||
f, err := spa.Open(cf)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Set caching headers
|
||||
if strings.HasSuffix(filePath, ".html") {
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
} else {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
}
|
||||
// Preserve original content type by extension of *uncompressed* file
|
||||
if ct := mimeByExt(path.Ext(filePath)); ct != "" {
|
||||
w.Header().Set("Content-Type", ct)
|
||||
}
|
||||
w.Header().Set("Content-Encoding", encoding)
|
||||
|
||||
info, _ := f.Stat()
|
||||
modTime := time.Now()
|
||||
if info != nil {
|
||||
modTime = info.ModTime()
|
||||
}
|
||||
|
||||
// Serve the compressed bytes as an io.ReadSeeker if possible
|
||||
http.ServeContent(w, r, cf, modTime, file{f})
|
||||
return true
|
||||
}
|
||||
|
||||
func mimeByExt(ext string) string {
|
||||
switch strings.ToLower(ext) {
|
||||
case ".html":
|
||||
return "text/html; charset=utf-8"
|
||||
case ".js":
|
||||
return "application/javascript"
|
||||
case ".css":
|
||||
return "text/css; charset=utf-8"
|
||||
case ".json":
|
||||
return "application/json"
|
||||
case ".svg":
|
||||
return "image/svg+xml"
|
||||
case ".png":
|
||||
return "image/png"
|
||||
case ".jpg", ".jpeg":
|
||||
return "image/jpeg"
|
||||
case ".webp":
|
||||
return "image/webp"
|
||||
case ".ico":
|
||||
return "image/x-icon"
|
||||
case ".woff2":
|
||||
return "font/woff2"
|
||||
case ".woff":
|
||||
return "font/woff"
|
||||
default:
|
||||
return "" // let Go sniff if empty
|
||||
}
|
||||
}
|
||||
|
||||
// file wraps fs.File to implement io.ReadSeeker if possible (for ServeContent)
|
||||
type file struct{ fs.File }
|
||||
|
||||
func (f file) Seek(offset int64, whence int) (int64, error) {
|
||||
if s, ok := f.File.(io.Seeker); ok {
|
||||
return s.Seek(offset, whence)
|
||||
}
|
||||
// Fallback: not seekable
|
||||
return 0, fs.ErrInvalid
|
||||
}
|
||||
Reference in New Issue
Block a user