feat(ui): show env mode persistence status and document writeback

This commit is contained in:
CJACK.
2026-03-30 21:18:56 +08:00
parent 958e7a0d04
commit 836eaf5290
12 changed files with 72 additions and 5 deletions

View File

@@ -587,6 +587,9 @@ Returns sanitized config.
{
"keys": ["k1", "k2"],
"env_backed": false,
"env_source_present": true,
"env_writeback_enabled": true,
"config_path": "/data/config.json",
"accounts": [
{
"identifier": "user@example.com",

3
API.md
View File

@@ -596,6 +596,9 @@ data: {"type":"message_stop"}
{
"keys": ["k1", "k2"],
"env_backed": false,
"env_source_present": true,
"env_writeback_enabled": true,
"config_path": "/data/config.json",
"accounts": [
{
"identifier": "user@example.com",

View File

@@ -320,6 +320,7 @@ cp opencode.json.example opencode.json
| `DS2API_CONFIG_PATH` | 配置文件路径 | `config.json` |
| `DS2API_CONFIG_JSON` | 直接注入配置JSON 或 Base64 | — |
| `CONFIG_JSON` | 旧版兼容配置注入 | — |
| `DS2API_ENV_WRITEBACK` | 环境变量模式下自动写回配置文件并切换文件模式(`1/true/yes/on` | 关闭 |
| `DS2API_WASM_PATH` | PoW WASM 文件路径 | 自动查找 |
| `DS2API_STATIC_ADMIN_DIR` | 管理台静态文件目录 | `static/admin` |
| `DS2API_AUTO_BUILD_WEBUI` | 启动时自动构建 WebUI | 本地开启Vercel 关闭 |
@@ -342,6 +343,8 @@ cp opencode.json.example opencode.json
| `VERCEL_TEAM_ID` | Vercel 团队 ID | — |
| `DS2API_VERCEL_PROTECTION_BYPASS` | Vercel 部署保护绕过密钥(内部 Node→Go 调用) | — |
> 提示:当检测到 `DS2API_CONFIG_JSON/CONFIG_JSON` 时,管理台会显示当前模式风险与自动持久化状态(含 `DS2API_CONFIG_PATH` 路径与模式切换说明)。
## 鉴权模式
调用业务接口(`/v1/*`、`/anthropic/*`、Gemini 路由)时支持两种模式:

View File

@@ -320,6 +320,7 @@ cp opencode.json.example opencode.json
| `DS2API_CONFIG_PATH` | Config file path | `config.json` |
| `DS2API_CONFIG_JSON` | Inline config (JSON or Base64) | — |
| `CONFIG_JSON` | Legacy compatibility config input | — |
| `DS2API_ENV_WRITEBACK` | Auto-write env-backed config to file and transition to file mode (`1/true/yes/on`) | Disabled |
| `DS2API_WASM_PATH` | PoW WASM file path | Auto-detect |
| `DS2API_STATIC_ADMIN_DIR` | Admin static assets dir | `static/admin` |
| `DS2API_AUTO_BUILD_WEBUI` | Auto-build WebUI on startup | Enabled locally, disabled on Vercel |
@@ -339,6 +340,8 @@ cp opencode.json.example opencode.json
| `VERCEL_TEAM_ID` | Vercel team ID | — |
| `DS2API_VERCEL_PROTECTION_BYPASS` | Vercel deployment protection bypass for internal Node→Go calls | — |
> Note: when `DS2API_CONFIG_JSON/CONFIG_JSON` is detected, the Admin UI shows mode risk and auto-persistence status (including `DS2API_CONFIG_PATH` and mode-transition hints).
## Authentication Modes
For business endpoints (`/v1/*`, `/anthropic/*`, Gemini routes), DS2API supports two modes:

View File

@@ -248,6 +248,7 @@ VERCEL_TEAM_ID=team_xxxxxxxxxxxx # optional for personal accounts
| `DS2API_ACCOUNT_QUEUE_SIZE` | Alias (legacy compat) | — |
| `DS2API_GLOBAL_MAX_INFLIGHT` | Global inflight limit | `recommended_concurrency` |
| `DS2API_MAX_INFLIGHT` | Alias (legacy compat) | — |
| `DS2API_ENV_WRITEBACK` | When `DS2API_CONFIG_JSON` is present, auto-write to `DS2API_CONFIG_PATH` and switch to file-backed mode after success (`1/true/yes/on`) | Disabled |
| `DS2API_VERCEL_INTERNAL_SECRET` | Hybrid streaming internal auth | Falls back to `DS2API_ADMIN_KEY` |
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | Stream lease TTL | `900` |
| `VERCEL_TOKEN` | Vercel sync token | — |

View File

@@ -248,6 +248,7 @@ VERCEL_TEAM_ID=team_xxxxxxxxxxxx # 个人账号可留空
| `DS2API_ACCOUNT_QUEUE_SIZE` | 同上(兼容别名) | — |
| `DS2API_GLOBAL_MAX_INFLIGHT` | 全局并发上限 | `recommended_concurrency` |
| `DS2API_MAX_INFLIGHT` | 同上(兼容别名) | — |
| `DS2API_ENV_WRITEBACK` | 检测到 `DS2API_CONFIG_JSON` 时自动写入 `DS2API_CONFIG_PATH`,并在成功后转为文件模式(`1/true/yes/on` | 关闭 |
| `DS2API_VERCEL_INTERNAL_SECRET` | 混合流式内部鉴权 | 回退用 `DS2API_ADMIN_KEY` |
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | 流式 lease TTL | `900` |
| `VERCEL_TOKEN` | Vercel 同步 token | — |

View File

@@ -21,6 +21,9 @@ type ConfigStore interface {
Update(mutator func(*config.Config) error) error
ExportJSONAndBase64() (string, string, error)
IsEnvBacked() bool
IsEnvWritebackEnabled() bool
HasEnvConfigSource() bool
ConfigPath() string
SetVercelSync(hash string, ts int64) error
AdminPasswordHash() string
AdminJWTExpireHours() int

View File

@@ -8,9 +8,12 @@ import (
func (h *Handler) getConfig(w http.ResponseWriter, _ *http.Request) {
snap := h.Store.Snapshot()
safe := map[string]any{
"keys": snap.Keys,
"accounts": []map[string]any{},
"env_backed": h.Store.IsEnvBacked(),
"keys": snap.Keys,
"accounts": []map[string]any{},
"env_backed": h.Store.IsEnvBacked(),
"env_source_present": h.Store.HasEnvConfigSource(),
"env_writeback_enabled": h.Store.IsEnvWritebackEnabled(),
"config_path": h.Store.ConfigPath(),
"claude_mapping": func() map[string]string {
if len(snap.ClaudeMapping) > 0 {
return snap.ClaudeMapping

View File

@@ -267,6 +267,22 @@ func (s *Store) IsEnvBacked() bool {
return s.fromEnv
}
func (s *Store) IsEnvWritebackEnabled() bool {
return envWritebackEnabled()
}
func (s *Store) HasEnvConfigSource() bool {
rawCfg := strings.TrimSpace(os.Getenv("DS2API_CONFIG_JSON"))
if rawCfg == "" {
rawCfg = strings.TrimSpace(os.Getenv("CONFIG_JSON"))
}
return rawCfg != ""
}
func (s *Store) ConfigPath() string {
return s.path
}
func (s *Store) SetVercelSync(hash string, ts int64) error {
return s.Update(func(c *Config) error {
c.VercelSyncHash = hash

View File

@@ -64,6 +64,27 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage,
return (
<div className="space-y-6">
{Boolean(config?.env_source_present) && (
<div className={`rounded-xl border px-4 py-3 text-sm ${
config?.env_writeback_enabled
? (config?.env_backed ? 'border-amber-500/30 bg-amber-500/10 text-amber-600' : 'border-emerald-500/30 bg-emerald-500/10 text-emerald-600')
: 'border-amber-500/30 bg-amber-500/10 text-amber-600'
}`}>
<p className="font-medium">
{config?.env_writeback_enabled
? (config?.env_backed
? t('accountManager.envModeWritebackPendingTitle')
: t('accountManager.envModeWritebackActiveTitle'))
: t('accountManager.envModeRiskTitle')}
</p>
<p className="mt-1 text-xs opacity-90">
{config?.env_writeback_enabled
? t('accountManager.envModeWritebackDesc', { path: config?.config_path || 'config.json' })
: t('accountManager.envModeRiskDesc')}
</p>
</div>
)}
<QueueCards queueStatus={queueStatus} t={t} />
<ApiKeysPanel

View File

@@ -138,7 +138,12 @@
"sessionCount": "Sessions: {count}",
"deleteAllSessions": "Delete all sessions",
"deleteAllSessionsConfirm": "Are you sure you want to delete all sessions for this account? This action cannot be undone.",
"deleteAllSessionsSuccess": "Successfully deleted all sessions"
"deleteAllSessionsSuccess": "Successfully deleted all sessions",
"envModeRiskTitle": "Environment-variable config mode detected (persistence risk)",
"envModeRiskDesc": "Detected DS2API_CONFIG_JSON/CONFIG_JSON. If DS2API_ENV_WRITEBACK is not enabled, Admin UI edits are in-memory only and may be lost after restart.",
"envModeWritebackPendingTitle": "Env mode + auto-persistence enabled (pending file handoff)",
"envModeWritebackActiveTitle": "Env mode + auto-persistence active",
"envModeWritebackDesc": "The app will auto-create/write the config file and transition to file-backed mode. Current persistence path: {path}"
},
"apiTester": {
"defaultMessage": "Hello, please introduce yourself in one sentence.",

View File

@@ -138,7 +138,12 @@
"sessionCount": "会话: {count}",
"deleteAllSessions": "删除所有会话",
"deleteAllSessionsConfirm": "确定要删除该账号的所有会话吗?此操作不可恢复。",
"deleteAllSessionsSuccess": "删除成功"
"deleteAllSessionsSuccess": "删除成功",
"envModeRiskTitle": "当前为环境变量配置模式(有持久化风险)",
"envModeRiskDesc": "检测到 DS2API_CONFIG_JSON/CONFIG_JSON。若未开启 DS2API_ENV_WRITEBACK管理台改动仅在内存生效重启可能丢失。",
"envModeWritebackPendingTitle": "环境变量模式 + 自动持久化已开启(等待落盘)",
"envModeWritebackActiveTitle": "环境变量模式 + 自动持久化已生效",
"envModeWritebackDesc": "程序会自动创建/写入配置文件并在后续切换为文件模式。当前持久化路径:{path}"
},
"apiTester": {
"defaultMessage": "你好,请用一句话介绍你自己。",