feat(keys): 模型密钥开放给用户自配 —— 放开官方「模型」页 + 平台改预置凭据

用户要求「把配置模型密钥开放给用户自己配」且「界面交互和官方一模一样」⇒ 不仿制,直接放开官方 ui-settings-models 页(可选厂家:DeepSeek 内置 + 自定义 OpenAI 兼容网关含 baseURL/模型)。

- ensure-role-profile-patch.cjs:不再对普通用户禁用 ui-settings-models(保留 plugins/inventory/cordis 禁用);--force 时能把「还禁着 models」的旧块升级。

- src/web/server.ts:resolveApiKey 不再注入 DEEPSEEK_API_KEY env,改为把「平台共享密钥」预置进 $DSH_HOME/.credentials.yaml 的 refs 段(新增 ensureRefInCredentials:只在无该 ref 时写 / 只在 version:1 上插入 / 备份落平台目录 / 写完 chown 给实例 uid / 失败退回 env)。真因:dsh 凭据解析里 env 优先级最高,且 dsh-credentials-local.write() 的 assertUnshadowed() 会让用户在模型页保存直接报错 ⇒ 注入 env 等于锁死用户自配。

- src/web/routes/auth.ts:/api/me/keys 由 requireAdmin 放开为 requireAuth(平台侧密钥 API 保留,UI 不再暴露)。

- poc/business-plugins 0.3.4→0.3.8:「系统管理」对齐门户 6 个功能页(同名同构,PA_PAGES 逐函数移植 portal)/ 官方插件列表高度改为「离弹窗底部约 100px」/ 撤掉自造的「我的密钥」分区(改由官方模型页承担)。

- web/portal.html:keys 页语义改名「平台共享密钥(未自配密钥的用户默认使用;仅管理员可改)」。scripts/verify-platform-admin-section.mjs:断言同步升级(含 zh/en 词典键集一致性、内联 HTML class 扫描)。
This commit is contained in:
admin committed 2026-09-13 22:27:49 +08:00
1 parent f6d4ad9b15
commit eb50ca2d5b
7 files changed
+1739 -471

No files matched your search

+3 -3
View File
@@ -165,7 +165,7 @@ function pageHead(title, sub) {
function renderHome() {
const sec1 = [
{ ic: '🖥️', t: '服务管理', d: '文件树 + DSH 启动', hash: '#/files' },
{ ic: '🔑', t: '密钥管理', d: '全局 API 密钥', hash: '#/keys' },
{ ic: '🔑', t: '密钥管理', d: '平台共享密钥', hash: '#/keys' },
]
const sec2 = [
{ ic: '👥', t: '用户管理', d: '审批 / 禁用 / 删除', hash: '#/users', admin: true },
@@ -267,7 +267,7 @@ async function initFiles() {
/* ================= 密钥管理(#/keys) ================= */
async function renderKeys() {
return `${pageHead('密钥管理', '全局 API 密钥(所有用户共用,仅管理员可改)')}
return `${pageHead('密钥管理', '平台共享密钥(未自配密钥的用户默认使用;仅管理员可改)')}
<div class="card">
<div id="keyList"></div>
<div style="margin-top:14px;display:flex;gap:8px;align-items:center">
@@ -282,7 +282,7 @@ async function initKeys() {
const res = await fetch('/api/me/keys'); if (!res.ok) return
const { keys } = await res.json()
const list = $('keyList')
if (keys.length === 0) { list.innerHTML = '<div class="empty">暂无全局密钥。添加后所有用户的 DSH 才能调用模型。</div>'; return }
if (keys.length === 0) { list.innerHTML = '<div class="empty">暂无平台共享密钥。未自配密钥的用户将无法调用模型 —— 用户可在自己实例的「设置 → 我的密钥」里配置自己的密钥。</div>'; return }
list.innerHTML = keys.map((k) => `<div class="key-row">
<span class="dot ${k.enabled ? 'on' : 'off'}"></span><strong>${esc(k.name)}</strong><span style="flex:1"></span>
${k.enabled ? '<span style="font-size:12px;color:#1a7f37">启用中</span>' : `<button class="btn-sm" data-sel="${k.id}">启用</button>`}