Refactor Docker setup to integrate Web UI and streamline configuration
- 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:
@@ -7,3 +7,6 @@ docs
|
||||
.gitea
|
||||
docker-compose*.yml
|
||||
deploy
|
||||
web/node_modules
|
||||
web/.svelte-kit
|
||||
web/build
|
||||
|
||||
+16
-5
@@ -1,24 +1,35 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# --- Web UI (SvelteKit static) — тот же origin, что и API (порт шлюза)
|
||||
FROM node:22-bookworm AS webui
|
||||
WORKDIR /web
|
||||
COPY web/package.json web/package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY web/ ./
|
||||
# Пустой URL: fetch идёт на тот же хост/порт, что и панель
|
||||
ENV PUBLIC_TELEMT_GATEWAY_URL=
|
||||
RUN npm run build
|
||||
|
||||
FROM golang:1.22-bookworm AS build
|
||||
WORKDIR /src
|
||||
COPY go.mod ./
|
||||
COPY cmd/ ./cmd/
|
||||
COPY internal/ ./internal/
|
||||
COPY --from=webui /web/build/ ./internal/webui/static/
|
||||
RUN go mod tidy && go mod download
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/gateway ./cmd/gateway
|
||||
|
||||
# Alpine: non-root + wget for HEALTHCHECK (distroless has no shell/wget).
|
||||
FROM alpine:3.19
|
||||
RUN apk add --no-cache ca-certificates wget \
|
||||
&& addgroup -S gateway -g 65532 \
|
||||
&& adduser -S -u 65532 -G gateway gateway \
|
||||
&& mkdir -p /var/lib/telemt-gateway \
|
||||
&& chown gateway:gateway /var/lib/telemt-gateway
|
||||
&& addgroup -S gateway -g 65532 \
|
||||
&& adduser -S -u 65532 -G gateway gateway \
|
||||
&& mkdir -p /var/lib/telemt-gateway \
|
||||
&& chown gateway:gateway /var/lib/telemt-gateway
|
||||
COPY --from=build /out/gateway /gateway
|
||||
USER gateway:gateway
|
||||
EXPOSE 8080
|
||||
ENV CONFIG_PATH=/etc/telemt-gateway/config.yaml
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD wget -q -O- http://127.0.0.1:8080/health >/dev/null || exit 1
|
||||
CMD wget -q -O- http://127.0.0.1:8080/health >/dev/null || exit 1
|
||||
ENTRYPOINT ["/gateway"]
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# telemt-api
|
||||
|
||||
HTTP‑шлюз на Go для [Telemt Control API](docs/API.md): один порт, **белый список IP (CIDR)**, маршруты вида `/api/{alias}/…` → `{base_url}/v1/…`, агрегация нескольких инстансов — [`/api/agg/…`](docs/AGGREGATE.md), метрики Prometheus на `/metrics`.
|
||||
HTTP‑шлюз на Go для [Telemt Control API](docs/API.md): один порт, **белый список IP (CIDR)**, маршруты вида `/api/{alias}/…` → `{base_url}/v1/…`, агрегация нескольких инстансов — [`/api/agg/…`](docs/AGGREGATE.md), метрики Prometheus на `/metrics`. **Web UI** (SvelteKit) встроен в тот же процесс/образ: статика на `/`, API на `/api/…` и `/health`.
|
||||
|
||||
## Быстрый старт (Linux)
|
||||
|
||||
Предполагается установлены Docker и Docker Compose v2.
|
||||
|
||||
**Рекомендуется** брать уже собранный образ из Container Registry Gitea (после каждого push в репозиторий CI обновляет теги, в том числе `latest`):
|
||||
**Рекомендуется** брать уже собранный образ из Container Registry Gitea (после каждого push CI обновляет теги, в том числе `latest`). В актуальном образе вместе с шлюзом уже **встроена панель** на `http://<хост>:8080/`:
|
||||
|
||||
```bash
|
||||
# при необходимости (закрытый registry): логин Gitea + PAT с read:package
|
||||
@@ -30,25 +30,25 @@ docker run -d --name telemt-gateway \
|
||||
|
||||
curl -sS http://127.0.0.1:8080/health
|
||||
curl -sS http://127.0.0.1:8080/api/main_srv/health
|
||||
# панель в браузере: http://127.0.0.1:8080/
|
||||
```
|
||||
|
||||
Обновление образа: `docker pull git.shts.su/denozord/telemt-api:latest` и пересоздайте контейнер (`docker rm -f telemt-gateway` и снова `docker run …`).
|
||||
|
||||
### Compose
|
||||
|
||||
Тот же образ шлюза подтягивается из registry (без локальной сборки). Сервис **web** (панель в браузере) собирается из каталога `web/` при первом запуске:
|
||||
Один сервис **gateway** — в образе уже есть и API, и статика панели (см. [Dockerfile](Dockerfile)).
|
||||
|
||||
```bash
|
||||
git clone <url-репозитория> && cd telemt-api
|
||||
docker compose pull
|
||||
docker compose up -d --build
|
||||
docker compose up -d
|
||||
docker compose logs -f gateway
|
||||
```
|
||||
|
||||
- Шлюз: `http://127.0.0.1:8080` (проверка: `curl -sS http://127.0.0.1:8080/health`).
|
||||
- Web UI: `http://127.0.0.1:4173` — в [config.compose.yaml](config.compose.yaml) заданы `cors_allowed_origins` под этот origin; `PUBLIC_TELEMT_GATEWAY_URL` при сборке образа UI указывает на шлюз на хосте (`http://127.0.0.1:8080`).
|
||||
- Всё на **одном порту**: `http://127.0.0.1:8080/` — Web UI, `http://127.0.0.1:8080/api/…` — шлюз, `http://127.0.0.1:8080/health` — проверка живости.
|
||||
|
||||
Подробнее по панели: [web/README.md](web/README.md).
|
||||
Подробнее по фронту и dev-режиму: [web/README.md](web/README.md). Полный сценарий Docker (CLI, compose, сборка, CI): [docs/GATEWAY_RUN.md](docs/GATEWAY_RUN.md).
|
||||
|
||||
### Web UI (локально без Docker)
|
||||
|
||||
@@ -71,7 +71,7 @@ cors_allowed_origins:
|
||||
|
||||
### Локальная сборка образа
|
||||
|
||||
Если нужен образ из исходников на этой машине: `docker build -t telemt-api-gateway:local .` и в `docker run` укажите тег `telemt-api-gateway:local`. Подробнее — [docs/GATEWAY_RUN.md](docs/GATEWAY_RUN.md).
|
||||
`docker build -t telemt-api-gateway:local .` — в образ попадают Web UI и бинарь шлюза (см. [Dockerfile](Dockerfile)). В `docker run` укажите этот тег. Подробнее — [docs/GATEWAY_RUN.md](docs/GATEWAY_RUN.md).
|
||||
|
||||
## Документация
|
||||
|
||||
@@ -81,7 +81,7 @@ cors_allowed_origins:
|
||||
| **[docs/API.md](docs/API.md)** | Контракт Telemt Control API (`/v1/…`) |
|
||||
| **[docs/AGGREGATE.md](docs/AGGREGATE.md)** | Агрегирующие эндпоинты шлюза (`/api/agg/…`), CORS, кэш |
|
||||
| **[docs/AGGREGATE_OPENAPI.yaml](docs/AGGREGATE_OPENAPI.yaml)** | OpenAPI 3 черновик для `/api/agg/*` (генерация типов для UI) |
|
||||
| **[web/README.md](web/README.md)** | Web UI (SvelteKit + shadcn-svelte): установка, `PUBLIC_TELEMT_GATEWAY_URL`, Docker |
|
||||
| **[web/README.md](web/README.md)** | Web UI (SvelteKit): разработка с Vite, `PUBLIC_TELEMT_GATEWAY_URL`, встраивание в образ шлюза |
|
||||
| **[docs/GEOIP.md](docs/GEOIP.md)** | GeoLite2 City (страна/город) и опционально ASN (номер AS, организация) для IP в `unique-ips` |
|
||||
|
||||
## Сборка и тесты без Docker
|
||||
|
||||
+1
-4
@@ -4,10 +4,7 @@ listen: ":8080"
|
||||
allow_all: true
|
||||
whitelist_cidrs: []
|
||||
trusted_proxies: []
|
||||
# UI в Docker (сервис web): браузер на другом origin — нужен CORS
|
||||
cors_allowed_origins:
|
||||
- "http://127.0.0.1:4173"
|
||||
- "http://localhost:4173"
|
||||
# UI отдаётся с того же порта, что и API (образ шлюза) — отдельный CORS для панели не нужен.
|
||||
servers:
|
||||
- alias: main_srv
|
||||
base_url: http://host.docker.internal:9091
|
||||
|
||||
+5
-1
@@ -2,6 +2,9 @@
|
||||
#
|
||||
# Semantics: client calls GET /api/{alias}/health
|
||||
# forwarded to GET {base_url}/v1/health
|
||||
#
|
||||
# Web UI (SvelteKit) в образе Docker встроен в тот же процесс: GET / — панель,
|
||||
# /api/… — API. CORS для панели на том же origin не нужен.
|
||||
|
||||
listen: ":8080"
|
||||
|
||||
@@ -23,7 +26,8 @@ whitelist_cidrs:
|
||||
# - "10.0.0.0/8"
|
||||
trusted_proxies: []
|
||||
|
||||
# SPA на другом origin (preflight OPTIONS + Access-Control-Allow-Origin):
|
||||
# CORS только если фронт на другом origin (например Vite :5173 при разработке).
|
||||
# В production за nginx на одном хосте с шлюзом обычно не требуется.
|
||||
# cors_allowed_origins:
|
||||
# - "http://localhost:5173"
|
||||
# # - "*"
|
||||
|
||||
+1
-10
@@ -1,16 +1,7 @@
|
||||
services:
|
||||
web:
|
||||
build:
|
||||
context: ./web
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
# URL шлюза в браузере (встраивается при сборке)
|
||||
PUBLIC_TELEMT_GATEWAY_URL: http://127.0.0.1:8080
|
||||
ports:
|
||||
- "4173:4173"
|
||||
gateway:
|
||||
image: git.shts.su/denozord/telemt-api:latest
|
||||
# Локальная сборка вместо pull: укажите build: . и image: telemt-api-gateway:local
|
||||
# Локальная сборка с Web UI внутри образа: build: . и image: telemt-api-gateway:local
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
|
||||
+12
-6
@@ -18,6 +18,7 @@
|
||||
|
||||
Шлюз — это один HTTP‑вход для нескольких экземпляров [Telemt Control API](API.md):
|
||||
|
||||
- **Web UI** (в образе Docker): статика панели на **`GET /`** (и клиентские маршруты SPA), агрегаты и прокси на **`/api/…`**. Тот же порт, что и у API (например `8080`). Исходники UI — каталог [web/](../web/README.md), сборка встроена в [Dockerfile](../Dockerfile) (стадия Node + `embed` в Go).
|
||||
- **Белый список IP** (CIDR): кто может обращаться к шлюзу (кроме `GET /health`, см. ниже).
|
||||
- **Маршрутизация по alias**: клиент вызывает `GET /api/{alias}/health`, шлюз проксирует на `{base_url}/v1/health` у соответствующего сервера.
|
||||
- **Метрики Prometheus**: `GET /metrics` (под тем же правилом whitelist, что и API).
|
||||
@@ -28,7 +29,8 @@
|
||||
## Требования
|
||||
|
||||
- Установленные [Docker](https://docs.docker.com/get-docker/) и при необходимости [Docker Compose](https://docs.docker.com/compose/) v2.
|
||||
- Для локальной сборки из исходников: [Go 1.22+](https://go.dev/dl/) (опционально, если не используете только готовый образ из registry).
|
||||
- Для локальной сборки **Docker-образа** из репозитория: Docker сам подтянет [Node](https://nodejs.org/) на стадии сборки фронта и [Go 1.22+](https://go.dev/dl/) на стадии компиляции (см. [Dockerfile](../Dockerfile)).
|
||||
- Для `go test ./...` без Docker на машине нужен только Go 1.22+.
|
||||
|
||||
## Минимальная конфигурация
|
||||
|
||||
@@ -106,7 +108,7 @@ docker login git.shts.su
|
||||
docker build -t telemt-api-gateway:local .
|
||||
```
|
||||
|
||||
В командах `docker run` ниже вместо имени из registry подставьте `telemt-api-gateway:local`.
|
||||
Сборка **многостадийная**: сначала `npm ci` + `npm run build` в каталоге `web/` (в бандл вшивается пустой `PUBLIC_TELEMT_GATEWAY_URL`, запросы API с того же origin), затем компиляция Go со встраиванием `web/build` через `embed`. В командах `docker run` ниже вместо имени из registry подставьте `telemt-api-gateway:local`.
|
||||
|
||||
## Запуск через Docker CLI
|
||||
|
||||
@@ -125,9 +127,12 @@ docker run -d --name telemt-gateway \
|
||||
```bash
|
||||
curl -sS -i http://127.0.0.1:8080/health
|
||||
curl -sS -i http://127.0.0.1:8080/api/main_srv/health
|
||||
curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/
|
||||
```
|
||||
|
||||
Второй запрос проксируется на `{base_url}/v1/health` для alias `main_srv`.
|
||||
Второй запрос проксируется на `{base_url}/v1/health` для alias `main_srv`. Третий — HTML панели (в образах, собранных с Web UI; ожидайте `200`).
|
||||
|
||||
Панель в браузере: `http://127.0.0.1:8080/` (при `allow_all: false` ваш IP должен быть в `whitelist_cidrs`, иначе для `/` будет `403`, как и для API).
|
||||
|
||||
Остановка и удаление:
|
||||
|
||||
@@ -138,7 +143,7 @@ docker rm telemt-gateway
|
||||
|
||||
## Запуск через Docker Compose
|
||||
|
||||
В репозитории есть [docker-compose.yml](../docker-compose.yml) и пример [config.compose.yaml](../config.compose.yaml) с `allow_all: true` и `base_url: http://host.docker.internal:9091` (Telemt на хосте; на Docker Desktop для Linux это обычно работает из коробки). Образ по умолчанию **скачивается** из registry, локальная сборка не требуется.
|
||||
В репозитории есть [docker-compose.yml](../docker-compose.yml) (один сервис **gateway**) и пример [config.compose.yaml](../config.compose.yaml) с `allow_all: true` и `base_url: http://host.docker.internal:9091` (Telemt на хосте; на Docker Desktop для Linux это обычно работает из коробки). Образ по умолчанию **скачивается** из registry, локальная сборка не требуется. UI доступен на том же порту, что и шлюз: `http://127.0.0.1:8080/`.
|
||||
|
||||
```bash
|
||||
docker compose pull
|
||||
@@ -159,6 +164,7 @@ docker compose down
|
||||
| Сценарий | Ожидание |
|
||||
|----------|----------|
|
||||
| `GET /health` | `200`, JSON `{"status":"ok"}` |
|
||||
| `GET /` (образ с Web UI) | `200`, HTML панели |
|
||||
| Разрешённый IP, корректный alias | ответ бэкенда (например `200` для `/v1/health`) |
|
||||
| IP не в whitelist | `403`, JSON с `code: forbidden` |
|
||||
| Неизвестный alias | `404`, JSON с `code: not_found` |
|
||||
@@ -167,9 +173,9 @@ docker compose down
|
||||
|
||||
## Обновление и CI/CD
|
||||
|
||||
- **Образ**: подтяните свежий тег (`docker pull git.shts.su/denozord/telemt-api:latest` или `docker compose pull`), пересоздайте контейнер (`docker compose up -d` или новый `docker run` с тем же volume конфига). Локальная пересборка нужна только если вы меняете Dockerfile/код и не пользуетесь CI.
|
||||
- **Образ**: подтяните свежий тег (`docker pull git.shts.su/denozord/telemt-api:latest` или `docker compose pull`), пересоздайте контейнер (`docker compose up -d` или новый `docker run` с тем же volume конфига). Локальная пересборка нужна только если вы меняете Dockerfile/код и не пользуетесь CI. Образы, собранные **до** добавления стадии `web/` в Dockerfile, могут отдавать на `/` только заглушку — нужен образ из актуального CI или локальный `docker build`.
|
||||
- **Конфиг**: отредактируйте файл на хосте и перезапустите контейнер (шлюз не перечитывает конфиг на лету).
|
||||
- **Gitea Actions**: workflow [.gitea/workflows/docker.yaml](../.gitea/workflows/docker.yaml) сначала выполняет `go mod tidy && go test ./...`, затем собирает образ через Buildx и пушит в Container Registry Gitea.
|
||||
- **Gitea Actions**: workflow [.gitea/workflows/docker.yaml](../.gitea/workflows/docker.yaml) сначала выполняет `go mod tidy && go test ./...`, затем собирает образ через Buildx по [Dockerfile](../Dockerfile) (стадии Node для `web/` и Go) и пушит в Container Registry Gitea.
|
||||
|
||||
- В репозитории должен быть secret **`ACTIONS_PAT`** — personal access token пользователя с правом **`write:package`** (и при необходимости `read:package`), как для обычного `docker login` к registry.
|
||||
- Логин в registry: пользователь **`gitea.actor`** (кто запустил workflow), пароль — этот PAT.
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/telemt/telemt-api/internal/config"
|
||||
"github.com/telemt/telemt-api/internal/geoip"
|
||||
"github.com/telemt/telemt-api/internal/proxy"
|
||||
"github.com/telemt/telemt-api/internal/webui"
|
||||
)
|
||||
|
||||
// Gateway serves health, metrics, and proxied API routes.
|
||||
@@ -29,6 +30,7 @@ type Gateway struct {
|
||||
transport *http.Transport
|
||||
promHandler http.Handler
|
||||
corsAllowed []string
|
||||
webUI http.Handler
|
||||
}
|
||||
|
||||
// NewGateway builds handlers and reverse proxies from parsed config.
|
||||
@@ -75,6 +77,7 @@ func NewGateway(p *config.Parsed, log *slog.Logger, geo *geoip.Service) (*Gatewa
|
||||
}
|
||||
g.corsAllowed = append([]string(nil), p.Config.CorsAllowedOrigins...)
|
||||
g.agg = aggregate.NewHandler(p, &http.Client{Transport: t}, geo, aggCacheTTL)
|
||||
g.webUI = webui.Handler()
|
||||
return g, nil
|
||||
}
|
||||
|
||||
@@ -231,7 +234,7 @@ func (g *Gateway) serve(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(r.URL.Path, prefix) {
|
||||
http.NotFound(w, r)
|
||||
g.webUI.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
trim := strings.TrimPrefix(r.URL.Path, prefix)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package webui
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed all:static
|
||||
var static embed.FS
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 && npm run build</code>) перед сборкой образа шлюза, либо
|
||||
используйте Dockerfile в корне репозитория.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
+3
-2
@@ -1,2 +1,3 @@
|
||||
# Базовый URL шлюза telemt-api (без завершающего слэша)
|
||||
PUBLIC_TELEMT_GATEWAY_URL=http://127.0.0.1:8080
|
||||
# Пусто = запросы к API на том же origin, что и панель (образ Docker со встроенным UI).
|
||||
# Для dev (Vite :5173, шлюз :8080): http://127.0.0.1:8080
|
||||
PUBLIC_TELEMT_GATEWAY_URL=
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# Статическая сборка SvelteKit и раздача через serve
|
||||
FROM node:22-alpine AS build
|
||||
WORKDIR /app
|
||||
ARG PUBLIC_TELEMT_GATEWAY_URL=http://127.0.0.1:8080
|
||||
ENV PUBLIC_TELEMT_GATEWAY_URL=$PUBLIC_TELEMT_GATEWAY_URL
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:22-alpine
|
||||
RUN npm install -g serve@14
|
||||
WORKDIR /srv
|
||||
COPY --from=build /app/build ./build
|
||||
EXPOSE 4173
|
||||
CMD ["serve", "-s", "build", "-l", "4173"]
|
||||
+13
-21
@@ -1,16 +1,17 @@
|
||||
# Telemt Panel (Web UI)
|
||||
|
||||
SvelteKit + shadcn-svelte: обзор флота (`/api/agg/*`), пользователи и IP, панель по каждой ноде через прокси шлюза (`/api/{alias}/…`).
|
||||
SvelteKit + shadcn-svelte. В **production** статика собирается и **встраивается в образ шлюза** ([Dockerfile](../Dockerfile) в корне репозитория): панель и API на **одном порту** (например `http://127.0.0.1:8080/` — UI, `/api/…` — шлюз).
|
||||
|
||||
## Переменные окружения
|
||||
## Переменная `PUBLIC_TELEMT_GATEWAY_URL`
|
||||
|
||||
| Переменная | Описание |
|
||||
| Значение | Когда |
|
||||
| --- | --- |
|
||||
| `PUBLIC_TELEMT_GATEWAY_URL` | Базовый URL шлюза **telemt-api** без завершающего `/` (например `http://127.0.0.1:8080`). Встраивается в клиент при сборке. |
|
||||
| *(пусто)* | Тот же хост и порт, что у страницы (Docker-образ шлюза, `npm run build` в Dockerfile с `ENV PUBLIC_TELEMT_GATEWAY_URL=`) |
|
||||
| `http://127.0.0.1:8080` | Локальная разработка: Vite на `:5173`, шлюз на `:8080` |
|
||||
|
||||
Скопируйте [.env.example](.env.example) в `.env` и при необходимости измените URL.
|
||||
Скопируйте [.env.example](.env.example) в `.env` и при необходимости задайте URL.
|
||||
|
||||
## Разработка
|
||||
## Разработка (Vite отдельно от Go)
|
||||
|
||||
```powershell
|
||||
cd web
|
||||
@@ -18,39 +19,30 @@ npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Откройте в браузере адрес Vite (по умолчанию `http://localhost:5173`). В конфиге шлюза укажите CORS, например:
|
||||
Откройте `http://localhost:5173`. В конфиге шлюза включите [CORS](../docs/AGGREGATE.md) для этого origin:
|
||||
|
||||
```yaml
|
||||
cors_allowed_origins:
|
||||
- "http://localhost:5173"
|
||||
```
|
||||
|
||||
## Сборка
|
||||
## Сборка только фронта
|
||||
|
||||
```powershell
|
||||
npm run build
|
||||
npm run preview # проверка статики
|
||||
```
|
||||
|
||||
## Типы из OpenAPI агрегатов
|
||||
Артефакт — каталог `build/`, при сборке **Docker-образа шлюза** он копируется в `internal/webui/static/` и попадает в бинарник через `embed`.
|
||||
|
||||
После изменения [../docs/AGGREGATE_OPENAPI.yaml](../docs/AGGREGATE_OPENAPI.yaml):
|
||||
## Типы из OpenAPI агрегатов
|
||||
|
||||
```powershell
|
||||
npm run gen:api
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
Сборка образа из каталога `web/`:
|
||||
|
||||
```powershell
|
||||
docker build -t telemt-web:local --build-arg PUBLIC_TELEMT_GATEWAY_URL=http://127.0.0.1:8080 .
|
||||
```
|
||||
|
||||
В [docker-compose.yml](../docker-compose.yml) сервис `web` отдаёт UI на порту **4173**. URL шлюза в образе задаётся **build-arg** (см. compose). Браузер на хосте обращается к шлюзу по `http://127.0.0.1:8080` — при другом адресе пересоберите образ с нужным `PUBLIC_TELEMT_GATEWAY_URL`.
|
||||
Источник: [../docs/AGGREGATE_OPENAPI.yaml](../docs/AGGREGATE_OPENAPI.yaml).
|
||||
|
||||
## Ограничения
|
||||
|
||||
- Секреты upstream к Telemt задаются на шлюзе (`authorization_env`), не в браузере.
|
||||
- Mutating CORS: шлюз должен разрешать `POST`, `PATCH`, `DELETE` в preflight (в текущей версии репозитория заголовок `Access-Control-Allow-Methods` это учитывает).
|
||||
- Для мутаций из другого origin шлюз отдаёт нужные заголовки CORS (в т.ч. методы `POST`, `PATCH`, `DELETE`).
|
||||
|
||||
Reference in New Issue
Block a user