mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-01 23:15:27 +08:00
refactor: update web UI asset serving and embedding mechanism.
This commit is contained in:
@@ -54,6 +54,10 @@ DS2API_ADMIN_KEY=admin
|
||||
# Built admin static assets directory
|
||||
# DS2API_STATIC_ADMIN_DIR=static/admin
|
||||
|
||||
# Auto-build WebUI on startup when static/admin is missing.
|
||||
# Default: enabled on local/Docker, disabled on Vercel.
|
||||
# DS2API_AUTO_BUILD_WEBUI=true
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Vercel sync integration (optional)
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -64,7 +64,8 @@ pnpm-lock.yaml
|
||||
*.tsbuildinfo
|
||||
.cache/
|
||||
.parcel-cache/
|
||||
# static/admin build output is versioned for serverless deployments
|
||||
static/admin/
|
||||
internal/webui/assets/admin/
|
||||
|
||||
# Environment
|
||||
.env.local
|
||||
|
||||
@@ -35,6 +35,9 @@ Build WebUI if `/admin` reports missing assets:
|
||||
|
||||
```bash
|
||||
./scripts/build-webui.sh
|
||||
|
||||
# Or rely on startup auto-build (enabled locally by default)
|
||||
# DS2API_AUTO_BUILD_WEBUI=true go run ./cmd/ds2api
|
||||
```
|
||||
|
||||
## 2. Docker Deployment
|
||||
@@ -62,7 +65,7 @@ Notes:
|
||||
|
||||
- Serverless entry: `api/index.go`
|
||||
- Rewrites and cache headers: `vercel.json`
|
||||
- Legacy `builds` has been removed to avoid the `unused-build-settings` warning
|
||||
- Build stage runs `npm ci --prefix webui && npm run build --prefix webui` automatically
|
||||
|
||||
Minimum environment variables:
|
||||
|
||||
@@ -79,6 +82,10 @@ Optional:
|
||||
|
||||
Recommended concurrency is computed dynamically as `account_count * per_account_inflight_limit` (default is `account_count * 2`).
|
||||
|
||||
Notes:
|
||||
- `static/admin` build output is not committed
|
||||
- Vercel/Docker generate WebUI assets during build
|
||||
|
||||
After deploy, verify:
|
||||
|
||||
- `/healthz`
|
||||
|
||||
@@ -35,6 +35,9 @@ go run ./cmd/ds2api
|
||||
|
||||
```bash
|
||||
./scripts/build-webui.sh
|
||||
|
||||
# 或依赖自动构建(默认本地开启)
|
||||
# DS2API_AUTO_BUILD_WEBUI=true go run ./cmd/ds2api
|
||||
```
|
||||
|
||||
## 2. Docker 部署
|
||||
@@ -62,7 +65,7 @@ docker-compose up -d --build
|
||||
|
||||
- serverless 入口:`api/index.go`
|
||||
- 路由与缓存头:`vercel.json`
|
||||
- 已移除 legacy `builds` 字段,避免 `unused-build-settings` 警告
|
||||
- 构建阶段会自动执行 `npm ci --prefix webui && npm run build --prefix webui`
|
||||
|
||||
至少配置环境变量:
|
||||
|
||||
@@ -79,6 +82,10 @@ docker-compose up -d --build
|
||||
|
||||
并发建议值会动态按 `账号数量 × 每账号并发上限` 计算(默认即 `账号数量 × 2`)。
|
||||
|
||||
说明:
|
||||
- 仓库不提交 `static/admin` 构建产物
|
||||
- Vercel / Docker 构建阶段自动生成 WebUI 静态文件
|
||||
|
||||
部署后建议先访问:
|
||||
|
||||
- `/healthz`
|
||||
|
||||
10
README.MD
10
README.MD
@@ -8,7 +8,7 @@
|
||||
|
||||
语言 / Language: [中文](README.MD) | [English](README.en.md)
|
||||
|
||||
将 DeepSeek Web 对话能力转换为 OpenAI 与 Claude 兼容 API。当前仓库后端为 **Go 全量实现**,前端保留 React WebUI(构建产物托管于 `static/admin`)。
|
||||
将 DeepSeek Web 对话能力转换为 OpenAI 与 Claude 兼容 API。当前仓库后端为 **Go 全量实现**,前端保留 React WebUI(源码在 `webui/`,部署时自动构建到 `static/admin`)。
|
||||
|
||||
## 当前实现边界
|
||||
|
||||
@@ -65,7 +65,8 @@ go run ./cmd/ds2api
|
||||
|
||||
默认地址:`http://localhost:5001`
|
||||
|
||||
如果访问 `/admin` 提示未构建 WebUI,请执行:
|
||||
本地默认会在启动时自动尝试构建 WebUI(需要本机有 Node.js/npm)。
|
||||
若你想手动构建,也可执行:
|
||||
|
||||
```bash
|
||||
./scripts/build-webui.sh
|
||||
@@ -85,12 +86,12 @@ docker-compose logs -f
|
||||
|
||||
- 入口:`api/index.go`
|
||||
- 路由重写:`vercel.json`
|
||||
- `vercel.json` 会在构建阶段自动执行 `npm ci --prefix webui && npm run build --prefix webui`
|
||||
- 至少配置:
|
||||
- `DS2API_ADMIN_KEY`
|
||||
- `DS2API_CONFIG_JSON`(JSON 字符串或 Base64)
|
||||
|
||||
说明:`vercel.json` 已移除 legacy `builds` 配置,避免部署时出现
|
||||
`unused-build-settings` 警告,并使用当前推荐的函数路由模式。
|
||||
说明:仓库不提交 `static/admin` 构建产物,Vercel 构建时自动生成并打包。
|
||||
|
||||
## Release 自动构建产物(GitHub Actions)
|
||||
|
||||
@@ -162,6 +163,7 @@ cp config.example.json config.json
|
||||
| `DS2API_CONFIG_JSON` | 直接注入配置(JSON 或 Base64) |
|
||||
| `DS2API_WASM_PATH` | PoW wasm 文件路径 |
|
||||
| `DS2API_STATIC_ADMIN_DIR` | 管理台静态文件目录 |
|
||||
| `DS2API_AUTO_BUILD_WEBUI` | 启动时缺失 WebUI 时是否自动执行 npm build(默认:本地开启,Vercel 关闭) |
|
||||
| `VERCEL_TOKEN` | Vercel 同步 token(可选) |
|
||||
| `VERCEL_PROJECT_ID` | Vercel 项目 ID(可选) |
|
||||
| `VERCEL_TEAM_ID` | Vercel 团队 ID(可选) |
|
||||
|
||||
10
README.en.md
10
README.en.md
@@ -8,7 +8,7 @@
|
||||
|
||||
Language: [中文](README.MD) | [English](README.en.md)
|
||||
|
||||
DS2API converts DeepSeek Web chat capability into OpenAI-compatible and Claude-compatible APIs. The current repository is **Go backend only** with the existing React WebUI kept as static assets under `static/admin`.
|
||||
DS2API converts DeepSeek Web chat capability into OpenAI-compatible and Claude-compatible APIs. The current repository is **Go backend only** with the existing React WebUI source in `webui/` and build output generated to `static/admin` during deployment.
|
||||
|
||||
## Implementation Boundary
|
||||
|
||||
@@ -65,7 +65,8 @@ go run ./cmd/ds2api
|
||||
|
||||
Default URL: `http://localhost:5001`
|
||||
|
||||
If `/admin` says WebUI not built:
|
||||
By default, local startup will auto-build WebUI when `static/admin` is missing (Node.js/npm required).
|
||||
If you prefer manual build:
|
||||
|
||||
```bash
|
||||
./scripts/build-webui.sh
|
||||
@@ -85,12 +86,12 @@ docker-compose logs -f
|
||||
|
||||
- Entrypoint: `api/index.go`
|
||||
- Rewrites: `vercel.json`
|
||||
- `vercel.json` runs `npm ci --prefix webui && npm run build --prefix webui` during build
|
||||
- Minimum env vars:
|
||||
- `DS2API_ADMIN_KEY`
|
||||
- `DS2API_CONFIG_JSON` (raw JSON or Base64)
|
||||
|
||||
Note: legacy `builds` has been removed from `vercel.json` to avoid
|
||||
the `unused-build-settings` warning and to follow the current function routing model.
|
||||
Note: build artifacts under `static/admin` are not committed; Vercel generates them during build.
|
||||
|
||||
## Release Artifact Automation (GitHub Actions)
|
||||
|
||||
@@ -162,6 +163,7 @@ cp config.example.json config.json
|
||||
| `DS2API_CONFIG_JSON` | Inline config (JSON or Base64) |
|
||||
| `DS2API_WASM_PATH` | PoW wasm path |
|
||||
| `DS2API_STATIC_ADMIN_DIR` | Admin static assets dir |
|
||||
| `DS2API_AUTO_BUILD_WEBUI` | Auto run npm build on startup when WebUI assets are missing (default: enabled locally, disabled on Vercel) |
|
||||
| `VERCEL_TOKEN` | Vercel sync token (optional) |
|
||||
| `VERCEL_PROJECT_ID` | Vercel project ID (optional) |
|
||||
| `VERCEL_TEAM_ID` | Vercel team ID (optional) |
|
||||
|
||||
@@ -7,9 +7,11 @@ import (
|
||||
|
||||
"ds2api/internal/config"
|
||||
"ds2api/internal/server"
|
||||
"ds2api/internal/webui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
webui.EnsureBuiltOnStartup()
|
||||
app := server.NewApp()
|
||||
port := strings.TrimSpace(os.Getenv("PORT"))
|
||||
if port == "" {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,43 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- SEO Meta Tags -->
|
||||
<title>DS2API - 管理面板 / Admin Console</title>
|
||||
<meta name="description" content="DS2API 管理面板:管理 DeepSeek 账号、测试 API、同步 Vercel 配置 / Admin console for accounts, API tests, and Vercel sync." />
|
||||
<meta name="keywords" content="DeepSeek, API, OpenAI, 管理面板, admin console, DS2API" />
|
||||
<meta name="author" content="CJackHwang" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
|
||||
<!-- Open Graph / Social -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="DS2API - 管理面板 / Admin Console" />
|
||||
<meta property="og:description" content="Manage DeepSeek accounts, test the API, and sync Vercel configuration." />
|
||||
<meta property="og:site_name" content="DS2API" />
|
||||
|
||||
<!-- PWA / Mobile -->
|
||||
<meta name="theme-color" content="#f59e0b" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="DS2API" />
|
||||
|
||||
<!-- Favicon - using data URI for orange-yellow gradient icon -->
|
||||
<link rel="icon" type="image/svg+xml"
|
||||
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23f59e0b'/%3E%3Cstop offset='100%25' stop-color='%23ef4444'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect rx='20' width='100' height='100' fill='url(%23g)'/%3E%3Ctext x='50' y='68' font-family='Arial,sans-serif' font-size='48' font-weight='bold' fill='white' text-anchor='middle'%3EDS%3C/text%3E%3C/svg%3E" />
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/admin/assets/index-BZnWnZUg.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/admin/assets/index-Dd4LAu0y.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
103
internal/webui/build.go
Normal file
103
internal/webui/build.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package webui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ds2api/internal/config"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBuildTimeout = 5 * time.Minute
|
||||
)
|
||||
|
||||
func EnsureBuiltOnStartup() {
|
||||
if !shouldAutoBuild() {
|
||||
return
|
||||
}
|
||||
staticDir := resolveStaticAdminDir(config.StaticAdminDir())
|
||||
if hasBuiltUI(staticDir) {
|
||||
return
|
||||
}
|
||||
if err := buildWebUI(staticDir); err != nil {
|
||||
config.Logger.Warn("[webui] auto build failed", "error", err)
|
||||
return
|
||||
}
|
||||
if hasBuiltUI(staticDir) {
|
||||
config.Logger.Info("[webui] auto build completed", "dir", staticDir)
|
||||
return
|
||||
}
|
||||
config.Logger.Warn("[webui] auto build finished but output missing", "dir", staticDir)
|
||||
}
|
||||
|
||||
func shouldAutoBuild() bool {
|
||||
raw := strings.TrimSpace(os.Getenv("DS2API_AUTO_BUILD_WEBUI"))
|
||||
if raw == "" {
|
||||
return !config.IsVercel()
|
||||
}
|
||||
switch strings.ToLower(raw) {
|
||||
case "1", "true", "yes", "on":
|
||||
return true
|
||||
case "0", "false", "no", "off":
|
||||
return false
|
||||
default:
|
||||
return !config.IsVercel()
|
||||
}
|
||||
}
|
||||
|
||||
func hasBuiltUI(staticDir string) bool {
|
||||
if strings.TrimSpace(staticDir) == "" {
|
||||
return false
|
||||
}
|
||||
indexPath := filepath.Join(staticDir, "index.html")
|
||||
st, err := os.Stat(indexPath)
|
||||
return err == nil && !st.IsDir()
|
||||
}
|
||||
|
||||
func buildWebUI(staticDir string) error {
|
||||
if _, err := exec.LookPath("npm"); err != nil {
|
||||
return fmt.Errorf("npm not found in PATH: %w", err)
|
||||
}
|
||||
if strings.TrimSpace(staticDir) == "" {
|
||||
return errors.New("static admin dir is empty")
|
||||
}
|
||||
|
||||
config.Logger.Info("[webui] static files missing, running npm build")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultBuildTimeout)
|
||||
defer cancel()
|
||||
|
||||
if _, err := os.Stat(filepath.Join("webui", "node_modules")); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
installCmd := exec.CommandContext(ctx, "npm", "ci", "--prefix", "webui")
|
||||
installCmd.Stdout = os.Stdout
|
||||
installCmd.Stderr = os.Stderr
|
||||
if err := installCmd.Run(); err != nil {
|
||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
return fmt.Errorf("webui npm ci timed out after %s", defaultBuildTimeout)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(staticDir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, "npm", "run", "build", "--prefix", "webui", "--", "--outDir", staticDir, "--emptyOutDir")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
return fmt.Errorf("webui build timed out after %s", defaultBuildTimeout)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package webui
|
||||
|
||||
import "embed"
|
||||
|
||||
// embeddedAdminFS bundles the built admin webui so serverless runtime does not
|
||||
// depend on function includeFiles behavior.
|
||||
//
|
||||
//go:embed assets/admin
|
||||
var embeddedAdminFS embed.FS
|
||||
@@ -1,10 +1,8 @@
|
||||
package webui
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -19,18 +17,11 @@ const welcomeHTML = `<!DOCTYPE html>
|
||||
</head><body><main><h1>DS2API</h1><p>DeepSeek to OpenAI & Claude Compatible API</p><div class="links"><a href="/admin">管理面板</a><a href="/v1/models">API 状态</a><a href="https://github.com/CJackHwang/ds2api" target="_blank">GitHub</a></div></main></body></html>`
|
||||
|
||||
type Handler struct {
|
||||
StaticDir string
|
||||
embeddedAdmin fs.FS
|
||||
hasEmbeddedUI bool
|
||||
StaticDir string
|
||||
}
|
||||
|
||||
func NewHandler() *Handler {
|
||||
h := &Handler{StaticDir: resolveStaticAdminDir(config.StaticAdminDir())}
|
||||
if sub, err := fs.Sub(embeddedAdminFS, "assets/admin"); err == nil {
|
||||
h.embeddedAdmin = sub
|
||||
h.hasEmbeddedUI = true
|
||||
}
|
||||
return h
|
||||
return &Handler{StaticDir: resolveStaticAdminDir(config.StaticAdminDir())}
|
||||
}
|
||||
|
||||
func RegisterRoutes(r chi.Router, h *Handler) {
|
||||
@@ -61,11 +52,6 @@ func (h *Handler) admin(w http.ResponseWriter, r *http.Request) {
|
||||
h.serveFromDisk(w, r, staticDir)
|
||||
return
|
||||
}
|
||||
if h.hasEmbeddedUI {
|
||||
if h.serveFromFS(w, r, h.embeddedAdmin) {
|
||||
return
|
||||
}
|
||||
}
|
||||
http.Error(w, "WebUI not built. Run `cd webui && npm run build` first.", http.StatusNotFound)
|
||||
}
|
||||
|
||||
@@ -100,6 +86,9 @@ func (h *Handler) serveFromDisk(w http.ResponseWriter, r *http.Request, staticDi
|
||||
}
|
||||
|
||||
func resolveStaticAdminDir(preferred string) string {
|
||||
if strings.TrimSpace(os.Getenv("DS2API_STATIC_ADMIN_DIR")) != "" {
|
||||
return filepath.Clean(preferred)
|
||||
}
|
||||
candidates := []string{preferred}
|
||||
if wd, err := os.Getwd(); err == nil {
|
||||
candidates = append(candidates, filepath.Join(wd, "static/admin"))
|
||||
@@ -130,34 +119,3 @@ func resolveStaticAdminDir(preferred string) string {
|
||||
}
|
||||
return filepath.Clean(preferred)
|
||||
}
|
||||
|
||||
func (h *Handler) serveFromFS(w http.ResponseWriter, r *http.Request, rootFS fs.FS) bool {
|
||||
rel := strings.TrimPrefix(r.URL.Path, "/admin")
|
||||
rel = strings.TrimPrefix(rel, "/")
|
||||
safe := strings.TrimPrefix(path.Clean("/"+rel), "/")
|
||||
if strings.HasPrefix(safe, "../") {
|
||||
http.NotFound(w, r)
|
||||
return true
|
||||
}
|
||||
|
||||
if safe != "" && strings.Contains(safe, ".") {
|
||||
if _, err := fs.Stat(rootFS, safe); err != nil {
|
||||
http.NotFound(w, r)
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(safe, "assets/") {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
} else {
|
||||
w.Header().Set("Cache-Control", "no-store, must-revalidate")
|
||||
}
|
||||
http.ServeFileFS(w, r, rootFS, safe)
|
||||
return true
|
||||
}
|
||||
|
||||
if _, err := fs.Stat(rootFS, "index.html"); err != nil {
|
||||
return false
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store, must-revalidate")
|
||||
http.ServeFileFS(w, r, rootFS, "index.html")
|
||||
return true
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,43 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- SEO Meta Tags -->
|
||||
<title>DS2API - 管理面板 / Admin Console</title>
|
||||
<meta name="description" content="DS2API 管理面板:管理 DeepSeek 账号、测试 API、同步 Vercel 配置 / Admin console for accounts, API tests, and Vercel sync." />
|
||||
<meta name="keywords" content="DeepSeek, API, OpenAI, 管理面板, admin console, DS2API" />
|
||||
<meta name="author" content="CJackHwang" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
|
||||
<!-- Open Graph / Social -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="DS2API - 管理面板 / Admin Console" />
|
||||
<meta property="og:description" content="Manage DeepSeek accounts, test the API, and sync Vercel configuration." />
|
||||
<meta property="og:site_name" content="DS2API" />
|
||||
|
||||
<!-- PWA / Mobile -->
|
||||
<meta name="theme-color" content="#f59e0b" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="DS2API" />
|
||||
|
||||
<!-- Favicon - using data URI for orange-yellow gradient icon -->
|
||||
<link rel="icon" type="image/svg+xml"
|
||||
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23f59e0b'/%3E%3Cstop offset='100%25' stop-color='%23ef4444'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect rx='20' width='100' height='100' fill='url(%23g)'/%3E%3Ctext x='50' y='68' font-family='Arial,sans-serif' font-size='48' font-weight='bold' fill='white' text-anchor='middle'%3EDS%3C/text%3E%3C/svg%3E" />
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/admin/assets/index-BZnWnZUg.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/admin/assets/index-Dd4LAu0y.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,5 +1,11 @@
|
||||
{
|
||||
"version": 2,
|
||||
"buildCommand": "npm ci --prefix webui && npm run build --prefix webui",
|
||||
"functions": {
|
||||
"api/index.go": {
|
||||
"includeFiles": "static/admin/**"
|
||||
}
|
||||
},
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
|
||||
Reference in New Issue
Block a user