Refactor Docker setup to integrate Web UI and streamline configuration
Publish telemt-api gateway Docker image / test (push) Successful in 23s
Publish telemt-api gateway Docker image / build-and-push (push) Successful in 1m54s

- Updated Dockerfile to build and embed the SvelteKit Web UI directly into the gateway image, eliminating the need for a separate web service.
- Modified .dockerignore to exclude unnecessary directories related to the web service.
- Adjusted config.compose.yaml to remove CORS settings for the web service, as the UI now shares the same origin as the API.
- Enhanced README.md to reflect the new single-port architecture for accessing both the Web UI and API.
- Removed the standalone web Dockerfile and updated related documentation for local development and build processes.
This commit is contained in:
Denozordec
2026-03-30 10:42:28 +07:00
parent 6421a9b28d
commit d69849e5e2
15 changed files with 182 additions and 75 deletions
+6
View File
@@ -0,0 +1,6 @@
package webui
import "embed"
//go:embed all:static
var static embed.FS
+68
View File
@@ -0,0 +1,68 @@
package webui
import (
"io/fs"
"mime"
"net/http"
"path"
"strconv"
"strings"
)
// Handler отдаёт статику SvelteKit (embed) и index.html для клиентских маршрутов SPA.
func Handler() http.Handler {
root, err := fs.Sub(static, "static")
if err != nil {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
})
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet && r.Method != http.MethodHead {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
name := strings.TrimPrefix(path.Clean(r.URL.Path), "/")
if name == "." || name == "" {
name = "index.html"
}
b, err := fs.ReadFile(root, name)
if err != nil {
if path.Ext(name) != "" {
http.NotFound(w, r)
return
}
b, err = fs.ReadFile(root, "index.html")
if err != nil {
http.NotFound(w, r)
return
}
name = "index.html"
}
ct := mime.TypeByExtension(path.Ext(name))
if ct == "" {
ct = "application/octet-stream"
}
if strings.HasPrefix(ct, "text/") && !strings.Contains(ct, "charset") {
ct = ct + "; charset=utf-8"
}
w.Header().Set("Content-Type", ct)
if name == "index.html" {
w.Header().Set("Cache-Control", "no-cache")
} else {
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
}
if r.Method == http.MethodHead {
w.Header().Set("Content-Length", strconv.Itoa(len(b)))
w.WriteHeader(http.StatusOK)
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write(b)
})
}
+28
View File
@@ -0,0 +1,28 @@
package webui
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestHandler_index(t *testing.T) {
h := Handler()
rr := httptest.NewRecorder()
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/", nil))
if rr.Code != http.StatusOK {
t.Fatalf("GET /: %d", rr.Code)
}
if got := rr.Header().Get("Content-Type"); got == "" {
t.Fatal("missing Content-Type")
}
}
func TestHandler_spaFallback(t *testing.T) {
h := Handler()
rr := httptest.NewRecorder()
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/servers/foo", nil))
if rr.Code != http.StatusOK {
t.Fatalf("GET /servers/foo: %d", rr.Code)
}
}
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<title>Telemt Panel</title>
</head>
<body>
<p>
Заглушка: UI не собран. Соберите фронт (<code>cd web &amp;&amp; npm run build</code>) перед сборкой образа шлюза, либо
используйте Dockerfile в корне репозитория.
</p>
</body>
</html>