@modesto_ma/dsh-channels 0.5.0-beta.4 → 0.5.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -22
- package/docs/attachment-pipeline-v2.md +1 -1
- package/docs/feishu-onboarding.md +2 -2
- package/lib/client.js +1304 -1490
- package/lib/client.js.map +1 -1
- package/lib/index.js +400 -348
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/number-field.ts +97 -0
- package/src/client/panel/access-tab.tsx +61 -20
- package/src/client/panel/advanced-tab.tsx +59 -68
- package/src/client/panel/connection-tab.tsx +147 -164
- package/src/client/panel/credentials-card.tsx +124 -87
- package/src/client/panel/fields-tab.tsx +27 -0
- package/src/client/panel/index.tsx +55 -46
- package/src/client/panel/shared.tsx +358 -253
- package/src/client/panel/whitelist.tsx +197 -0
- package/src/client/panel/wiki-tab.tsx +24 -38
- package/src/client/styles.ts +108 -91
- package/src/index.ts +1 -1
- package/src/settings/contract.ts +197 -152
- package/src/settings/surface.ts +179 -197
- package/src/client/panel/access-card.tsx +0 -329
- package/src/client/panel/analyze-token-card.tsx +0 -70
- package/src/client/panel/model-tab.tsx +0 -23
- package/src/client/panel/sessions-tab.tsx +0 -24
- package/src/client/panel/workspace-tab.tsx +0 -24
package/lib/client.js
CHANGED
|
@@ -11,7 +11,7 @@ window.__ModuleLoader__.load({
|
|
|
11
11
|
* Web 设置面板的线上契约:Host 半侧的路由/面板数据与 Browser 半侧的渲染共用。
|
|
12
12
|
*
|
|
13
13
|
* 本模块**没有运行时依赖**:Host 与 Client bundle 都直接内联它。因此这里只放
|
|
14
|
-
*
|
|
14
|
+
* 常量、字段清单、可序列化的 wire 类型与无依赖的纯文案函数,不 import zod / node / DSH 内部包。
|
|
15
15
|
*
|
|
16
16
|
* @module dsh-channels/settings/contract
|
|
17
17
|
*/
|
|
@@ -56,15 +56,15 @@ window.__ModuleLoader__.load({
|
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
id: "sessions",
|
|
59
|
-
label: "
|
|
59
|
+
label: "会话"
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
id: "model",
|
|
63
63
|
label: "模型"
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
|
-
id: "
|
|
67
|
-
label: "
|
|
66
|
+
id: "attachments",
|
|
67
|
+
label: "附件与工具"
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
id: "access",
|
|
@@ -79,91 +79,91 @@ window.__ModuleLoader__.load({
|
|
|
79
79
|
label: "高级"
|
|
80
80
|
}
|
|
81
81
|
];
|
|
82
|
-
/** Which editable field groups each tab renders
|
|
82
|
+
/** Which editable field groups each tab renders, in section order. */
|
|
83
83
|
const TAB_FIELD_GROUPS = {
|
|
84
84
|
connection: [],
|
|
85
85
|
sessions: [
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
86
|
+
"trigger",
|
|
87
|
+
"workspace",
|
|
88
|
+
"cards",
|
|
89
|
+
"features"
|
|
89
90
|
],
|
|
90
91
|
model: ["model"],
|
|
91
|
-
|
|
92
|
+
attachments: [
|
|
93
|
+
"attachments",
|
|
94
|
+
"tools",
|
|
95
|
+
"limits"
|
|
96
|
+
],
|
|
92
97
|
access: ["access"],
|
|
93
98
|
wiki: ["wiki"],
|
|
94
|
-
advanced: ["
|
|
99
|
+
advanced: ["analyze", "logging"]
|
|
95
100
|
};
|
|
96
101
|
/** Section heading for one field group inside its tab. */
|
|
97
102
|
const FIELD_GROUP_LABELS = {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
model: "
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
trigger: "触发",
|
|
104
|
+
workspace: "工作区",
|
|
105
|
+
cards: "话题与卡片",
|
|
106
|
+
features: "飞书侧功能",
|
|
107
|
+
model: "模型",
|
|
108
|
+
attachments: "入站附件",
|
|
109
|
+
tools: "模型可用的飞书工具",
|
|
110
|
+
limits: "限额与超时",
|
|
111
|
+
access: "准入",
|
|
112
|
+
wiki: "知识库工具",
|
|
113
|
+
analyze: "日志分析接口",
|
|
114
|
+
logging: "日志"
|
|
106
115
|
};
|
|
116
|
+
/** Groups rendered collapsed behind one summary row (rarely changed values). */
|
|
117
|
+
const COLLAPSED_FIELD_GROUPS = ["limits"];
|
|
107
118
|
/** Display labels for the panel header/cards (kept on the wire so the client owns no copy). */
|
|
108
119
|
const PANEL_COPY = {
|
|
109
120
|
title: "飞书渠道",
|
|
110
|
-
description: "飞书长连接、应用凭据与渠道配置。凭据只写入 DSH 凭据库且不回显;标注「需重连」的项在连接建立时读取。",
|
|
111
|
-
statusCard: "连接状态",
|
|
112
121
|
phaseUnconfigured: "待配置",
|
|
113
122
|
phaseIdle: "未连接",
|
|
114
123
|
phaseConnecting: "连接中",
|
|
115
124
|
phaseConnected: "已连接",
|
|
116
125
|
phaseError: "连接失败",
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
statusSummaryError: "最近一次连接飞书失败,请检查凭据与网络后重试。",
|
|
126
|
+
statusSummaryConnected: "机器人在线,可以收发消息",
|
|
127
|
+
statusSummaryUnconfigured: "还没有填写飞书应用凭据,插件不会连接飞书",
|
|
128
|
+
statusSummaryIdle: "凭据已就绪,还没有建立连接",
|
|
129
|
+
statusSummaryConnecting: "正在连接飞书…",
|
|
130
|
+
statusSummaryError: "连接飞书失败,请检查凭据与网络后重试",
|
|
131
|
+
uptimePrefix: "运行",
|
|
124
132
|
lastError: "最近一次连接失败",
|
|
125
|
-
|
|
126
|
-
scopeCheckOk: "
|
|
127
|
-
scopeCheckMissing: "
|
|
128
|
-
|
|
133
|
+
checksTitle: "自检",
|
|
134
|
+
scopeCheckOk: "插件需要的飞书权限都已开通",
|
|
135
|
+
scopeCheckMissing: "{n} 项权限未开通",
|
|
136
|
+
scopeCheckMissingRequired: "{n} 项必需权限未开通,相关功能无法使用",
|
|
137
|
+
scopeCheckMissingOptional: "不影响收发消息,只有相关功能不可用。开通后需在开发者后台发布新版本。",
|
|
138
|
+
scopeCheckPublish: "开通后需在开发者后台发布新版本。",
|
|
139
|
+
scopeCheckOpenAll: "全部开通",
|
|
129
140
|
scopeCheckOpenOne: "去开通",
|
|
130
|
-
scopeCheckOptional: "可选",
|
|
131
141
|
scopeCheckRequired: "必需",
|
|
132
|
-
scopeCheckError: "
|
|
142
|
+
scopeCheckError: "无法读取权限开通情况",
|
|
133
143
|
slashCommandSyncTitle: "斜杠指令面板",
|
|
134
|
-
slashCommandSyncOk: "
|
|
135
|
-
|
|
136
|
-
slashCommandSyncCreated: "本次新增",
|
|
137
|
-
slashCommandSyncUpdated: "本次更新",
|
|
138
|
-
slashCommandSyncRemoved: "本次删除",
|
|
139
|
-
slashCommandSyncError: "同步飞书原生斜杠指令面板失败(不影响直接输入命令):",
|
|
144
|
+
slashCommandSyncOk: "已同步 {total} 条命令,飞书客户端约 5 分钟后刷新",
|
|
145
|
+
slashCommandSyncError: "同步失败,直接输入命令不受影响",
|
|
140
146
|
missingConfig: "缺少配置",
|
|
141
|
-
statusHintUnconfigured: "配置完成后插件会自动连接;也可以稍后在本页手动连接。",
|
|
142
|
-
connectionInfo: "连接信息",
|
|
143
|
-
connectionStateLabel: "飞书长连接",
|
|
144
|
-
botOpenId: "Bot open_id",
|
|
145
|
-
uptime: "运行时长",
|
|
146
147
|
routeCount: "话题路由",
|
|
147
148
|
liveAgentCount: "活动会话",
|
|
148
149
|
inflightTurnCount: "在途回复",
|
|
150
|
+
botOpenId: "Bot open_id",
|
|
149
151
|
healthReasons: "健康判定",
|
|
150
152
|
unknownValue: "—",
|
|
151
153
|
connect: "连接飞书",
|
|
152
154
|
connecting: "连接中…",
|
|
153
155
|
onboardingTitle: "开始使用飞书渠道",
|
|
154
|
-
onboardingIntro: "只需三步:在飞书开放平台创建自建应用,把 App ID / App Secret 填入本页,保存后自动连接。",
|
|
155
156
|
onboardingStep1Title: "创建飞书自建应用",
|
|
156
|
-
onboardingStep1Text: "
|
|
157
|
+
onboardingStep1Text: "在 open.feishu.cn/app 创建企业自建应用,开启机器人能力;事件订阅选「长连接」,订阅 im.message.receive_v1。",
|
|
157
158
|
onboardingStep2Title: "填写应用凭据",
|
|
158
|
-
onboardingStep2Text: "
|
|
159
|
-
onboardingStep3Title: "
|
|
160
|
-
onboardingStep3Text: "
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
credentialsRef: "凭据引用",
|
|
159
|
+
onboardingStep2Text: "把 App ID(cli_ 开头)和 App Secret 填到下方「应用凭据」。",
|
|
160
|
+
onboardingStep3Title: "保存后自动连接",
|
|
161
|
+
onboardingStep3Text: "两项都保存后插件会自动连接,状态变成「已连接」就能在飞书里私聊机器人。",
|
|
162
|
+
credentialsTitle: "应用凭据",
|
|
163
|
+
credentialsHint: "值只存在本机凭据库,面板不回显。",
|
|
164
|
+
appIdLabel: "App ID",
|
|
165
|
+
appSecretLabel: "App Secret",
|
|
166
|
+
credentialStore: "凭据库",
|
|
167
167
|
saveCredentials: "保存凭据",
|
|
168
168
|
savingCredentials: "保存中…",
|
|
169
169
|
credentialSaved: "凭据已保存",
|
|
@@ -173,27 +173,29 @@ window.__ModuleLoader__.load({
|
|
|
173
173
|
credentialAutoConnectFailed: "凭据已保存,但自动连接失败",
|
|
174
174
|
credentialRemoved: "已移除存储的凭据",
|
|
175
175
|
credentialRemovedPending: "已移除存储的凭据,重启飞书连接后生效",
|
|
176
|
-
|
|
176
|
+
replaceCredential: "更换",
|
|
177
|
+
setCredential: "设置",
|
|
178
|
+
cancel: "取消",
|
|
179
|
+
removeCredential: "移除",
|
|
177
180
|
removingCredential: "移除中…",
|
|
178
181
|
credentialConfigured: "已配置",
|
|
179
182
|
credentialMissing: "未配置",
|
|
180
183
|
credentialReadOnly: "当前凭据存储不可写",
|
|
181
184
|
appIdPlaceholder: "输入 App ID(cli_…)",
|
|
182
185
|
appSecretPlaceholder: "输入 App Secret",
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
sessionsTabHelp: "私聊、群聊触发方式与入站内容:附件存储/预取、模型工具开关与读取预算;「需重连」项在下次连接时读取。",
|
|
186
|
-
modelTabHelp: "飞书会话使用的模型服务商、模型、推理档位与 Agent 预设;下拉选项来自宿主机实时目录,保存后对新会话生效。",
|
|
187
|
-
workspaceTabHelp: "每个用户一个工作区(模型/工具产生文件的落点),每个话题在其中拥有独立工作目录;只影响之后新建的话题。入站附件默认存于 DSH 原生附件库,不占用话题目录。插件不会自动删除工作区或话题目录;过期目录可手动删除,话题下次对话时会重建空目录。",
|
|
188
|
-
accessTabHelp: "准入采用 fail-closed:管理员不受白名单限制,非管理员私聊会被拒绝,群聊需要「群已授权 + 用户在白名单」两层同时命中。",
|
|
189
|
-
wikiTabHelp: "知识库工具以应用身份访问「知识库范围」内的文档、电子表格和多维表格。只有管理员或知识库白名单里的用户发起的话题能用;每次打开、新建、修改仍需话题发起人在授权卡上确认。",
|
|
190
|
-
advancedTabHelp: "流式刷新等低层参数、Web 覆盖文件与只读信息。",
|
|
191
|
-
save: "保存更改",
|
|
186
|
+
credentialRemoveConfirm: "确定移除已存储的凭据值?移除后不会回显,需要时须重新填写。",
|
|
187
|
+
save: "保存",
|
|
192
188
|
saving: "保存中…",
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
189
|
+
discard: "放弃",
|
|
190
|
+
dirtyCount: "{n} 项未保存",
|
|
191
|
+
dirtyRestart: ",其中 {n} 项需要重连后生效",
|
|
192
|
+
details: "详情",
|
|
193
|
+
expand: "展开",
|
|
194
|
+
collapse: "收起",
|
|
195
|
+
collapsedCount: "{n} 项",
|
|
196
|
+
collapsedClean: "一般不用改",
|
|
197
|
+
collapsedChanged: "{n} 项已在面板修改",
|
|
198
|
+
restartTag: "需重连",
|
|
197
199
|
listAdd: "添加",
|
|
198
200
|
listRemove: "移除",
|
|
199
201
|
listEntry: "第 {n} 项",
|
|
@@ -205,62 +207,55 @@ window.__ModuleLoader__.load({
|
|
|
205
207
|
directoryPicked: "已选择目录",
|
|
206
208
|
directoryPickFailed: "无法打开系统目录选择器",
|
|
207
209
|
saved: "已保存",
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
210
|
+
saveInvalid: "请先修正标红的配置项",
|
|
211
|
+
numberInvalid: "请输入数字",
|
|
212
|
+
numberMin: "不得小于 {n}",
|
|
213
|
+
numberMax: "不得大于 {n}",
|
|
214
|
+
modelFootnote: "对之后新建的会话生效。",
|
|
215
|
+
catalogFailed: "模型目录加载失败,已退回手填。",
|
|
216
|
+
catalogLoading: "正在读取宿主机选项…",
|
|
217
|
+
catalogUnavailable: "宿主机未提供可选项,请手填。",
|
|
218
|
+
restartBanner: "以下配置要重启飞书连接后生效:",
|
|
219
|
+
restartWarning: "重启会断开当前飞书连接并中断进行中的回复,确定现在重启?",
|
|
220
|
+
reconnect: "重启连接",
|
|
218
221
|
reconnecting: "重连中…",
|
|
219
|
-
refresh: "
|
|
222
|
+
refresh: "刷新",
|
|
220
223
|
retry: "重试",
|
|
221
224
|
pollFailed: "状态刷新失败",
|
|
222
225
|
connectDone: "已连接飞书",
|
|
223
226
|
reconnectDone: "已重启飞书连接",
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
disabled: "已关闭",
|
|
227
|
-
credentialRemoveConfirm: "确定移除已存储的凭据值?移除后不会回显,需要时须重新填写。",
|
|
228
|
-
analyzeTokenCard: "日志分析接口令牌",
|
|
229
|
-
analyzeTokenCardHelp: "日志平台调用分析接口时 Authorization: Bearer 后面的值。经 DSH 凭据服务写入本机凭据库,面板永不回显;保存或移除后下一次请求即生效,无需重连。未配置时接口对所有请求返回 503。",
|
|
227
|
+
loading: "加载中…",
|
|
228
|
+
loadFailed: "设置面板加载失败",
|
|
230
229
|
analyzeTokenLabel: "访问令牌",
|
|
230
|
+
analyzeTokenSummary: "请求头 Authorization: Bearer 的值,保存后立即生效;未配置时接口拒绝所有请求",
|
|
231
231
|
analyzeTokenPlaceholder: "填写新令牌(建议 32 位以上随机串)",
|
|
232
|
-
saveAnalyzeToken: "保存令牌",
|
|
233
232
|
analyzeTokenSaved: "令牌已保存,下一次请求生效",
|
|
234
233
|
analyzeTokenRemoved: "令牌已移除,接口将拒绝所有请求",
|
|
235
|
-
|
|
236
|
-
loading: "加载中…",
|
|
237
|
-
loadFailed: "设置面板加载失败",
|
|
238
|
-
catalogFailed: "模型目录加载失败,已退回手填。",
|
|
239
|
-
catalogLoading: "正在读取宿主机选项…",
|
|
240
|
-
catalogUnavailable: "宿主机未提供可选项,请手填。",
|
|
241
|
-
version: "插件版本",
|
|
234
|
+
overlayTitle: "Web 覆盖",
|
|
242
235
|
overlayPath: "覆盖文件",
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
236
|
+
overlayCount: "{n} 项配置由面板覆盖",
|
|
237
|
+
noOverrides: "当前没有面板覆盖的配置",
|
|
238
|
+
overlayPathHelp: "面板保存的配置写在这个 JSON 文件里,优先级高于 profile。可以直接编辑;面板保存会整体重写它。",
|
|
239
|
+
clear: "恢复 profile 配置",
|
|
240
|
+
clearing: "恢复中…",
|
|
241
|
+
clearConfirm: "确定删除所有面板覆盖,恢复为 profile 配置?",
|
|
242
|
+
cleared: "已恢复为 profile 配置",
|
|
243
|
+
debugModeWarning: "调试模式已打开:应用可用范围内的任何人都能和机器人对话。用完请在「访问授权」关闭。",
|
|
244
|
+
accessAdminMissingWarning: "没有可用的管理员,所有对话都会被拒绝。请在下方填写管理员 user_id(私聊机器人发送 /userid 获取)。",
|
|
245
|
+
accessAdminOwnerSummary: "未单独设置,正在使用应用所有者 {id}",
|
|
246
|
+
accessNotLoaded: "飞书连接建立后加载授权列表",
|
|
247
|
+
accessRequestsTitle: "待处理申请",
|
|
252
248
|
accessGroupsTitle: "已授权群",
|
|
253
|
-
accessGroupsEmpty: "尚未授权任何群。机器人被拉进群后,管理员会收到带 chat_id 的群信息卡;也可在群内发送 /groupid 获取。",
|
|
254
249
|
accessUsersTitle: "已授权用户",
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
accessGroupChatIdPlaceholder: "群 chat_id(oc_…)",
|
|
250
|
+
accessGroupsEmpty: "还没有授权任何群",
|
|
251
|
+
accessUsersEmpty: "还没有授权任何用户",
|
|
252
|
+
accessGroupChatIdPlaceholder: "chat_id(oc_…,群里发 /groupid 获取)",
|
|
259
253
|
accessGroupNamePlaceholder: "群名(可选)",
|
|
260
|
-
accessUserIdPlaceholder: "
|
|
254
|
+
accessUserIdPlaceholder: "user_id(私聊机器人发 /userid 获取)",
|
|
261
255
|
accessUserNamePlaceholder: "昵称(可选)",
|
|
262
256
|
accessAddGroup: "添加群",
|
|
263
257
|
accessAddUser: "添加用户",
|
|
258
|
+
accessAdd: "添加",
|
|
264
259
|
accessRemove: "移除",
|
|
265
260
|
accessAllow: "授权",
|
|
266
261
|
accessDeny: "忽略",
|
|
@@ -268,24 +263,36 @@ window.__ModuleLoader__.load({
|
|
|
268
263
|
accessSourceCommand: "对话命令",
|
|
269
264
|
accessSourceCard: "授权卡",
|
|
270
265
|
accessSourcePanel: "设置面板",
|
|
271
|
-
accessRequestGroup: "
|
|
272
|
-
accessRequestUser: "
|
|
273
|
-
|
|
274
|
-
accessRequestExpires: "
|
|
266
|
+
accessRequestGroup: "申请授权群",
|
|
267
|
+
accessRequestUser: "申请使用",
|
|
268
|
+
accessRequestIn: "在「{group}」",
|
|
269
|
+
accessRequestExpires: "{date} 前有效",
|
|
275
270
|
accessGroupAdded: "已添加群授权",
|
|
276
271
|
accessGroupRemoved: "已移除群授权",
|
|
277
272
|
accessUserAdded: "已添加用户授权",
|
|
278
273
|
accessUserRemoved: "已移除用户授权",
|
|
279
274
|
accessRequestAllowed: "已授权",
|
|
280
275
|
accessRequestDenied: "已忽略",
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
276
|
+
wikiUsersTitle: "可使用的人",
|
|
277
|
+
wikiUsersAdminPlus: "管理员 + {n}",
|
|
278
|
+
wikiUsersEmpty: "目前只有管理员发起的话题能用知识库工具",
|
|
279
|
+
wikiUsersHint: "也可以在飞书发 /auth wiki add @用户",
|
|
285
280
|
wikiUserAdded: "已加入知识库白名单",
|
|
286
|
-
wikiUserRemoved: "已移出知识库白名单"
|
|
287
|
-
accessChatIdHint: "chat_id 可从入群群信息卡或群内 /groupid 获取;user_id 可私聊机器人发送 /userid 获取。"
|
|
281
|
+
wikiUserRemoved: "已移出知识库白名单"
|
|
288
282
|
};
|
|
283
|
+
/**
|
|
284
|
+
* Fill `{name}` placeholders of one `PANEL_COPY` template.
|
|
285
|
+
*
|
|
286
|
+
* @param template - Copy containing `{name}` placeholders.
|
|
287
|
+
* @param values - Replacement per placeholder name.
|
|
288
|
+
* @returns The filled string; unknown placeholders stay as written.
|
|
289
|
+
*/
|
|
290
|
+
function fillCopy(template, values) {
|
|
291
|
+
return template.replace(/\{(\w+)\}/g, (match, name) => {
|
|
292
|
+
const value = values[name];
|
|
293
|
+
return value === void 0 ? match : String(value);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
289
296
|
//#endregion
|
|
290
297
|
//#region src/client/api.ts
|
|
291
298
|
/**
|
|
@@ -602,6 +609,110 @@ window.__ModuleLoader__.load({
|
|
|
602
609
|
});
|
|
603
610
|
}
|
|
604
611
|
//#endregion
|
|
612
|
+
//#region src/client/number-field.ts
|
|
613
|
+
/**
|
|
614
|
+
* 浏览器半区的 `number` 字段辅助:按 Host 描述的 `scale` 在显示单位与存储
|
|
615
|
+
* 单位之间换算、保存前校验上下界,以及字节数的 MiB 读数。
|
|
616
|
+
*
|
|
617
|
+
* 不含 JSX/React,便于单测。Host 保存时按 config schema 复核。
|
|
618
|
+
*
|
|
619
|
+
* @module dsh-channels/client/number-field
|
|
620
|
+
*/
|
|
621
|
+
/** Drop float noise from `stored / scale` (e.g. 0.30000000000000004). */
|
|
622
|
+
function tidy(value) {
|
|
623
|
+
return Number(value.toPrecision(12));
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Value the input shows for one field.
|
|
627
|
+
*
|
|
628
|
+
* @param field - The Host descriptor (`kind: 'number'`).
|
|
629
|
+
* @param value - Draft (the typed string) or effective value (stored units).
|
|
630
|
+
* @returns The input text.
|
|
631
|
+
*/
|
|
632
|
+
function numberInputValue(field, value) {
|
|
633
|
+
if (typeof value === "string") return value;
|
|
634
|
+
if (typeof value !== "number") return "";
|
|
635
|
+
return String(field.scale === void 0 ? value : tidy(value / field.scale));
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Input `step` in display units.
|
|
639
|
+
*
|
|
640
|
+
* @param field - The Host descriptor (`kind: 'number'`).
|
|
641
|
+
* @returns The step, or `undefined` when the Host gives none.
|
|
642
|
+
*/
|
|
643
|
+
function numberInputStep(field) {
|
|
644
|
+
if (field.step === void 0) return void 0;
|
|
645
|
+
return field.scale === void 0 ? field.step : tidy(field.step / field.scale);
|
|
646
|
+
}
|
|
647
|
+
/** One bound in display units with the field's unit appended. */
|
|
648
|
+
function boundLabel(field, stored) {
|
|
649
|
+
const shown = field.scale === void 0 ? stored : tidy(stored / field.scale);
|
|
650
|
+
return field.unit === void 0 ? String(shown) : `${shown} ${field.unit}`;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Turn one number draft into its save payload, checking the Host bounds.
|
|
654
|
+
*
|
|
655
|
+
* @param field - The Host descriptor (`kind: 'number'`).
|
|
656
|
+
* @param raw - The draft: the typed string, or a stored-unit number.
|
|
657
|
+
* @returns The stored-unit value (`''` clears an optional field), or the reason it is rejected.
|
|
658
|
+
*/
|
|
659
|
+
function numberDraftValue(field, raw) {
|
|
660
|
+
let stored;
|
|
661
|
+
if (typeof raw === "number") stored = raw;
|
|
662
|
+
else {
|
|
663
|
+
const text = typeof raw === "string" ? raw.trim() : "";
|
|
664
|
+
if (text === "") return field.optional === true ? {
|
|
665
|
+
ok: true,
|
|
666
|
+
value: ""
|
|
667
|
+
} : {
|
|
668
|
+
ok: false,
|
|
669
|
+
message: PANEL_COPY.numberInvalid
|
|
670
|
+
};
|
|
671
|
+
const parsed = Number(text);
|
|
672
|
+
if (!Number.isFinite(parsed)) return {
|
|
673
|
+
ok: false,
|
|
674
|
+
message: PANEL_COPY.numberInvalid
|
|
675
|
+
};
|
|
676
|
+
stored = field.scale === void 0 ? parsed : Math.round(parsed * field.scale);
|
|
677
|
+
}
|
|
678
|
+
if (field.min !== void 0 && stored < field.min) return {
|
|
679
|
+
ok: false,
|
|
680
|
+
message: fillCopy(PANEL_COPY.numberMin, { n: boundLabel(field, field.min) })
|
|
681
|
+
};
|
|
682
|
+
if (field.max !== void 0 && stored > field.max) return {
|
|
683
|
+
ok: false,
|
|
684
|
+
message: fillCopy(PANEL_COPY.numberMax, { n: boundLabel(field, field.max) })
|
|
685
|
+
};
|
|
686
|
+
return {
|
|
687
|
+
ok: true,
|
|
688
|
+
value: stored
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Human reading of a byte count, e.g. `20 MiB`.
|
|
693
|
+
*
|
|
694
|
+
* @param text - The input text (bytes).
|
|
695
|
+
* @returns The reading, or `undefined` when the text is not a positive number.
|
|
696
|
+
*/
|
|
697
|
+
function bytesReading(text) {
|
|
698
|
+
const bytes = Number(text);
|
|
699
|
+
if (text.trim() === "" || !Number.isFinite(bytes) || bytes <= 0) return void 0;
|
|
700
|
+
const units = [
|
|
701
|
+
"B",
|
|
702
|
+
"KiB",
|
|
703
|
+
"MiB",
|
|
704
|
+
"GiB",
|
|
705
|
+
"TiB"
|
|
706
|
+
];
|
|
707
|
+
let value = bytes;
|
|
708
|
+
let unit = 0;
|
|
709
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
710
|
+
value /= 1024;
|
|
711
|
+
unit += 1;
|
|
712
|
+
}
|
|
713
|
+
return `${Number(value.toFixed(value >= 100 || unit === 0 ? 0 : 1))} ${units[unit]}`;
|
|
714
|
+
}
|
|
715
|
+
//#endregion
|
|
605
716
|
//#region src/feishu/wiki/links.ts
|
|
606
717
|
/**
|
|
607
718
|
* Feishu wiki (知识库) link parsing: the `wikiRoots` allow-list entries and
|
|
@@ -801,8 +912,8 @@ window.__ModuleLoader__.load({
|
|
|
801
912
|
//#endregion
|
|
802
913
|
//#region src/client/panel/shared.tsx
|
|
803
914
|
/**
|
|
804
|
-
* 设置面板共享层:页面状态类型、Host 字段渲染原语(
|
|
805
|
-
* SaveBar
|
|
915
|
+
* 设置面板共享层:页面状态类型、Host 字段渲染原语(SettingRow /
|
|
916
|
+
* FieldSections / SaveBar)、分区与列表原语、生命周期徽标与纯格式化工具。
|
|
806
917
|
*
|
|
807
918
|
* 这一层不发请求、不持有页面状态,只把 Host 下发的 wire 数据渲染成控件;
|
|
808
919
|
* 页面外壳在 `index.tsx`,各 tab 只组合这些原语。
|
|
@@ -812,7 +923,7 @@ window.__ModuleLoader__.load({
|
|
|
812
923
|
function messageOf(error) {
|
|
813
924
|
return error instanceof Error ? error.message : String(error);
|
|
814
925
|
}
|
|
815
|
-
/** Compact duration for the
|
|
926
|
+
/** Compact duration for the page header. */
|
|
816
927
|
function formatUptime(ms) {
|
|
817
928
|
if (ms === void 0 || !Number.isFinite(ms)) return PANEL_COPY.unknownValue;
|
|
818
929
|
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
@@ -832,21 +943,9 @@ window.__ModuleLoader__.load({
|
|
|
832
943
|
function fieldLabel(fields, key) {
|
|
833
944
|
return fields.find((field) => field.key === key)?.label ?? key;
|
|
834
945
|
}
|
|
835
|
-
/** Effective access-mode label, reusing the Host field options for consistency. */
|
|
836
|
-
function accessModeLabel(panel, mode) {
|
|
837
|
-
return panel.config.fields.find((item) => item.key === "accessMode")?.options?.find((option) => option.value === mode)?.label ?? mode;
|
|
838
|
-
}
|
|
839
|
-
/** Numeric draft kept as the raw input string; parsed on save. */
|
|
840
|
-
function numericDraft(field, raw) {
|
|
841
|
-
if (typeof raw === "number") return raw;
|
|
842
|
-
if (typeof raw !== "string" || raw.trim() === "") return void 0;
|
|
843
|
-
const parsed = Number(raw);
|
|
844
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
845
|
-
}
|
|
846
946
|
/**
|
|
847
947
|
* Turn the dirty draft into the wire payload, keeping the Host-descriptor
|
|
848
|
-
* bounds check client-side
|
|
849
|
-
* every value on save).
|
|
948
|
+
* bounds check client-side (the Host re-validates every value on save).
|
|
850
949
|
*/
|
|
851
950
|
function validateDraft(panel, draft) {
|
|
852
951
|
const values = {};
|
|
@@ -855,29 +954,12 @@ window.__ModuleLoader__.load({
|
|
|
855
954
|
if (!(field.key in draft)) continue;
|
|
856
955
|
const raw = draft[field.key];
|
|
857
956
|
if (field.kind === "number") {
|
|
858
|
-
const parsed =
|
|
859
|
-
if (parsed
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
continue;
|
|
865
|
-
}
|
|
866
|
-
if (field.min !== void 0 && parsed < field.min) {
|
|
867
|
-
issues.push({
|
|
868
|
-
key: field.key,
|
|
869
|
-
message: `不得小于 ${field.min}`
|
|
870
|
-
});
|
|
871
|
-
continue;
|
|
872
|
-
}
|
|
873
|
-
if (field.max !== void 0 && parsed > field.max) {
|
|
874
|
-
issues.push({
|
|
875
|
-
key: field.key,
|
|
876
|
-
message: `不得大于 ${field.max}`
|
|
877
|
-
});
|
|
878
|
-
continue;
|
|
879
|
-
}
|
|
880
|
-
values[field.key] = parsed;
|
|
957
|
+
const parsed = numberDraftValue(field, raw);
|
|
958
|
+
if (parsed.ok) values[field.key] = parsed.value;
|
|
959
|
+
else issues.push({
|
|
960
|
+
key: field.key,
|
|
961
|
+
message: parsed.message
|
|
962
|
+
});
|
|
881
963
|
continue;
|
|
882
964
|
}
|
|
883
965
|
if (field.kind === "boolean") {
|
|
@@ -897,7 +979,7 @@ window.__ModuleLoader__.load({
|
|
|
897
979
|
issues
|
|
898
980
|
};
|
|
899
981
|
}
|
|
900
|
-
/** Header
|
|
982
|
+
/** Header pill for the Host-derived lifecycle phase. */
|
|
901
983
|
function phaseBadge(phase) {
|
|
902
984
|
switch (phase) {
|
|
903
985
|
case "connected": return {
|
|
@@ -922,7 +1004,7 @@ window.__ModuleLoader__.load({
|
|
|
922
1004
|
};
|
|
923
1005
|
}
|
|
924
1006
|
}
|
|
925
|
-
/** One-line explanation
|
|
1007
|
+
/** One-line explanation of the current phase (connection tab status block). */
|
|
926
1008
|
function phaseSummary(phase) {
|
|
927
1009
|
switch (phase) {
|
|
928
1010
|
case "connected": return PANEL_COPY.statusSummaryConnected;
|
|
@@ -932,6 +1014,99 @@ window.__ModuleLoader__.load({
|
|
|
932
1014
|
default: return PANEL_COPY.statusSummaryUnconfigured;
|
|
933
1015
|
}
|
|
934
1016
|
}
|
|
1017
|
+
/** Small rounded status pill. */
|
|
1018
|
+
function Pill(props) {
|
|
1019
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1020
|
+
className: `dsh-channels-cfg__pill dsh-channels-cfg__pill--${props.tone}`,
|
|
1021
|
+
children: [props.dot === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsh-channels-cfg__dot" }) : null, props.children]
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
/** One titled section: a muted heading above a bordered row list. */
|
|
1025
|
+
function Section(props) {
|
|
1026
|
+
const { title, footnote, tone, children } = props;
|
|
1027
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1028
|
+
className: "dsh-channels-cfg__sec",
|
|
1029
|
+
children: [
|
|
1030
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1031
|
+
className: "dsh-channels-cfg__secTitle",
|
|
1032
|
+
children: title
|
|
1033
|
+
}),
|
|
1034
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1035
|
+
className: `dsh-channels-cfg__list${tone === "warn" ? " dsh-channels-cfg__list--warn" : ""}`,
|
|
1036
|
+
children
|
|
1037
|
+
}),
|
|
1038
|
+
footnote === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1039
|
+
className: "dsh-channels-cfg__secFoot",
|
|
1040
|
+
children: footnote
|
|
1041
|
+
})
|
|
1042
|
+
]
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* One setting row: label (with tags and the「详情」toggle) and a one-line
|
|
1047
|
+
* summary on the left, the control on the right. `details` renders below the
|
|
1048
|
+
* row once expanded; `wide` puts the control under the text.
|
|
1049
|
+
*/
|
|
1050
|
+
function Row(props) {
|
|
1051
|
+
const { label, tags, summary, details, control, error, wide = false, leading } = props;
|
|
1052
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1053
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1054
|
+
className: "dsh-channels-cfg__rowWrap",
|
|
1055
|
+
children: [
|
|
1056
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1057
|
+
className: `dsh-channels-cfg__row${wide ? " dsh-channels-cfg__row--wide" : ""}`,
|
|
1058
|
+
children: [
|
|
1059
|
+
leading,
|
|
1060
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1061
|
+
className: "dsh-channels-cfg__rowText",
|
|
1062
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1063
|
+
className: "dsh-channels-cfg__rowLabel",
|
|
1064
|
+
children: [
|
|
1065
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }),
|
|
1066
|
+
tags,
|
|
1067
|
+
details === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1068
|
+
type: "button",
|
|
1069
|
+
className: "dsh-channels-cfg__more",
|
|
1070
|
+
"aria-expanded": open,
|
|
1071
|
+
onClick: () => setOpen(!open),
|
|
1072
|
+
children: PANEL_COPY.details
|
|
1073
|
+
})
|
|
1074
|
+
]
|
|
1075
|
+
}), summary === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1076
|
+
className: "dsh-channels-cfg__rowSummary",
|
|
1077
|
+
children: summary
|
|
1078
|
+
})]
|
|
1079
|
+
}),
|
|
1080
|
+
control === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1081
|
+
className: "dsh-channels-cfg__rowCtl",
|
|
1082
|
+
children: control
|
|
1083
|
+
})
|
|
1084
|
+
]
|
|
1085
|
+
}),
|
|
1086
|
+
open && details !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1087
|
+
className: "dsh-channels-cfg__details",
|
|
1088
|
+
children: details
|
|
1089
|
+
}) : null,
|
|
1090
|
+
error === void 0 || error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1091
|
+
className: "dsh-channels-cfg__rowError",
|
|
1092
|
+
children: error
|
|
1093
|
+
})
|
|
1094
|
+
]
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
/** Accessible on/off switch. */
|
|
1098
|
+
function Switch(props) {
|
|
1099
|
+
const { checked, label, disabled = false, onChange } = props;
|
|
1100
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1101
|
+
type: "button",
|
|
1102
|
+
role: "switch",
|
|
1103
|
+
className: "dsh-channels-cfg__switch",
|
|
1104
|
+
"aria-checked": checked,
|
|
1105
|
+
"aria-label": label,
|
|
1106
|
+
disabled,
|
|
1107
|
+
onClick: () => onChange(!checked)
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
935
1110
|
/**
|
|
936
1111
|
* Text input paired with the host-native「选择目录…」button. The button owns
|
|
937
1112
|
* its own busy state: the OS dialog can stay open for a long time, so the
|
|
@@ -960,7 +1135,7 @@ window.__ModuleLoader__.load({
|
|
|
960
1135
|
onChange: (event) => onChange(field.key, event.target.value)
|
|
961
1136
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
962
1137
|
type: "button",
|
|
963
|
-
className: "dsh-channels-cfg__btn
|
|
1138
|
+
className: "dsh-channels-cfg__btn",
|
|
964
1139
|
disabled: disabled || picking,
|
|
965
1140
|
onClick: () => {
|
|
966
1141
|
pick();
|
|
@@ -971,7 +1146,7 @@ window.__ModuleLoader__.load({
|
|
|
971
1146
|
}
|
|
972
1147
|
/**
|
|
973
1148
|
* `string-list` control: one single-line input per entry, each with a remove
|
|
974
|
-
* button, and an add
|
|
1149
|
+
* button, and an add link below (disabled at `maxItems` inputs). An empty
|
|
975
1150
|
* list renders one blank input so the first entry can be typed directly.
|
|
976
1151
|
* Issues carrying an `index` mark that input and show the reason under it.
|
|
977
1152
|
*/
|
|
@@ -980,12 +1155,12 @@ window.__ModuleLoader__.load({
|
|
|
980
1155
|
const empty = listItems(value).length === 0;
|
|
981
1156
|
const rows = listRows(value);
|
|
982
1157
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
983
|
-
className: "dsh-channels-
|
|
1158
|
+
className: "dsh-channels-cfg__stringList",
|
|
984
1159
|
children: [rows.map((item, index) => {
|
|
985
1160
|
const issue = issues.find((entry) => entry.index === index);
|
|
986
1161
|
const position = PANEL_COPY.listEntry.replace("{n}", String(index + 1));
|
|
987
1162
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
988
|
-
className: "dsh-channels-
|
|
1163
|
+
className: "dsh-channels-cfg__stringListItem",
|
|
989
1164
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
990
1165
|
className: "dsh-channels-cfg__inputRow",
|
|
991
1166
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -999,7 +1174,7 @@ window.__ModuleLoader__.load({
|
|
|
999
1174
|
onChange: (event) => onChange(field.key, withListEntry(rows, index, event.target.value))
|
|
1000
1175
|
}), empty ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1001
1176
|
type: "button",
|
|
1002
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--
|
|
1177
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--remove",
|
|
1003
1178
|
"aria-label": `${PANEL_COPY.listRemove} ${position}`,
|
|
1004
1179
|
disabled,
|
|
1005
1180
|
onClick: () => onChange(field.key, withoutListEntry(rows, index)),
|
|
@@ -1010,874 +1185,703 @@ window.__ModuleLoader__.load({
|
|
|
1010
1185
|
children: issue.message
|
|
1011
1186
|
})]
|
|
1012
1187
|
}, index);
|
|
1013
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1188
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1014
1189
|
type: "button",
|
|
1015
|
-
className: "dsh-channels-
|
|
1190
|
+
className: "dsh-channels-cfg__link",
|
|
1016
1191
|
disabled: disabled || listIsFull(field, rows),
|
|
1017
1192
|
onClick: () => onChange(field.key, withBlankListEntry(rows)),
|
|
1018
|
-
children: PANEL_COPY.listAdd
|
|
1193
|
+
children: ["+ ", PANEL_COPY.listAdd]
|
|
1019
1194
|
}) })]
|
|
1020
1195
|
});
|
|
1021
1196
|
}
|
|
1022
|
-
/**
|
|
1023
|
-
function
|
|
1024
|
-
const { field, value,
|
|
1025
|
-
const
|
|
1026
|
-
const
|
|
1027
|
-
const inputClass = `dsh-channels-cfg__input${issue === void 0 ? "" : " dsh-channels-cfg__input--invalid"}`;
|
|
1197
|
+
/** Number input with its unit (and a MiB reading for byte counts). */
|
|
1198
|
+
function NumberInput(props) {
|
|
1199
|
+
const { field, value, inputClass, disabled, onChange } = props;
|
|
1200
|
+
const text = numberInputValue(field, value);
|
|
1201
|
+
const reading = field.unit === "字节" ? bytesReading(text) : void 0;
|
|
1028
1202
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1029
|
-
className:
|
|
1203
|
+
className: "dsh-channels-cfg__inputRow",
|
|
1030
1204
|
children: [
|
|
1031
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1032
|
-
className: "dsh-channels-
|
|
1033
|
-
children:
|
|
1034
|
-
className: "dsh-channels-cfg__label",
|
|
1035
|
-
children: field.label
|
|
1036
|
-
}), field.restartRequired ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1037
|
-
className: "dsh-channels-cfg__tag dsh-channels-cfg__tag--restart",
|
|
1038
|
-
children: PANEL_COPY.restartTag
|
|
1039
|
-
}) : null]
|
|
1205
|
+
reading === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1206
|
+
className: "dsh-channels-cfg__bytes",
|
|
1207
|
+
children: reading
|
|
1040
1208
|
}),
|
|
1041
|
-
|
|
1042
|
-
className: "dsh-channels-cfg__check",
|
|
1043
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1044
|
-
type: "checkbox",
|
|
1045
|
-
className: "dsh-channels-cfg__checkbox",
|
|
1046
|
-
checked: value === true,
|
|
1047
|
-
"aria-label": field.label,
|
|
1048
|
-
disabled,
|
|
1049
|
-
onChange: (event) => onChange(field.key, event.target.checked)
|
|
1050
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: value === true ? PANEL_COPY.enabled : PANEL_COPY.disabled })]
|
|
1051
|
-
}) : null,
|
|
1052
|
-
field.kind === "select" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
1053
|
-
className: inputClass,
|
|
1054
|
-
value: String(value ?? ""),
|
|
1055
|
-
disabled: disabled || field.disabled === true,
|
|
1056
|
-
"aria-label": field.label,
|
|
1057
|
-
onChange: (event) => onChange(field.key, event.target.value),
|
|
1058
|
-
children: (field.options ?? []).map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1059
|
-
value: option.value,
|
|
1060
|
-
children: option.label
|
|
1061
|
-
}, option.value))
|
|
1062
|
-
}) : null,
|
|
1063
|
-
field.kind === "number" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1209
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1064
1210
|
type: "number",
|
|
1065
|
-
className: inputClass
|
|
1066
|
-
value:
|
|
1067
|
-
min: field.min,
|
|
1068
|
-
max: field.max,
|
|
1069
|
-
step: field.step,
|
|
1070
|
-
"aria-label": field.label,
|
|
1071
|
-
disabled,
|
|
1072
|
-
onChange: (event) => onChange(field.key, event.target.value)
|
|
1073
|
-
}) : null,
|
|
1074
|
-
field.kind === "text" ? field.browse === "directory" && onPickDirectory !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DirectoryTextInput, {
|
|
1075
|
-
field,
|
|
1076
|
-
value,
|
|
1077
|
-
inputClass,
|
|
1078
|
-
disabled,
|
|
1079
|
-
onChange,
|
|
1080
|
-
onPick: onPickDirectory
|
|
1081
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1082
|
-
type: "text",
|
|
1083
|
-
className: inputClass,
|
|
1084
|
-
value: String(value ?? ""),
|
|
1211
|
+
className: `${inputClass} dsh-channels-cfg__input--num`,
|
|
1212
|
+
value: text,
|
|
1085
1213
|
placeholder: field.placeholder,
|
|
1214
|
+
step: numberInputStep(field),
|
|
1086
1215
|
"aria-label": field.label,
|
|
1087
1216
|
disabled,
|
|
1088
1217
|
onChange: (event) => onChange(field.key, event.target.value)
|
|
1089
|
-
}) : null,
|
|
1090
|
-
field.kind === "string-list" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StringListInput, {
|
|
1091
|
-
field,
|
|
1092
|
-
value,
|
|
1093
|
-
issues: entryIssues,
|
|
1094
|
-
disabled,
|
|
1095
|
-
onChange
|
|
1096
|
-
}) : null,
|
|
1097
|
-
field.help === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1098
|
-
className: "dsh-channels-cfg__help",
|
|
1099
|
-
children: field.help
|
|
1100
1218
|
}),
|
|
1101
|
-
|
|
1102
|
-
className: "dsh-channels-
|
|
1103
|
-
children:
|
|
1219
|
+
field.unit === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1220
|
+
className: "dsh-channels-cfg__unit",
|
|
1221
|
+
children: field.unit
|
|
1104
1222
|
})
|
|
1105
1223
|
]
|
|
1106
1224
|
});
|
|
1107
1225
|
}
|
|
1226
|
+
/** The control for one Host field descriptor. */
|
|
1227
|
+
function FieldControl(props) {
|
|
1228
|
+
const { field, value, issues, invalid, disabled, onChange, onPickDirectory } = props;
|
|
1229
|
+
const inputClass = `dsh-channels-cfg__input${invalid ? " dsh-channels-cfg__input--invalid" : ""}`;
|
|
1230
|
+
switch (field.kind) {
|
|
1231
|
+
case "boolean": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Switch, {
|
|
1232
|
+
checked: value === true,
|
|
1233
|
+
label: field.label,
|
|
1234
|
+
disabled,
|
|
1235
|
+
onChange: (next) => onChange(field.key, next)
|
|
1236
|
+
});
|
|
1237
|
+
case "select": {
|
|
1238
|
+
const toggle = field.toggle;
|
|
1239
|
+
if (toggle !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Switch, {
|
|
1240
|
+
checked: value === toggle.on,
|
|
1241
|
+
label: field.label,
|
|
1242
|
+
disabled,
|
|
1243
|
+
onChange: (next) => onChange(field.key, next ? toggle.on : toggle.off)
|
|
1244
|
+
});
|
|
1245
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
1246
|
+
className: `${inputClass} dsh-channels-cfg__select`,
|
|
1247
|
+
value: String(value ?? ""),
|
|
1248
|
+
disabled: disabled || field.disabled === true,
|
|
1249
|
+
"aria-label": field.label,
|
|
1250
|
+
onChange: (event) => onChange(field.key, event.target.value),
|
|
1251
|
+
children: (field.options ?? []).map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1252
|
+
value: option.value,
|
|
1253
|
+
children: option.label
|
|
1254
|
+
}, option.value))
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
case "number": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberInput, {
|
|
1258
|
+
field,
|
|
1259
|
+
value,
|
|
1260
|
+
inputClass,
|
|
1261
|
+
disabled,
|
|
1262
|
+
onChange
|
|
1263
|
+
});
|
|
1264
|
+
case "text": return field.browse === "directory" && onPickDirectory !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DirectoryTextInput, {
|
|
1265
|
+
field,
|
|
1266
|
+
value,
|
|
1267
|
+
inputClass,
|
|
1268
|
+
disabled,
|
|
1269
|
+
onChange,
|
|
1270
|
+
onPick: onPickDirectory
|
|
1271
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1272
|
+
type: "text",
|
|
1273
|
+
className: `${inputClass}${field.wide === true ? "" : " dsh-channels-cfg__input--text"}`,
|
|
1274
|
+
value: String(value ?? ""),
|
|
1275
|
+
placeholder: field.placeholder,
|
|
1276
|
+
"aria-label": field.label,
|
|
1277
|
+
disabled,
|
|
1278
|
+
onChange: (event) => onChange(field.key, event.target.value)
|
|
1279
|
+
});
|
|
1280
|
+
case "string-list": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StringListInput, {
|
|
1281
|
+
field,
|
|
1282
|
+
value,
|
|
1283
|
+
issues,
|
|
1284
|
+
disabled,
|
|
1285
|
+
onChange
|
|
1286
|
+
});
|
|
1287
|
+
default: return null;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
/** One Host field rendered as a setting row. */
|
|
1291
|
+
function SettingRow(props) {
|
|
1292
|
+
const { field, value, issues, disabled = false, onChange, onPickDirectory } = props;
|
|
1293
|
+
const issue = issues.find((item) => item.key === field.key && item.index === void 0);
|
|
1294
|
+
const entryIssues = issues.filter((item) => item.key === field.key && item.index !== void 0);
|
|
1295
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
1296
|
+
label: field.label,
|
|
1297
|
+
tags: field.restartRequired ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1298
|
+
className: "dsh-channels-cfg__tag",
|
|
1299
|
+
children: PANEL_COPY.restartTag
|
|
1300
|
+
}) : void 0,
|
|
1301
|
+
summary: field.summary,
|
|
1302
|
+
details: field.help,
|
|
1303
|
+
wide: field.wide === true,
|
|
1304
|
+
error: issue?.message,
|
|
1305
|
+
control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldControl, {
|
|
1306
|
+
field,
|
|
1307
|
+
value,
|
|
1308
|
+
issues: entryIssues,
|
|
1309
|
+
invalid: issue !== void 0,
|
|
1310
|
+
disabled,
|
|
1311
|
+
onChange,
|
|
1312
|
+
onPickDirectory
|
|
1313
|
+
})
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
/** Rows for every field of one group. */
|
|
1317
|
+
function GroupRows(props) {
|
|
1318
|
+
const { form, fields } = props;
|
|
1319
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: fields.map((field) => {
|
|
1320
|
+
const onPick = form.onPickDirectory;
|
|
1321
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
|
|
1322
|
+
field,
|
|
1323
|
+
value: form.draft[field.key] ?? effectiveValue(form.panel, field.key),
|
|
1324
|
+
issues: form.issues,
|
|
1325
|
+
disabled: form.locked,
|
|
1326
|
+
onChange: form.onChange,
|
|
1327
|
+
onPickDirectory: onPick === void 0 ? void 0 : () => onPick(field.key)
|
|
1328
|
+
}, field.key);
|
|
1329
|
+
}) });
|
|
1330
|
+
}
|
|
1108
1331
|
/**
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
1111
|
-
* every key out of a group does not leave a stray heading behind.
|
|
1332
|
+
* A group of rarely changed fields behind one summary row. It opens on its
|
|
1333
|
+
* own when one of its fields has a validation issue.
|
|
1112
1334
|
*/
|
|
1113
|
-
function
|
|
1114
|
-
const { form,
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
className: "dsh-channels-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
children: section.fields.map((field) => {
|
|
1134
|
-
const onPick = form.onPickDirectory;
|
|
1135
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldRow, {
|
|
1136
|
-
field,
|
|
1137
|
-
value: form.draft[field.key] ?? effectiveValue(form.panel, field.key),
|
|
1138
|
-
issues: form.issues,
|
|
1139
|
-
disabled: form.locked,
|
|
1140
|
-
onChange: form.onChange,
|
|
1141
|
-
onPickDirectory: onPick === void 0 ? void 0 : () => onPick(field.key)
|
|
1142
|
-
}, field.key);
|
|
1143
|
-
})
|
|
1144
|
-
})]
|
|
1145
|
-
}, section.group))
|
|
1146
|
-
});
|
|
1335
|
+
function CollapsedGroup(props) {
|
|
1336
|
+
const { form, fields } = props;
|
|
1337
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1338
|
+
const keys = new Set(fields.map((field) => field.key));
|
|
1339
|
+
const changed = fields.filter((field) => form.panel.config.overriddenKeys.includes(field.key) || field.key in form.draft).length;
|
|
1340
|
+
const expanded = open || form.issues.some((issue) => keys.has(issue.key));
|
|
1341
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
1342
|
+
label: fillCopy(PANEL_COPY.collapsedCount, { n: fields.length }),
|
|
1343
|
+
summary: changed === 0 ? PANEL_COPY.collapsedClean : fillCopy(PANEL_COPY.collapsedChanged, { n: changed }),
|
|
1344
|
+
control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1345
|
+
type: "button",
|
|
1346
|
+
className: "dsh-channels-cfg__more",
|
|
1347
|
+
"aria-expanded": expanded,
|
|
1348
|
+
onClick: () => setOpen(!expanded),
|
|
1349
|
+
children: expanded ? PANEL_COPY.collapse : PANEL_COPY.expand
|
|
1350
|
+
})
|
|
1351
|
+
}), expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupRows, {
|
|
1352
|
+
form,
|
|
1353
|
+
fields
|
|
1354
|
+
}) : null] });
|
|
1147
1355
|
}
|
|
1148
|
-
/**
|
|
1356
|
+
/**
|
|
1357
|
+
* One tab's editable fields as titled sections, one per Host field group, in
|
|
1358
|
+
* the given order. Empty groups render nothing, so a Host that moves every key
|
|
1359
|
+
* out of a group does not leave a stray heading behind. `extra` appends rows
|
|
1360
|
+
* that are not Host fields (e.g. a credential) to one group's list.
|
|
1361
|
+
*/
|
|
1362
|
+
function FieldSections(props) {
|
|
1363
|
+
const { form, groups, extra = {}, footnotes = {} } = props;
|
|
1364
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: groups.map((group) => {
|
|
1365
|
+
const fields = form.fields.filter((field) => field.group === group);
|
|
1366
|
+
const more = extra[group];
|
|
1367
|
+
if (fields.length === 0 && more === void 0) return null;
|
|
1368
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
|
|
1369
|
+
title: FIELD_GROUP_LABELS[group],
|
|
1370
|
+
footnote: footnotes[group],
|
|
1371
|
+
children: [COLLAPSED_FIELD_GROUPS.includes(group) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsedGroup, {
|
|
1372
|
+
form,
|
|
1373
|
+
fields
|
|
1374
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupRows, {
|
|
1375
|
+
form,
|
|
1376
|
+
fields
|
|
1377
|
+
}), more]
|
|
1378
|
+
}, group);
|
|
1379
|
+
}) });
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Sticky bar shown only while the draft has unsaved keys. Saving submits the
|
|
1383
|
+
* whole draft (every tab); discarding drops it.
|
|
1384
|
+
*/
|
|
1149
1385
|
function SaveBar(props) {
|
|
1150
|
-
const {
|
|
1151
|
-
|
|
1152
|
-
const
|
|
1153
|
-
const dirty = form.dirtyCount > 0;
|
|
1154
|
-
const disabled = !dirty || form.busy !== null || form.locked;
|
|
1386
|
+
const { dirtyCount, restartCount, busy, locked, onSave, onDiscard } = props;
|
|
1387
|
+
if (dirtyCount === 0) return null;
|
|
1388
|
+
const disabled = busy !== null || locked;
|
|
1155
1389
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1156
|
-
className: "dsh-channels-
|
|
1390
|
+
className: "dsh-channels-cfg__saveBar",
|
|
1391
|
+
role: "region",
|
|
1392
|
+
"aria-label": PANEL_COPY.save,
|
|
1157
1393
|
children: [
|
|
1394
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1395
|
+
className: "dsh-channels-cfg__saveMsg",
|
|
1396
|
+
children: [fillCopy(PANEL_COPY.dirtyCount, { n: dirtyCount }), restartCount === 0 ? "" : fillCopy(PANEL_COPY.dirtyRestart, { n: restartCount })]
|
|
1397
|
+
}),
|
|
1158
1398
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1159
1399
|
type: "button",
|
|
1160
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--
|
|
1161
|
-
onClick:
|
|
1400
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost",
|
|
1401
|
+
onClick: onDiscard,
|
|
1162
1402
|
disabled,
|
|
1163
|
-
children:
|
|
1403
|
+
children: PANEL_COPY.discard
|
|
1164
1404
|
}),
|
|
1165
1405
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1166
1406
|
type: "button",
|
|
1167
|
-
className: "dsh-channels-cfg__btn",
|
|
1168
|
-
onClick:
|
|
1169
|
-
disabled
|
|
1170
|
-
children: PANEL_COPY.
|
|
1171
|
-
}),
|
|
1172
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsh-channels-cfg__spacer" }),
|
|
1173
|
-
pageDirty > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1174
|
-
className: "dsh-channels-cfg__tag dsh-channels-cfg__tag--dirty",
|
|
1175
|
-
children: [
|
|
1176
|
-
PANEL_COPY.dirtyCount,
|
|
1177
|
-
" ",
|
|
1178
|
-
pageDirty
|
|
1179
|
-
]
|
|
1180
|
-
}) : dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1181
|
-
className: "dsh-channels-cfg__tag dsh-channels-cfg__tag--dirty",
|
|
1182
|
-
children: [
|
|
1183
|
-
PANEL_COPY.dirtyCount,
|
|
1184
|
-
" ",
|
|
1185
|
-
form.dirtyCount,
|
|
1186
|
-
"(其他标签页)"
|
|
1187
|
-
]
|
|
1188
|
-
}) : null,
|
|
1189
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1190
|
-
className: "dsh-channels-cfg__help",
|
|
1191
|
-
children: dirty ? PANEL_COPY.saveHintDirty : PANEL_COPY.saveHintClean
|
|
1407
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
1408
|
+
onClick: onSave,
|
|
1409
|
+
disabled,
|
|
1410
|
+
children: busy === "save" ? PANEL_COPY.saving : PANEL_COPY.save
|
|
1192
1411
|
})
|
|
1193
1412
|
]
|
|
1194
1413
|
});
|
|
1195
1414
|
}
|
|
1196
|
-
/**
|
|
1197
|
-
function TabHelp(props) {
|
|
1198
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1199
|
-
className: "dsh-channels-cfg__tabHelp",
|
|
1200
|
-
children: props.text
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
|
-
/** Transient page-level banner (save / credentials / reconnect / catalog). */
|
|
1415
|
+
/** Transient banner (save / credentials / reconnect / catalog / access). */
|
|
1204
1416
|
function NoticeBar(props) {
|
|
1205
|
-
const {
|
|
1417
|
+
const { kind, text } = props;
|
|
1206
1418
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1207
|
-
className: `dsh-channels-cfg__notice dsh-channels-cfg__notice--${
|
|
1208
|
-
role: "status",
|
|
1419
|
+
className: `dsh-channels-cfg__notice dsh-channels-cfg__notice--${kind}`,
|
|
1420
|
+
role: kind === "error" ? "alert" : "status",
|
|
1209
1421
|
"aria-live": "polite",
|
|
1210
|
-
children:
|
|
1422
|
+
children: text
|
|
1211
1423
|
});
|
|
1212
1424
|
}
|
|
1213
|
-
/** Muted
|
|
1214
|
-
function
|
|
1425
|
+
/** Muted row inside a list (empty or not-yet-loaded state). */
|
|
1426
|
+
function EmptyRow(props) {
|
|
1215
1427
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1216
|
-
className: "dsh-channels-
|
|
1428
|
+
className: "dsh-channels-cfg__emptyRow",
|
|
1217
1429
|
children: props.text
|
|
1218
1430
|
});
|
|
1219
1431
|
}
|
|
1220
|
-
/** Human label for the official SDK's WebSocket lifecycle state. */
|
|
1221
|
-
function connectionStateLabel(state) {
|
|
1222
|
-
switch (state) {
|
|
1223
|
-
case "connected": return {
|
|
1224
|
-
text: PANEL_COPY.phaseConnected,
|
|
1225
|
-
tone: "ok"
|
|
1226
|
-
};
|
|
1227
|
-
case "connecting": return {
|
|
1228
|
-
text: PANEL_COPY.phaseConnecting,
|
|
1229
|
-
tone: "warn"
|
|
1230
|
-
};
|
|
1231
|
-
case "reconnecting": return {
|
|
1232
|
-
text: PANEL_COPY.phaseConnecting,
|
|
1233
|
-
tone: "warn"
|
|
1234
|
-
};
|
|
1235
|
-
case "failed": return {
|
|
1236
|
-
text: PANEL_COPY.phaseError,
|
|
1237
|
-
tone: "bad"
|
|
1238
|
-
};
|
|
1239
|
-
case "idle": return {
|
|
1240
|
-
text: PANEL_COPY.phaseIdle,
|
|
1241
|
-
tone: "muted"
|
|
1242
|
-
};
|
|
1243
|
-
default: return {
|
|
1244
|
-
text: state ?? PANEL_COPY.unknownValue,
|
|
1245
|
-
tone: "muted"
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
1432
|
//#endregion
|
|
1250
|
-
//#region src/client/panel/
|
|
1433
|
+
//#region src/client/panel/whitelist.tsx
|
|
1251
1434
|
/**
|
|
1252
|
-
*
|
|
1435
|
+
* 白名单列表:群白名单、用户白名单与知识库白名单共用的条目行、添加表单
|
|
1436
|
+
* (点「+ 添加」才展开)与待处理申请行。
|
|
1253
1437
|
*
|
|
1254
|
-
*
|
|
1255
|
-
* 申请列表是「未知」而不是「空」,因此只渲染连接提示并禁用全部写操作。
|
|
1256
|
-
* 管理员与准入模式由同 tab 上方「准入与授权」字段表单编辑,这里不再重复。
|
|
1257
|
-
*
|
|
1258
|
-
* @module dsh-channels/client/panel/access-card
|
|
1438
|
+
* @module dsh-channels/client/panel/whitelist
|
|
1259
1439
|
*/
|
|
1260
|
-
/** Local `
|
|
1440
|
+
/** Local `MM-DD HH:mm` rendering for authorization timestamps. */
|
|
1261
1441
|
function formatAccessDate(ms) {
|
|
1262
1442
|
if (!Number.isFinite(ms) || ms <= 0) return PANEL_COPY.unknownValue;
|
|
1263
1443
|
const date = new Date(ms);
|
|
1264
1444
|
const pad = (value) => String(value).padStart(2, "0");
|
|
1265
|
-
return `${
|
|
1445
|
+
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
1266
1446
|
}
|
|
1267
1447
|
/** Which authorization entry produced one grant. */
|
|
1268
|
-
function
|
|
1448
|
+
function sourceLabel(source) {
|
|
1269
1449
|
switch (source) {
|
|
1270
1450
|
case "command": return PANEL_COPY.accessSourceCommand;
|
|
1271
1451
|
case "card": return PANEL_COPY.accessSourceCard;
|
|
1272
1452
|
default: return PANEL_COPY.accessSourcePanel;
|
|
1273
1453
|
}
|
|
1274
1454
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
const requests = access.requests;
|
|
1282
|
-
const working = busy === "access";
|
|
1283
|
-
const disabled = busy !== null || locked || !access.loaded;
|
|
1284
|
-
const submitGroup = async () => {
|
|
1285
|
-
if (groupChatId.trim() === "") return;
|
|
1286
|
-
if (await onAddGroup(groupChatId, groupName)) {
|
|
1287
|
-
setGroupChatId("");
|
|
1288
|
-
setGroupName("");
|
|
1289
|
-
}
|
|
1290
|
-
};
|
|
1291
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1292
|
-
className: "dsh-channels-cfg__card",
|
|
1293
|
-
children: [
|
|
1294
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1295
|
-
className: "dsh-channels-cfg__cardHead",
|
|
1296
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1297
|
-
className: "dsh-channels-cfg__cardTitle",
|
|
1298
|
-
children: PANEL_COPY.accessCard
|
|
1299
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1300
|
-
className: "dsh-channels-cfg__cardMeta",
|
|
1301
|
-
children: accessModeLabel(panel, access.mode)
|
|
1302
|
-
})]
|
|
1303
|
-
}),
|
|
1304
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1305
|
-
className: "dsh-channels-cfg__help",
|
|
1306
|
-
children: PANEL_COPY.accessCardHelp
|
|
1307
|
-
}),
|
|
1308
|
-
access.mode === "open" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1309
|
-
className: "dsh-channels-cfg__banner",
|
|
1310
|
-
children: PANEL_COPY.accessModeOpenWarning
|
|
1311
|
-
}) : null,
|
|
1312
|
-
access.loaded && access.mode === "guarded" && !access.admin.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1313
|
-
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--error",
|
|
1314
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: PANEL_COPY.accessAdminMissingWarning }), access.admin.ownerLookupError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: access.admin.ownerLookupError })]
|
|
1315
|
-
}) : null,
|
|
1316
|
-
access.mode === "guarded" && access.admin.source === "owner" && access.admin.userId !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1317
|
-
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--ok",
|
|
1318
|
-
children: [PANEL_COPY.accessAdminOwnerNotice, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1319
|
-
className: "dsh-channels-cfg__mono",
|
|
1320
|
-
children: access.admin.userId
|
|
1321
|
-
})]
|
|
1322
|
-
}) : null,
|
|
1323
|
-
!access.loaded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, { text: PANEL_COPY.accessNotLoaded }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1324
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1325
|
-
className: "dsh-channels-cfg__accessSection",
|
|
1326
|
-
children: [
|
|
1327
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1328
|
-
className: "dsh-channels-cfg__labelRow",
|
|
1329
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1330
|
-
className: "dsh-channels-cfg__label",
|
|
1331
|
-
children: PANEL_COPY.accessGroupsTitle
|
|
1332
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1333
|
-
className: "dsh-channels-cfg__cardMeta",
|
|
1334
|
-
children: [groups.length, " 个"]
|
|
1335
|
-
})]
|
|
1336
|
-
}),
|
|
1337
|
-
groups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1338
|
-
className: "dsh-channels-cfg__help",
|
|
1339
|
-
children: PANEL_COPY.accessGroupsEmpty
|
|
1340
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
1341
|
-
className: "dsh-channels-cfg__accessList",
|
|
1342
|
-
children: groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1343
|
-
className: "dsh-channels-cfg__accessRow",
|
|
1344
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1345
|
-
className: "dsh-channels-cfg__accessMain",
|
|
1346
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: group.name ?? group.chatId }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1347
|
-
className: "dsh-channels-cfg__accessMeta",
|
|
1348
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1349
|
-
className: "dsh-channels-cfg__mono",
|
|
1350
|
-
children: group.chatId
|
|
1351
|
-
}), ` · ${accessSourceLabel(group.source)} · ${formatAccessDate(group.addedAt)}`]
|
|
1352
|
-
})]
|
|
1353
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1354
|
-
type: "button",
|
|
1355
|
-
className: "dsh-channels-cfg__btn",
|
|
1356
|
-
disabled,
|
|
1357
|
-
onClick: () => onRemoveGroup(group.chatId),
|
|
1358
|
-
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessRemove
|
|
1359
|
-
})]
|
|
1360
|
-
}, group.chatId))
|
|
1361
|
-
}),
|
|
1362
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1363
|
-
className: "dsh-channels-cfg__accessAdd",
|
|
1364
|
-
children: [
|
|
1365
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1366
|
-
type: "text",
|
|
1367
|
-
className: "dsh-channels-cfg__input dsh-channels-cfg__accessInput",
|
|
1368
|
-
value: groupChatId,
|
|
1369
|
-
placeholder: PANEL_COPY.accessGroupChatIdPlaceholder,
|
|
1370
|
-
"aria-label": PANEL_COPY.accessGroupChatIdPlaceholder,
|
|
1371
|
-
disabled,
|
|
1372
|
-
onChange: (event) => setGroupChatId(event.target.value)
|
|
1373
|
-
}),
|
|
1374
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1375
|
-
type: "text",
|
|
1376
|
-
className: "dsh-channels-cfg__input dsh-channels-cfg__accessInput",
|
|
1377
|
-
value: groupName,
|
|
1378
|
-
placeholder: PANEL_COPY.accessGroupNamePlaceholder,
|
|
1379
|
-
"aria-label": PANEL_COPY.accessGroupNamePlaceholder,
|
|
1380
|
-
disabled,
|
|
1381
|
-
onChange: (event) => setGroupName(event.target.value)
|
|
1382
|
-
}),
|
|
1383
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1384
|
-
type: "button",
|
|
1385
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
1386
|
-
disabled: disabled || groupChatId.trim() === "",
|
|
1387
|
-
onClick: () => {
|
|
1388
|
-
submitGroup();
|
|
1389
|
-
},
|
|
1390
|
-
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessAddGroup
|
|
1391
|
-
})
|
|
1392
|
-
]
|
|
1393
|
-
})
|
|
1394
|
-
]
|
|
1395
|
-
}),
|
|
1396
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserWhitelist, {
|
|
1397
|
-
title: PANEL_COPY.accessUsersTitle,
|
|
1398
|
-
empty: PANEL_COPY.accessUsersEmpty,
|
|
1399
|
-
addLabel: PANEL_COPY.accessAddUser,
|
|
1400
|
-
users: access.users,
|
|
1401
|
-
disabled,
|
|
1402
|
-
working,
|
|
1403
|
-
onAdd: onAddUser,
|
|
1404
|
-
onRemove: onRemoveUser
|
|
1405
|
-
}),
|
|
1406
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1407
|
-
className: "dsh-channels-cfg__accessSection",
|
|
1408
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1409
|
-
className: "dsh-channels-cfg__labelRow",
|
|
1410
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1411
|
-
className: "dsh-channels-cfg__label",
|
|
1412
|
-
children: PANEL_COPY.accessRequestsTitle
|
|
1413
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1414
|
-
className: "dsh-channels-cfg__cardMeta",
|
|
1415
|
-
children: [requests.length, " 条"]
|
|
1416
|
-
})]
|
|
1417
|
-
}), requests.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1418
|
-
className: "dsh-channels-cfg__help",
|
|
1419
|
-
children: PANEL_COPY.accessRequestsEmpty
|
|
1420
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
1421
|
-
className: "dsh-channels-cfg__accessList",
|
|
1422
|
-
children: requests.map((request) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1423
|
-
className: "dsh-channels-cfg__accessRow",
|
|
1424
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1425
|
-
className: "dsh-channels-cfg__accessMain",
|
|
1426
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1427
|
-
request.kind === "group" ? PANEL_COPY.accessRequestGroup : PANEL_COPY.accessRequestUser,
|
|
1428
|
-
":",
|
|
1429
|
-
request.name ?? request.targetId
|
|
1430
|
-
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1431
|
-
className: "dsh-channels-cfg__accessMeta",
|
|
1432
|
-
children: [
|
|
1433
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1434
|
-
className: "dsh-channels-cfg__mono",
|
|
1435
|
-
children: request.targetId
|
|
1436
|
-
}),
|
|
1437
|
-
` · ${formatAccessDate(request.createdAt)} · ${PANEL_COPY.accessRequestExpires} ${formatAccessDate(request.expiresAt)}`,
|
|
1438
|
-
request.kind === "user" && request.requester !== void 0 ? ` · ${PANEL_COPY.accessRequestRequester} ${request.requester}` : "",
|
|
1439
|
-
request.kind === "user" && request.groupName !== void 0 ? ` · ${request.groupName}` : request.kind === "user" && request.chatId !== void 0 ? ` · ${request.chatId}` : ""
|
|
1440
|
-
]
|
|
1441
|
-
})]
|
|
1442
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1443
|
-
className: "dsh-channels-cfg__accessActions",
|
|
1444
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1445
|
-
type: "button",
|
|
1446
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
1447
|
-
disabled,
|
|
1448
|
-
onClick: () => onResolveRequest(request.token, "allow"),
|
|
1449
|
-
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessAllow
|
|
1450
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1451
|
-
type: "button",
|
|
1452
|
-
className: "dsh-channels-cfg__btn",
|
|
1453
|
-
disabled,
|
|
1454
|
-
onClick: () => onResolveRequest(request.token, "deny"),
|
|
1455
|
-
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessDeny
|
|
1456
|
-
})]
|
|
1457
|
-
})]
|
|
1458
|
-
}, request.token))
|
|
1459
|
-
})]
|
|
1460
|
-
})
|
|
1461
|
-
] }),
|
|
1462
|
-
notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1463
|
-
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--ok",
|
|
1464
|
-
role: "status",
|
|
1465
|
-
"aria-live": "polite",
|
|
1466
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: notice })
|
|
1467
|
-
}),
|
|
1468
|
-
error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1469
|
-
className: "dsh-channels-cfg__fieldError",
|
|
1470
|
-
role: "alert",
|
|
1471
|
-
children: error
|
|
1472
|
-
}),
|
|
1473
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1474
|
-
className: "dsh-channels-cfg__help",
|
|
1475
|
-
children: PANEL_COPY.accessHints
|
|
1476
|
-
}),
|
|
1477
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1478
|
-
className: "dsh-channels-cfg__help",
|
|
1479
|
-
children: PANEL_COPY.accessChatIdHint
|
|
1480
|
-
})
|
|
1481
|
-
]
|
|
1455
|
+
/** Round initial badge in front of a whitelist entry. */
|
|
1456
|
+
function Avatar(props) {
|
|
1457
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1458
|
+
className: "dsh-channels-cfg__avatar",
|
|
1459
|
+
"aria-hidden": "true",
|
|
1460
|
+
children: Array.from(props.text)[0] ?? "?"
|
|
1482
1461
|
});
|
|
1483
1462
|
}
|
|
1484
|
-
/**
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1463
|
+
/**
|
|
1464
|
+
* One titled whitelist with remove buttons and a collapsed add form.
|
|
1465
|
+
* `onAdd` resolves true when the entry was added (the form then closes).
|
|
1466
|
+
*/
|
|
1467
|
+
function Whitelist(props) {
|
|
1468
|
+
const { title, countLabel, entries, avatar, empty, loaded, addLabel, idPlaceholder, namePlaceholder, footnote, disabled, working, onAdd, onRemove } = props;
|
|
1469
|
+
const [adding, setAdding] = (0, react.useState)(false);
|
|
1470
|
+
const [id, setId] = (0, react.useState)("");
|
|
1471
|
+
const [name, setName] = (0, react.useState)("");
|
|
1472
|
+
const submit = async () => {
|
|
1473
|
+
if (id.trim() === "") return;
|
|
1474
|
+
if (await onAdd(id, name)) {
|
|
1475
|
+
setId("");
|
|
1476
|
+
setName("");
|
|
1477
|
+
setAdding(false);
|
|
1494
1478
|
}
|
|
1495
1479
|
};
|
|
1496
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
1497
|
-
|
|
1480
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
|
|
1481
|
+
title: loaded ? `${title} · ${countLabel ?? entries.length}` : title,
|
|
1482
|
+
footnote,
|
|
1498
1483
|
children: [
|
|
1499
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
children: user.userId
|
|
1523
|
-
}), ` · ${accessSourceLabel(user.source)} · ${formatAccessDate(user.addedAt)}`]
|
|
1524
|
-
})]
|
|
1525
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1526
|
-
type: "button",
|
|
1527
|
-
className: "dsh-channels-cfg__btn",
|
|
1528
|
-
disabled,
|
|
1529
|
-
onClick: () => onRemove(user.userId),
|
|
1530
|
-
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessRemove
|
|
1531
|
-
})]
|
|
1532
|
-
}, user.userId))
|
|
1533
|
-
}),
|
|
1534
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1535
|
-
className: "dsh-channels-cfg__accessAdd",
|
|
1484
|
+
!loaded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyRow, { text: PANEL_COPY.accessNotLoaded }) : null,
|
|
1485
|
+
loaded && entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyRow, { text: empty }) : null,
|
|
1486
|
+
entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
1487
|
+
leading: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Avatar, { text: avatar ?? entry.name ?? entry.id }),
|
|
1488
|
+
label: entry.name ?? entry.id,
|
|
1489
|
+
summary: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1490
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1491
|
+
className: "dsh-channels-cfg__mono",
|
|
1492
|
+
children: entry.id
|
|
1493
|
+
}),
|
|
1494
|
+
" · ",
|
|
1495
|
+
sourceLabel(entry.source)
|
|
1496
|
+
] }),
|
|
1497
|
+
control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1498
|
+
type: "button",
|
|
1499
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--remove",
|
|
1500
|
+
disabled,
|
|
1501
|
+
onClick: () => onRemove(entry.id),
|
|
1502
|
+
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessRemove
|
|
1503
|
+
})
|
|
1504
|
+
}, entry.id)),
|
|
1505
|
+
!loaded ? null : adding ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1506
|
+
className: "dsh-channels-cfg__addRow",
|
|
1536
1507
|
children: [
|
|
1537
1508
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1538
1509
|
type: "text",
|
|
1539
|
-
className: "dsh-channels-cfg__input
|
|
1540
|
-
value:
|
|
1541
|
-
placeholder:
|
|
1542
|
-
"aria-label":
|
|
1510
|
+
className: "dsh-channels-cfg__input",
|
|
1511
|
+
value: id,
|
|
1512
|
+
placeholder: idPlaceholder,
|
|
1513
|
+
"aria-label": idPlaceholder,
|
|
1543
1514
|
disabled,
|
|
1544
|
-
onChange: (event) =>
|
|
1515
|
+
onChange: (event) => setId(event.target.value)
|
|
1545
1516
|
}),
|
|
1546
1517
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1547
1518
|
type: "text",
|
|
1548
|
-
className: "dsh-channels-cfg__input
|
|
1549
|
-
value:
|
|
1550
|
-
placeholder:
|
|
1551
|
-
"aria-label":
|
|
1519
|
+
className: "dsh-channels-cfg__input",
|
|
1520
|
+
value: name,
|
|
1521
|
+
placeholder: namePlaceholder,
|
|
1522
|
+
"aria-label": namePlaceholder,
|
|
1552
1523
|
disabled,
|
|
1553
|
-
onChange: (event) =>
|
|
1524
|
+
onChange: (event) => setName(event.target.value)
|
|
1554
1525
|
}),
|
|
1555
1526
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1556
1527
|
type: "button",
|
|
1557
1528
|
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
1558
|
-
disabled: disabled ||
|
|
1529
|
+
disabled: disabled || id.trim() === "",
|
|
1559
1530
|
onClick: () => {
|
|
1560
|
-
|
|
1531
|
+
submit();
|
|
1561
1532
|
},
|
|
1562
|
-
children: working ? PANEL_COPY.accessWorking :
|
|
1533
|
+
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessAdd
|
|
1534
|
+
}),
|
|
1535
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1536
|
+
type: "button",
|
|
1537
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost",
|
|
1538
|
+
onClick: () => setAdding(false),
|
|
1539
|
+
children: PANEL_COPY.cancel
|
|
1563
1540
|
})
|
|
1564
1541
|
]
|
|
1542
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1543
|
+
className: "dsh-channels-cfg__addRow",
|
|
1544
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1545
|
+
type: "button",
|
|
1546
|
+
className: "dsh-channels-cfg__link",
|
|
1547
|
+
disabled,
|
|
1548
|
+
onClick: () => setAdding(true),
|
|
1549
|
+
children: ["+ ", addLabel]
|
|
1550
|
+
})
|
|
1565
1551
|
})
|
|
1566
1552
|
]
|
|
1567
1553
|
});
|
|
1568
1554
|
}
|
|
1555
|
+
/** Pending authorization requests, settled with the same resolver as the DM card. */
|
|
1556
|
+
function RequestList(props) {
|
|
1557
|
+
const { requests, disabled, working, onResolve } = props;
|
|
1558
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Section, {
|
|
1559
|
+
title: `${PANEL_COPY.accessRequestsTitle} · ${requests.length}`,
|
|
1560
|
+
tone: "warn",
|
|
1561
|
+
children: requests.map((request) => {
|
|
1562
|
+
const target = request.name ?? request.targetId;
|
|
1563
|
+
const where = request.kind === "user" && (request.groupName ?? request.chatId) !== void 0 ? fillCopy(PANEL_COPY.accessRequestIn, { group: request.groupName ?? request.chatId ?? "" }) : "";
|
|
1564
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
1565
|
+
leading: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Avatar, { text: target }),
|
|
1566
|
+
label: `${target} ${where}${request.kind === "group" ? PANEL_COPY.accessRequestGroup : PANEL_COPY.accessRequestUser}`,
|
|
1567
|
+
summary: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1568
|
+
className: "dsh-channels-cfg__mono",
|
|
1569
|
+
children: request.targetId
|
|
1570
|
+
}), ` · ${formatAccessDate(request.createdAt)} · ${fillCopy(PANEL_COPY.accessRequestExpires, { date: formatAccessDate(request.expiresAt) })}`] }),
|
|
1571
|
+
control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1572
|
+
className: "dsh-channels-cfg__inputRow",
|
|
1573
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1574
|
+
type: "button",
|
|
1575
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost",
|
|
1576
|
+
disabled,
|
|
1577
|
+
onClick: () => onResolve(request.token, "deny"),
|
|
1578
|
+
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessDeny
|
|
1579
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1580
|
+
type: "button",
|
|
1581
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
1582
|
+
disabled,
|
|
1583
|
+
onClick: () => onResolve(request.token, "allow"),
|
|
1584
|
+
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessAllow
|
|
1585
|
+
})]
|
|
1586
|
+
})
|
|
1587
|
+
}, request.token);
|
|
1588
|
+
})
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1569
1591
|
//#endregion
|
|
1570
1592
|
//#region src/client/panel/access-tab.tsx
|
|
1571
1593
|
/**
|
|
1572
|
-
* 「访问授权」tab
|
|
1573
|
-
*
|
|
1574
|
-
*
|
|
1594
|
+
* 「访问授权」tab:待处理申请(有才显示,置顶)、准入字段(调试模式 /
|
|
1595
|
+
* 管理员 / 申请有效期)与群、用户白名单。字段保存走全局 draft,白名单增删
|
|
1596
|
+
* 与申请处理是独立即时 mutation。
|
|
1597
|
+
*
|
|
1598
|
+
* `access.loaded === false`(飞书未连接、插件 storage 域未打开)时白名单与
|
|
1599
|
+
* 申请列表是「未知」而不是「空」,因此只渲染连接提示并禁用全部写操作。
|
|
1575
1600
|
*
|
|
1576
1601
|
* @module dsh-channels/client/panel/access-tab
|
|
1577
1602
|
*/
|
|
1578
1603
|
function AccessTab(props) {
|
|
1579
1604
|
const { form, accessNotice, accessError, onAddGroup, onRemoveGroup, onAddUser, onRemoveUser, onResolveRequest } = props;
|
|
1605
|
+
const { access } = form.panel;
|
|
1606
|
+
const working = form.busy === "access";
|
|
1607
|
+
const disabled = form.busy !== null || form.locked || !access.loaded;
|
|
1608
|
+
const ownerAdmin = access.mode === "guarded" && access.admin.source === "owner" ? access.admin.userId : void 0;
|
|
1609
|
+
const fields = ownerAdmin === void 0 ? form.fields : form.fields.map((field) => field.key === "adminUserId" ? {
|
|
1610
|
+
...field,
|
|
1611
|
+
summary: fillCopy(PANEL_COPY.accessAdminOwnerSummary, { id: ownerAdmin })
|
|
1612
|
+
} : field);
|
|
1580
1613
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1581
1614
|
className: "dsh-channels-cfg__tabBody",
|
|
1582
1615
|
children: [
|
|
1583
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1584
|
-
|
|
1585
|
-
|
|
1616
|
+
accessNotice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NoticeBar, {
|
|
1617
|
+
kind: "ok",
|
|
1618
|
+
text: accessNotice
|
|
1619
|
+
}),
|
|
1620
|
+
accessError === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NoticeBar, {
|
|
1621
|
+
kind: "error",
|
|
1622
|
+
text: accessError
|
|
1623
|
+
}),
|
|
1624
|
+
access.loaded && access.mode === "guarded" && !access.admin.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NoticeBar, {
|
|
1625
|
+
kind: "error",
|
|
1626
|
+
text: access.admin.ownerLookupError === void 0 ? PANEL_COPY.accessAdminMissingWarning : `${PANEL_COPY.accessAdminMissingWarning}(${access.admin.ownerLookupError})`
|
|
1627
|
+
}) : null,
|
|
1628
|
+
access.loaded && access.requests.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RequestList, {
|
|
1629
|
+
requests: access.requests,
|
|
1630
|
+
disabled,
|
|
1631
|
+
working,
|
|
1632
|
+
onResolve: onResolveRequest
|
|
1633
|
+
}) : null,
|
|
1634
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldSections, {
|
|
1635
|
+
form: {
|
|
1636
|
+
...form,
|
|
1637
|
+
fields
|
|
1638
|
+
},
|
|
1586
1639
|
groups: TAB_FIELD_GROUPS.access
|
|
1587
1640
|
}),
|
|
1588
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1589
|
-
|
|
1590
|
-
|
|
1641
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Whitelist, {
|
|
1642
|
+
title: PANEL_COPY.accessGroupsTitle,
|
|
1643
|
+
entries: access.groups.map((group) => ({
|
|
1644
|
+
id: group.chatId,
|
|
1645
|
+
name: group.name,
|
|
1646
|
+
source: group.source
|
|
1647
|
+
})),
|
|
1648
|
+
avatar: "群",
|
|
1649
|
+
empty: PANEL_COPY.accessGroupsEmpty,
|
|
1650
|
+
loaded: access.loaded,
|
|
1651
|
+
addLabel: PANEL_COPY.accessAddGroup,
|
|
1652
|
+
idPlaceholder: PANEL_COPY.accessGroupChatIdPlaceholder,
|
|
1653
|
+
namePlaceholder: PANEL_COPY.accessGroupNamePlaceholder,
|
|
1654
|
+
disabled,
|
|
1655
|
+
working,
|
|
1656
|
+
onAdd: onAddGroup,
|
|
1657
|
+
onRemove: onRemoveGroup
|
|
1591
1658
|
}),
|
|
1592
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1659
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Whitelist, {
|
|
1660
|
+
title: PANEL_COPY.accessUsersTitle,
|
|
1661
|
+
entries: access.users.map((user) => ({
|
|
1662
|
+
id: user.userId,
|
|
1663
|
+
name: user.name,
|
|
1664
|
+
source: user.source
|
|
1665
|
+
})),
|
|
1666
|
+
empty: PANEL_COPY.accessUsersEmpty,
|
|
1667
|
+
loaded: access.loaded,
|
|
1668
|
+
addLabel: PANEL_COPY.accessAddUser,
|
|
1669
|
+
idPlaceholder: PANEL_COPY.accessUserIdPlaceholder,
|
|
1670
|
+
namePlaceholder: PANEL_COPY.accessUserNamePlaceholder,
|
|
1671
|
+
disabled,
|
|
1672
|
+
working,
|
|
1673
|
+
onAdd: onAddUser,
|
|
1674
|
+
onRemove: onRemoveUser
|
|
1603
1675
|
})
|
|
1604
1676
|
]
|
|
1605
1677
|
});
|
|
1606
1678
|
}
|
|
1607
1679
|
//#endregion
|
|
1608
|
-
//#region src/client/panel/
|
|
1680
|
+
//#region src/client/panel/credentials-card.tsx
|
|
1609
1681
|
/**
|
|
1610
|
-
*
|
|
1611
|
-
*
|
|
1682
|
+
* 凭据行:值经 Host 的 DSH 凭据服务写入本机凭据库,面板只显示「已配置 /
|
|
1683
|
+
* 未配置」,两个方向都不回显已存值。已配置时只显示「更换 / 移除」,点「更换」
|
|
1684
|
+
* 才出现输入框。保存动作由页面外壳持有(凭据保存独立于配置 draft)。
|
|
1612
1685
|
*
|
|
1613
|
-
* @module dsh-channels/client/panel/
|
|
1686
|
+
* @module dsh-channels/client/panel/credentials-card
|
|
1614
1687
|
*/
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1688
|
+
/** One credential slot as a setting row with its own input and actions. */
|
|
1689
|
+
function CredentialRow(props) {
|
|
1690
|
+
const { label, summary, state, value, secret, placeholder, issue, busy, disabled, onChange, onSave, onClear } = props;
|
|
1691
|
+
const [editing, setEditing] = (0, react.useState)(false);
|
|
1692
|
+
const previousBusy = (0, react.useRef)(busy);
|
|
1693
|
+
(0, react.useEffect)(() => {
|
|
1694
|
+
if (previousBusy.current === "credentials" && busy === null) setEditing(false);
|
|
1695
|
+
previousBusy.current = busy;
|
|
1696
|
+
}, [busy]);
|
|
1697
|
+
const showInput = !state.configured || editing || value !== "";
|
|
1698
|
+
const source = `${PANEL_COPY.credentialStore} · ${state.ref}${state.source === void 0 ? "" : ` · ${state.source}`}`;
|
|
1699
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
1700
|
+
label,
|
|
1701
|
+
tags: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pill, {
|
|
1702
|
+
tone: state.configured ? "ok" : "muted",
|
|
1703
|
+
children: state.configured ? PANEL_COPY.credentialConfigured : PANEL_COPY.credentialMissing
|
|
1704
|
+
}),
|
|
1705
|
+
summary: summary === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1706
|
+
className: "dsh-channels-cfg__mono",
|
|
1707
|
+
children: source
|
|
1708
|
+
}) : summary,
|
|
1709
|
+
error: issue?.message ?? (state.writable ? void 0 : PANEL_COPY.credentialReadOnly),
|
|
1710
|
+
control: showInput ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1711
|
+
className: "dsh-channels-cfg__inputRow",
|
|
1712
|
+
children: [
|
|
1713
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1714
|
+
type: secret ? "password" : "text",
|
|
1715
|
+
className: `dsh-channels-cfg__input dsh-channels-cfg__input--text${issue === void 0 ? "" : " dsh-channels-cfg__input--invalid"}`,
|
|
1716
|
+
value,
|
|
1717
|
+
placeholder,
|
|
1718
|
+
autoComplete: "new-password",
|
|
1719
|
+
"aria-label": label,
|
|
1720
|
+
disabled,
|
|
1721
|
+
onChange: (event) => onChange(event.target.value)
|
|
1722
|
+
}),
|
|
1723
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1724
|
+
type: "button",
|
|
1725
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
1726
|
+
disabled: disabled || value.trim() === "",
|
|
1727
|
+
onClick: onSave,
|
|
1728
|
+
children: busy === "credentials" ? PANEL_COPY.savingCredentials : PANEL_COPY.saveCredentials
|
|
1729
|
+
}),
|
|
1730
|
+
state.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1731
|
+
type: "button",
|
|
1732
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost",
|
|
1733
|
+
disabled: busy !== null,
|
|
1734
|
+
onClick: () => {
|
|
1735
|
+
setEditing(false);
|
|
1736
|
+
onChange("");
|
|
1737
|
+
},
|
|
1738
|
+
children: PANEL_COPY.cancel
|
|
1739
|
+
}) : null
|
|
1740
|
+
]
|
|
1741
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1742
|
+
className: "dsh-channels-cfg__inputRow",
|
|
1743
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1744
|
+
type: "button",
|
|
1745
|
+
className: "dsh-channels-cfg__btn",
|
|
1746
|
+
disabled,
|
|
1747
|
+
onClick: () => setEditing(true),
|
|
1748
|
+
children: PANEL_COPY.replaceCredential
|
|
1749
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1750
|
+
type: "button",
|
|
1751
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--remove",
|
|
1752
|
+
disabled,
|
|
1753
|
+
onClick: onClear,
|
|
1754
|
+
children: busy === "credentials-clear" ? PANEL_COPY.removingCredential : PANEL_COPY.removeCredential
|
|
1755
|
+
})]
|
|
1756
|
+
})
|
|
1636
1757
|
});
|
|
1637
1758
|
}
|
|
1638
|
-
/**
|
|
1639
|
-
function
|
|
1640
|
-
const { panel,
|
|
1641
|
-
const {
|
|
1642
|
-
const
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
children: [
|
|
1673
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1674
|
-
className: "dsh-channels-cfg__kvKey",
|
|
1675
|
-
children: field.label
|
|
1676
|
-
}),
|
|
1677
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1678
|
-
className: "dsh-channels-cfg__kvValue",
|
|
1679
|
-
children: field.value
|
|
1680
|
-
}),
|
|
1681
|
-
field.help === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1682
|
-
className: "dsh-channels-cfg__help",
|
|
1683
|
-
children: field.help
|
|
1684
|
-
})
|
|
1685
|
-
]
|
|
1686
|
-
}, field.key)),
|
|
1687
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1688
|
-
className: "dsh-channels-cfg__kv",
|
|
1689
|
-
children: [
|
|
1690
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1691
|
-
className: "dsh-channels-cfg__kvKey",
|
|
1692
|
-
children: PANEL_COPY.overlayPath
|
|
1693
|
-
}),
|
|
1694
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1695
|
-
className: "dsh-channels-cfg__kvValue dsh-channels-cfg__mono",
|
|
1696
|
-
children: panel.status.overlayPath
|
|
1697
|
-
}),
|
|
1698
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1699
|
-
className: "dsh-channels-cfg__help",
|
|
1700
|
-
children: PANEL_COPY.overlayPathHelp
|
|
1701
|
-
})
|
|
1702
|
-
]
|
|
1703
|
-
}),
|
|
1704
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1705
|
-
className: "dsh-channels-cfg__kv",
|
|
1706
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1707
|
-
className: "dsh-channels-cfg__kvKey",
|
|
1708
|
-
children: PANEL_COPY.version
|
|
1709
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1710
|
-
className: "dsh-channels-cfg__kvValue dsh-channels-cfg__mono",
|
|
1711
|
-
children: panel.status.version
|
|
1712
|
-
})]
|
|
1713
|
-
})
|
|
1714
|
-
]
|
|
1715
|
-
}),
|
|
1716
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1717
|
-
className: "dsh-channels-cfg__actions",
|
|
1718
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1719
|
-
type: "button",
|
|
1720
|
-
className: "dsh-channels-cfg__btn",
|
|
1721
|
-
onClick: onClear,
|
|
1722
|
-
disabled: overridden === 0 || busy !== null || locked,
|
|
1723
|
-
children: busy === "clear" ? PANEL_COPY.clearing : PANEL_COPY.clear
|
|
1724
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1725
|
-
className: "dsh-channels-cfg__help",
|
|
1726
|
-
children: overridden === 0 ? PANEL_COPY.noOverrides : `${PANEL_COPY.overridden} ${overridden} 项`
|
|
1727
|
-
})]
|
|
1728
|
-
})
|
|
1729
|
-
]
|
|
1759
|
+
/** The Feishu application pair (App ID / App Secret). */
|
|
1760
|
+
function CredentialsSection(props) {
|
|
1761
|
+
const { panel, credentials } = props;
|
|
1762
|
+
const { appId, appSecret, issues, busy, locked, onAppIdChange, onAppSecretChange, onSave, onClear } = credentials;
|
|
1763
|
+
const appIdState = panel.config.credentials.appId;
|
|
1764
|
+
const appSecretState = panel.config.credentials.appSecret;
|
|
1765
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
|
|
1766
|
+
title: PANEL_COPY.credentialsTitle,
|
|
1767
|
+
footnote: PANEL_COPY.credentialsHint,
|
|
1768
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CredentialRow, {
|
|
1769
|
+
label: PANEL_COPY.appIdLabel,
|
|
1770
|
+
state: appIdState,
|
|
1771
|
+
value: appId,
|
|
1772
|
+
secret: false,
|
|
1773
|
+
placeholder: PANEL_COPY.appIdPlaceholder,
|
|
1774
|
+
issue: issues.find((item) => item.key === "appId"),
|
|
1775
|
+
busy,
|
|
1776
|
+
disabled: busy !== null || locked || !appIdState.writable,
|
|
1777
|
+
onChange: onAppIdChange,
|
|
1778
|
+
onSave,
|
|
1779
|
+
onClear: () => onClear("appId")
|
|
1780
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CredentialRow, {
|
|
1781
|
+
label: PANEL_COPY.appSecretLabel,
|
|
1782
|
+
state: appSecretState,
|
|
1783
|
+
value: appSecret,
|
|
1784
|
+
secret: true,
|
|
1785
|
+
placeholder: PANEL_COPY.appSecretPlaceholder,
|
|
1786
|
+
issue: issues.find((item) => item.key === "appSecret"),
|
|
1787
|
+
busy,
|
|
1788
|
+
disabled: busy !== null || locked || !appSecretState.writable,
|
|
1789
|
+
onChange: onAppSecretChange,
|
|
1790
|
+
onSave,
|
|
1791
|
+
onClear: () => onClear("appSecret")
|
|
1792
|
+
})]
|
|
1730
1793
|
});
|
|
1731
1794
|
}
|
|
1732
1795
|
//#endregion
|
|
1733
|
-
//#region src/client/panel/
|
|
1796
|
+
//#region src/client/panel/advanced-tab.tsx
|
|
1734
1797
|
/**
|
|
1735
|
-
*
|
|
1736
|
-
*
|
|
1737
|
-
* (凭据保存独立于配置 draft)。
|
|
1798
|
+
* 「高级」tab:日志分析接口(监听地址字段 + 访问令牌凭据行)、SDK 日志级别
|
|
1799
|
+
* 与 Web 覆盖文件(「恢复 profile 配置」删除整个覆盖层,不走 draft)。
|
|
1738
1800
|
*
|
|
1739
|
-
* @module dsh-channels/client/panel/
|
|
1801
|
+
* @module dsh-channels/client/panel/advanced-tab
|
|
1740
1802
|
*/
|
|
1741
|
-
function
|
|
1742
|
-
const {
|
|
1743
|
-
const {
|
|
1744
|
-
const
|
|
1745
|
-
const
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
placeholder,
|
|
1773
|
-
autoComplete: "new-password",
|
|
1774
|
-
"aria-label": label,
|
|
1775
|
-
disabled,
|
|
1776
|
-
onChange: (event) => onChange(event.target.value)
|
|
1777
|
-
}), state.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1778
|
-
type: "button",
|
|
1779
|
-
className: "dsh-channels-cfg__btn",
|
|
1780
|
-
disabled,
|
|
1781
|
-
onClick: () => onClear(field),
|
|
1782
|
-
children: busy === "credentials-clear" ? PANEL_COPY.removingCredential : PANEL_COPY.removeCredential
|
|
1783
|
-
}) : null]
|
|
1784
|
-
}),
|
|
1785
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1786
|
-
className: "dsh-channels-cfg__help",
|
|
1787
|
-
children: [
|
|
1788
|
-
PANEL_COPY.credentialsRef,
|
|
1789
|
-
":",
|
|
1790
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1791
|
-
className: "dsh-channels-cfg__mono",
|
|
1792
|
-
children: state.ref
|
|
1793
|
-
}),
|
|
1794
|
-
state.source === void 0 ? "" : ` · ${state.source}`
|
|
1795
|
-
]
|
|
1803
|
+
function AdvancedTab(props) {
|
|
1804
|
+
const { form, analyzeToken, onClear } = props;
|
|
1805
|
+
const { panel, busy, locked } = form;
|
|
1806
|
+
const tokenState = panel.config.credentials.analyzeToken;
|
|
1807
|
+
const overridden = panel.config.overriddenKeys.length;
|
|
1808
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1809
|
+
className: "dsh-channels-cfg__tabBody",
|
|
1810
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldSections, {
|
|
1811
|
+
form,
|
|
1812
|
+
groups: TAB_FIELD_GROUPS.advanced,
|
|
1813
|
+
extra: { analyze: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CredentialRow, {
|
|
1814
|
+
label: PANEL_COPY.analyzeTokenLabel,
|
|
1815
|
+
summary: PANEL_COPY.analyzeTokenSummary,
|
|
1816
|
+
state: tokenState,
|
|
1817
|
+
value: analyzeToken.value,
|
|
1818
|
+
secret: true,
|
|
1819
|
+
placeholder: PANEL_COPY.analyzeTokenPlaceholder,
|
|
1820
|
+
issue: analyzeToken.issues.find((item) => item.key === "analyzeToken"),
|
|
1821
|
+
busy: analyzeToken.busy,
|
|
1822
|
+
disabled: analyzeToken.busy !== null || analyzeToken.locked || !tokenState.writable,
|
|
1823
|
+
onChange: analyzeToken.onChange,
|
|
1824
|
+
onSave: analyzeToken.onSave,
|
|
1825
|
+
onClear: analyzeToken.onClear
|
|
1826
|
+
}) }
|
|
1827
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Section, {
|
|
1828
|
+
title: PANEL_COPY.overlayTitle,
|
|
1829
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
1830
|
+
label: PANEL_COPY.overlayPath,
|
|
1831
|
+
summary: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1832
|
+
className: "dsh-channels-cfg__mono",
|
|
1833
|
+
children: panel.status.overlayPath
|
|
1796
1834
|
}),
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1810
|
-
className: "dsh-channels-cfg__cardTitle",
|
|
1811
|
-
children: PANEL_COPY.credentialsCard
|
|
1812
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1813
|
-
className: `dsh-channels-cfg__badge dsh-channels-cfg__badge--${configured ? "ok" : "muted"}`,
|
|
1814
|
-
children: configured ? PANEL_COPY.configuredBadge : PANEL_COPY.unconfiguredBadge
|
|
1815
|
-
})]
|
|
1816
|
-
}),
|
|
1817
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1818
|
-
className: "dsh-channels-cfg__help",
|
|
1819
|
-
children: PANEL_COPY.credentialsCardHelp
|
|
1820
|
-
}),
|
|
1821
|
-
row("appId", PANEL_COPY.appIdLabel, appIdState, appId, "text", PANEL_COPY.appIdPlaceholder, onAppIdChange),
|
|
1822
|
-
row("appSecret", PANEL_COPY.appSecretLabel, appSecretState, appSecret, "password", PANEL_COPY.appSecretPlaceholder, onAppSecretChange),
|
|
1823
|
-
writable ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1824
|
-
className: "dsh-channels-cfg__fieldError",
|
|
1825
|
-
children: PANEL_COPY.credentialReadOnly
|
|
1826
|
-
}),
|
|
1827
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1828
|
-
className: "dsh-channels-cfg__footer",
|
|
1829
|
-
children: [
|
|
1830
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1831
|
-
type: "button",
|
|
1832
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
1833
|
-
onClick: onSave,
|
|
1834
|
-
disabled: disabled || !pending,
|
|
1835
|
-
children: busy === "credentials" ? PANEL_COPY.savingCredentials : PANEL_COPY.saveCredentials
|
|
1836
|
-
}),
|
|
1837
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsh-channels-cfg__spacer" }),
|
|
1838
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1839
|
-
className: "dsh-channels-cfg__help",
|
|
1840
|
-
children: PANEL_COPY.credentialsPendingHint
|
|
1841
|
-
})
|
|
1842
|
-
]
|
|
1835
|
+
details: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1836
|
+
PANEL_COPY.overlayPathHelp,
|
|
1837
|
+
" ",
|
|
1838
|
+
overridden === 0 ? PANEL_COPY.noOverrides : fillCopy(PANEL_COPY.overlayCount, { n: overridden })
|
|
1839
|
+
] }),
|
|
1840
|
+
control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1841
|
+
type: "button",
|
|
1842
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--danger",
|
|
1843
|
+
onClick: onClear,
|
|
1844
|
+
disabled: overridden === 0 || busy !== null || locked,
|
|
1845
|
+
children: busy === "clear" ? PANEL_COPY.clearing : PANEL_COPY.clear
|
|
1846
|
+
})
|
|
1843
1847
|
})
|
|
1844
|
-
]
|
|
1848
|
+
})]
|
|
1845
1849
|
});
|
|
1846
1850
|
}
|
|
1847
1851
|
//#endregion
|
|
1848
1852
|
//#region src/client/panel/connection-tab.tsx
|
|
1849
1853
|
/**
|
|
1850
|
-
* 「连接」tab:按 Host
|
|
1854
|
+
* 「连接」tab:按 Host 派生的生命周期阶段渲染。
|
|
1851
1855
|
*
|
|
1852
|
-
* - unconfigured
|
|
1853
|
-
* - idle
|
|
1854
|
-
* -
|
|
1855
|
-
* -
|
|
1856
|
-
* - connecting:禁用按钮显示「连接中…」
|
|
1857
|
-
* - 已连接且权限自检完成:权限自检卡(缺失权限红字 + 开发者后台深链)
|
|
1856
|
+
* - unconfigured:上手三步 + 应用凭据
|
|
1857
|
+
* - idle / error / connecting / connected:状态块(当前阶段的唯一操作 + 运行数字)
|
|
1858
|
+
* - 连接后:自检(权限缺口可展开、斜杠指令面板同步结果)
|
|
1859
|
+
* - 有需重连才生效的已保存配置:顶部「重启连接」横幅
|
|
1858
1860
|
*
|
|
1859
1861
|
* @module dsh-channels/client/panel/connection-tab
|
|
1860
1862
|
*/
|
|
1861
1863
|
function ConnectionTab(props) {
|
|
1862
1864
|
const { panel, status, busy, credentials, onReconnect } = props;
|
|
1865
|
+
const checks = status.scopeCheck !== void 0 || status.slashCommandSync !== void 0;
|
|
1863
1866
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1864
1867
|
className: "dsh-channels-cfg__tabBody",
|
|
1865
1868
|
children: [
|
|
1866
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabHelp, { text: PANEL_COPY.connectionTabHelp }),
|
|
1867
1869
|
panel.config.restartPendingKeys.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartBanner, {
|
|
1868
1870
|
panel,
|
|
1869
1871
|
busy,
|
|
1870
1872
|
locked: status.busy,
|
|
1871
1873
|
onReconnect
|
|
1872
1874
|
}),
|
|
1873
|
-
status.phase === "unconfigured" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1875
|
+
status.phase === "unconfigured" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OnboardingSection, { status }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusBlock, {
|
|
1874
1876
|
status,
|
|
1875
1877
|
busy,
|
|
1876
1878
|
onReconnect
|
|
1877
1879
|
}),
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1880
|
+
checks ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
|
|
1881
|
+
title: PANEL_COPY.checksTitle,
|
|
1882
|
+
children: [status.scopeCheck === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScopeCheckRow, { check: status.scopeCheck }), status.slashCommandSync === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SlashCommandSyncRow, { sync: status.slashCommandSync })]
|
|
1883
|
+
}) : null,
|
|
1884
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CredentialsSection, {
|
|
1881
1885
|
panel,
|
|
1882
1886
|
credentials
|
|
1883
1887
|
})
|
|
@@ -1885,7 +1889,7 @@ window.__ModuleLoader__.load({
|
|
|
1885
1889
|
});
|
|
1886
1890
|
}
|
|
1887
1891
|
/** First-run guide: create the app, fill the credentials, save and wait. */
|
|
1888
|
-
function
|
|
1892
|
+
function OnboardingSection(props) {
|
|
1889
1893
|
const { status } = props;
|
|
1890
1894
|
const steps = [
|
|
1891
1895
|
{
|
|
@@ -1901,22 +1905,12 @@ window.__ModuleLoader__.load({
|
|
|
1901
1905
|
text: PANEL_COPY.onboardingStep3Text
|
|
1902
1906
|
}
|
|
1903
1907
|
];
|
|
1904
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
1905
|
-
className: "dsh-channels-
|
|
1908
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1909
|
+
className: "dsh-channels-cfg__hero",
|
|
1906
1910
|
children: [
|
|
1907
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1908
|
-
className: "dsh-channels-
|
|
1909
|
-
children:
|
|
1910
|
-
className: "dsh-channels-cfg__cardTitle",
|
|
1911
|
-
children: PANEL_COPY.onboardingTitle
|
|
1912
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1913
|
-
className: "dsh-channels-cfg__badge dsh-channels-cfg__badge--muted",
|
|
1914
|
-
children: PANEL_COPY.phaseUnconfigured
|
|
1915
|
-
})]
|
|
1916
|
-
}),
|
|
1917
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1918
|
-
className: "dsh-channels-cfg__help",
|
|
1919
|
-
children: PANEL_COPY.onboardingIntro
|
|
1911
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1912
|
+
className: "dsh-channels-cfg__heroTop",
|
|
1913
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: PANEL_COPY.onboardingTitle })
|
|
1920
1914
|
}),
|
|
1921
1915
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
1922
1916
|
className: "dsh-channels-cfg__steps",
|
|
@@ -1931,50 +1925,29 @@ window.__ModuleLoader__.load({
|
|
|
1931
1925
|
className: "dsh-channels-cfg__stepTitle",
|
|
1932
1926
|
children: step.title
|
|
1933
1927
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1934
|
-
className: "dsh-channels-
|
|
1928
|
+
className: "dsh-channels-cfg__rowSummary",
|
|
1935
1929
|
children: step.text
|
|
1936
1930
|
})]
|
|
1937
1931
|
})]
|
|
1938
1932
|
}, step.title))
|
|
1939
1933
|
}),
|
|
1940
|
-
status.missingConfig.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
1941
|
-
className: "dsh-channels-
|
|
1942
|
-
children: [
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
}),
|
|
1948
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1949
|
-
className: "dsh-channels-cfg__help",
|
|
1950
|
-
children: PANEL_COPY.onboardingDocsHint
|
|
1934
|
+
status.missingConfig.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1935
|
+
className: "dsh-channels-cfg__rowSummary",
|
|
1936
|
+
children: [
|
|
1937
|
+
PANEL_COPY.missingConfig,
|
|
1938
|
+
":",
|
|
1939
|
+
status.missingConfig.join("、")
|
|
1940
|
+
]
|
|
1951
1941
|
})
|
|
1952
1942
|
]
|
|
1953
1943
|
});
|
|
1954
1944
|
}
|
|
1955
|
-
/**
|
|
1956
|
-
function
|
|
1945
|
+
/** Phase, the one lifecycle action available in it, and the runtime counters. */
|
|
1946
|
+
function StatusBlock(props) {
|
|
1957
1947
|
const { status, busy, onReconnect } = props;
|
|
1958
1948
|
const badge = phaseBadge(status.phase);
|
|
1959
1949
|
const actionBusy = busy !== null || status.busy;
|
|
1960
|
-
const
|
|
1961
|
-
const facts = [
|
|
1962
|
-
{
|
|
1963
|
-
label: PANEL_COPY.statusCard,
|
|
1964
|
-
value: badge.text
|
|
1965
|
-
},
|
|
1966
|
-
{
|
|
1967
|
-
label: PANEL_COPY.connectionStateLabel,
|
|
1968
|
-
value: connection.text
|
|
1969
|
-
},
|
|
1970
|
-
{
|
|
1971
|
-
label: PANEL_COPY.botOpenId,
|
|
1972
|
-
value: status.botOpenIdTail ?? PANEL_COPY.unknownValue
|
|
1973
|
-
},
|
|
1974
|
-
{
|
|
1975
|
-
label: PANEL_COPY.uptime,
|
|
1976
|
-
value: formatUptime(status.uptimeMs)
|
|
1977
|
-
},
|
|
1950
|
+
const stats = [
|
|
1978
1951
|
{
|
|
1979
1952
|
label: PANEL_COPY.routeCount,
|
|
1980
1953
|
value: String(status.routeCount)
|
|
@@ -1986,188 +1959,167 @@ window.__ModuleLoader__.load({
|
|
|
1986
1959
|
{
|
|
1987
1960
|
label: PANEL_COPY.inflightTurnCount,
|
|
1988
1961
|
value: String(status.inflightTurnCount)
|
|
1962
|
+
},
|
|
1963
|
+
{
|
|
1964
|
+
label: PANEL_COPY.botOpenId,
|
|
1965
|
+
value: status.botOpenIdTail ?? PANEL_COPY.unknownValue,
|
|
1966
|
+
mono: true
|
|
1989
1967
|
}
|
|
1990
1968
|
];
|
|
1991
|
-
|
|
1992
|
-
|
|
1969
|
+
let action = null;
|
|
1970
|
+
if (status.phase === "connecting") action = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1971
|
+
type: "button",
|
|
1972
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
1973
|
+
disabled: true,
|
|
1974
|
+
children: PANEL_COPY.connecting
|
|
1975
|
+
});
|
|
1976
|
+
else if (status.phase === "idle" || status.phase === "error") action = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1977
|
+
type: "button",
|
|
1978
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
1979
|
+
onClick: onReconnect,
|
|
1980
|
+
disabled: actionBusy,
|
|
1981
|
+
children: busy === "reconnect" ? PANEL_COPY.connecting : status.phase === "idle" ? PANEL_COPY.connect : PANEL_COPY.retry
|
|
1982
|
+
});
|
|
1983
|
+
else if (status.phase === "connected") action = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1984
|
+
type: "button",
|
|
1985
|
+
className: "dsh-channels-cfg__btn",
|
|
1986
|
+
onClick: onReconnect,
|
|
1987
|
+
disabled: actionBusy,
|
|
1988
|
+
children: busy === "reconnect" ? PANEL_COPY.reconnecting : PANEL_COPY.reconnect
|
|
1989
|
+
});
|
|
1990
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1991
|
+
className: "dsh-channels-cfg__hero",
|
|
1993
1992
|
children: [
|
|
1994
1993
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1995
|
-
className: "dsh-channels-
|
|
1996
|
-
children: [
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
1994
|
+
className: "dsh-channels-cfg__heroTop",
|
|
1995
|
+
children: [
|
|
1996
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pill, {
|
|
1997
|
+
tone: badge.tone,
|
|
1998
|
+
dot: true,
|
|
1999
|
+
children: badge.text
|
|
2000
|
+
}),
|
|
2001
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: phaseSummary(status.phase) }),
|
|
2002
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsh-channels-cfg__spacer" }),
|
|
2003
|
+
action
|
|
2004
|
+
]
|
|
2003
2005
|
}),
|
|
2004
2006
|
status.lastStartError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2005
2007
|
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--error",
|
|
2006
|
-
children: [
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2008
|
+
children: [
|
|
2009
|
+
PANEL_COPY.lastError,
|
|
2010
|
+
":",
|
|
2011
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2012
|
+
className: "dsh-channels-cfg__mono",
|
|
2013
|
+
children: status.lastStartError
|
|
2014
|
+
})
|
|
2015
|
+
]
|
|
2010
2016
|
}),
|
|
2011
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2012
|
-
className: "dsh-channels-
|
|
2013
|
-
children:
|
|
2014
|
-
className: "dsh-channels-
|
|
2015
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2016
|
-
className: "dsh-channels-
|
|
2017
|
-
children:
|
|
2018
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2019
|
-
className: "dsh-channels-cfg__factValue",
|
|
2020
|
-
children: fact.value
|
|
2017
|
+
status.phase === "connected" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2018
|
+
className: "dsh-channels-cfg__stats",
|
|
2019
|
+
children: stats.map((stat) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2020
|
+
className: "dsh-channels-cfg__stat",
|
|
2021
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: stat.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
2022
|
+
className: stat.mono === true ? "dsh-channels-cfg__mono" : void 0,
|
|
2023
|
+
children: stat.value
|
|
2021
2024
|
})]
|
|
2022
|
-
},
|
|
2023
|
-
}),
|
|
2025
|
+
}, stat.label))
|
|
2026
|
+
}) : null,
|
|
2024
2027
|
status.reasons.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2025
|
-
className: `dsh-channels-
|
|
2028
|
+
className: `dsh-channels-cfg__rowSummary${status.level === "unhealthy" ? " dsh-channels-cfg__textBad" : ""}`,
|
|
2026
2029
|
children: [
|
|
2027
2030
|
PANEL_COPY.healthReasons,
|
|
2028
2031
|
":",
|
|
2029
2032
|
status.reasons.join(";")
|
|
2030
2033
|
]
|
|
2031
|
-
}),
|
|
2032
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2033
|
-
className: "dsh-channels-cfg__actions",
|
|
2034
|
-
children: [
|
|
2035
|
-
status.phase === "connecting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2036
|
-
type: "button",
|
|
2037
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
2038
|
-
disabled: true,
|
|
2039
|
-
children: PANEL_COPY.connecting
|
|
2040
|
-
}) : null,
|
|
2041
|
-
status.phase === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2042
|
-
type: "button",
|
|
2043
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
2044
|
-
onClick: onReconnect,
|
|
2045
|
-
disabled: actionBusy,
|
|
2046
|
-
children: busy === "reconnect" ? PANEL_COPY.connecting : PANEL_COPY.connect
|
|
2047
|
-
}) : null,
|
|
2048
|
-
status.phase === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2049
|
-
type: "button",
|
|
2050
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
2051
|
-
onClick: onReconnect,
|
|
2052
|
-
disabled: actionBusy,
|
|
2053
|
-
children: busy === "reconnect" ? PANEL_COPY.connecting : PANEL_COPY.retry
|
|
2054
|
-
}) : null,
|
|
2055
|
-
status.phase === "connected" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2056
|
-
type: "button",
|
|
2057
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--danger",
|
|
2058
|
-
onClick: onReconnect,
|
|
2059
|
-
disabled: actionBusy,
|
|
2060
|
-
children: busy === "reconnect" ? PANEL_COPY.reconnecting : PANEL_COPY.reconnect
|
|
2061
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2062
|
-
className: "dsh-channels-cfg__help",
|
|
2063
|
-
children: PANEL_COPY.restartWarning
|
|
2064
|
-
})] }) : null
|
|
2065
|
-
]
|
|
2066
2034
|
})
|
|
2067
2035
|
]
|
|
2068
2036
|
});
|
|
2069
2037
|
}
|
|
2070
|
-
/**
|
|
2071
|
-
function
|
|
2038
|
+
/** Status icon in front of one check row. */
|
|
2039
|
+
function CheckIcon(props) {
|
|
2040
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2041
|
+
className: `dsh-channels-cfg__checkIcon dsh-channels-cfg__checkIcon--${props.tone}`,
|
|
2042
|
+
"aria-hidden": "true",
|
|
2043
|
+
children: props.tone === "ok" ? "✓" : "!"
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
/** Post-connect scope self-check: the gap count, expandable to each scope with its console link. */
|
|
2047
|
+
function ScopeCheckRow(props) {
|
|
2072
2048
|
const { check } = props;
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2049
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2050
|
+
if (check.state === "ok") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
2051
|
+
leading: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { tone: "ok" }),
|
|
2052
|
+
label: PANEL_COPY.scopeCheckOk
|
|
2053
|
+
});
|
|
2054
|
+
if (check.state === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
2055
|
+
leading: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { tone: "bad" }),
|
|
2056
|
+
label: PANEL_COPY.scopeCheckError,
|
|
2057
|
+
summary: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2058
|
+
className: "dsh-channels-cfg__mono",
|
|
2059
|
+
children: check.error
|
|
2060
|
+
})
|
|
2061
|
+
});
|
|
2062
|
+
const required = check.missing.filter((item) => item.level === "required").length;
|
|
2063
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
2064
|
+
leading: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { tone: required > 0 ? "bad" : "warn" }),
|
|
2065
|
+
label: required > 0 ? fillCopy(PANEL_COPY.scopeCheckMissingRequired, { n: required }) : fillCopy(PANEL_COPY.scopeCheckMissing, { n: check.missing.length }),
|
|
2066
|
+
summary: required > 0 ? PANEL_COPY.scopeCheckPublish : PANEL_COPY.scopeCheckMissingOptional,
|
|
2067
|
+
control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2068
|
+
className: "dsh-channels-cfg__inputRow",
|
|
2069
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2070
|
+
type: "button",
|
|
2071
|
+
className: "dsh-channels-cfg__more",
|
|
2072
|
+
"aria-expanded": open,
|
|
2073
|
+
onClick: () => setOpen(!open),
|
|
2074
|
+
children: open ? PANEL_COPY.collapse : PANEL_COPY.expand
|
|
2075
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2076
|
+
className: "dsh-channels-cfg__btn",
|
|
2077
|
+
href: check.consoleUrl,
|
|
2078
|
+
target: "_blank",
|
|
2079
|
+
rel: "noreferrer noopener",
|
|
2080
|
+
children: PANEL_COPY.scopeCheckOpenAll
|
|
2081
|
+
})]
|
|
2082
|
+
})
|
|
2083
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2084
|
+
className: "dsh-channels-cfg__scopeList",
|
|
2085
|
+
children: check.missing.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
2086
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
2087
|
+
className: "dsh-channels-cfg__mono",
|
|
2088
|
+
children: item.scopes.join(" / ")
|
|
2082
2089
|
}),
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
children: PANEL_COPY.
|
|
2086
|
-
}) : null,
|
|
2087
|
-
check.state === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2088
|
-
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--error",
|
|
2089
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: PANEL_COPY.scopeCheckError }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2090
|
-
className: "dsh-channels-cfg__mono",
|
|
2091
|
-
children: check.error
|
|
2092
|
-
})]
|
|
2090
|
+
item.level === "required" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pill, {
|
|
2091
|
+
tone: "bad",
|
|
2092
|
+
children: PANEL_COPY.scopeCheckRequired
|
|
2093
2093
|
}) : null,
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
"] "
|
|
2105
|
-
] }),
|
|
2106
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2107
|
-
className: "dsh-channels-cfg__mono",
|
|
2108
|
-
children: item.scopes.join(" / ")
|
|
2109
|
-
}),
|
|
2110
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
2111
|
-
":",
|
|
2112
|
-
item.purpose,
|
|
2113
|
-
" "
|
|
2114
|
-
] }),
|
|
2115
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2116
|
-
href: item.consoleUrl,
|
|
2117
|
-
target: "_blank",
|
|
2118
|
-
rel: "noreferrer noopener",
|
|
2119
|
-
children: PANEL_COPY.scopeCheckOpenOne
|
|
2120
|
-
})
|
|
2121
|
-
] }, item.scopes.join(",")))
|
|
2122
|
-
}),
|
|
2123
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2124
|
-
href: check.consoleUrl,
|
|
2125
|
-
target: "_blank",
|
|
2126
|
-
rel: "noreferrer noopener",
|
|
2127
|
-
children: PANEL_COPY.scopeCheckOpenAll
|
|
2128
|
-
})
|
|
2129
|
-
]
|
|
2130
|
-
}) : null
|
|
2131
|
-
]
|
|
2132
|
-
});
|
|
2094
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.purpose }),
|
|
2095
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2096
|
+
className: "dsh-channels-cfg__link",
|
|
2097
|
+
href: item.consoleUrl,
|
|
2098
|
+
target: "_blank",
|
|
2099
|
+
rel: "noreferrer noopener",
|
|
2100
|
+
children: PANEL_COPY.scopeCheckOpenOne
|
|
2101
|
+
})
|
|
2102
|
+
] }, item.scopes.join(",")))
|
|
2103
|
+
}) : null] });
|
|
2133
2104
|
}
|
|
2134
|
-
/** Post-connect native slash-command panel sync:
|
|
2135
|
-
function
|
|
2105
|
+
/** Post-connect native slash-command panel sync: the command count, or the Feishu reason. */
|
|
2106
|
+
function SlashCommandSyncRow(props) {
|
|
2136
2107
|
const { sync } = props;
|
|
2137
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
PANEL_COPY.slashCommandSyncTotal,
|
|
2149
|
-
" ",
|
|
2150
|
-
sync.total,
|
|
2151
|
-
" · ",
|
|
2152
|
-
PANEL_COPY.slashCommandSyncCreated,
|
|
2153
|
-
" ",
|
|
2154
|
-
sync.created,
|
|
2155
|
-
" · ",
|
|
2156
|
-
PANEL_COPY.slashCommandSyncUpdated,
|
|
2157
|
-
" ",
|
|
2158
|
-
sync.updated,
|
|
2159
|
-
" · ",
|
|
2160
|
-
PANEL_COPY.slashCommandSyncRemoved,
|
|
2161
|
-
" ",
|
|
2162
|
-
sync.removed
|
|
2163
|
-
] })]
|
|
2164
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2165
|
-
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--error",
|
|
2166
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: PANEL_COPY.slashCommandSyncError }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2108
|
+
return sync.state === "ok" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
2109
|
+
leading: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { tone: "ok" }),
|
|
2110
|
+
label: PANEL_COPY.slashCommandSyncTitle,
|
|
2111
|
+
summary: fillCopy(PANEL_COPY.slashCommandSyncOk, { total: sync.total })
|
|
2112
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
2113
|
+
leading: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { tone: "warn" }),
|
|
2114
|
+
label: PANEL_COPY.slashCommandSyncTitle,
|
|
2115
|
+
summary: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2116
|
+
PANEL_COPY.slashCommandSyncError,
|
|
2117
|
+
":",
|
|
2118
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2167
2119
|
className: "dsh-channels-cfg__mono",
|
|
2168
2120
|
children: sync.error
|
|
2169
|
-
})
|
|
2170
|
-
})
|
|
2121
|
+
})
|
|
2122
|
+
] })
|
|
2171
2123
|
});
|
|
2172
2124
|
}
|
|
2173
2125
|
/** Config saved but only applied on the next connect: offer the one-click restart. */
|
|
@@ -2176,179 +2128,42 @@ window.__ModuleLoader__.load({
|
|
|
2176
2128
|
const keys = panel.config.restartPendingKeys.map((key) => fieldLabel(panel.config.fields, key)).join("、");
|
|
2177
2129
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2178
2130
|
className: "dsh-channels-cfg__banner",
|
|
2179
|
-
children: [
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
className: "dsh-channels-cfg__help",
|
|
2190
|
-
children: PANEL_COPY.restartWarning
|
|
2191
|
-
})
|
|
2192
|
-
]
|
|
2193
|
-
});
|
|
2194
|
-
}
|
|
2195
|
-
//#endregion
|
|
2196
|
-
//#region src/client/panel/model-tab.tsx
|
|
2197
|
-
/**
|
|
2198
|
-
* 「模型」tab:模型服务商 / 模型 id / 推理档位 / Agent 预设四个选择器。
|
|
2199
|
-
* 选项来自 Host 实时目录(页面外壳负责 provider→model→effort 级联刷新),
|
|
2200
|
-
* 目录读取中时在表单上方提示。
|
|
2201
|
-
*
|
|
2202
|
-
* @module dsh-channels/client/panel/model-tab
|
|
2203
|
-
*/
|
|
2204
|
-
function ModelTab(props) {
|
|
2205
|
-
const { form, catalogBusy } = props;
|
|
2206
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2207
|
-
className: "dsh-channels-cfg__tabBody",
|
|
2208
|
-
children: [
|
|
2209
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabHelp, { text: PANEL_COPY.modelTabHelp }),
|
|
2210
|
-
catalogBusy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2211
|
-
className: "dsh-channels-cfg__help",
|
|
2212
|
-
children: PANEL_COPY.catalogLoading
|
|
2213
|
-
}) : null,
|
|
2214
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldsCard, {
|
|
2215
|
-
form,
|
|
2216
|
-
groups: TAB_FIELD_GROUPS.model
|
|
2217
|
-
}),
|
|
2218
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SaveBar, {
|
|
2219
|
-
form,
|
|
2220
|
-
tab: "model"
|
|
2221
|
-
})
|
|
2222
|
-
]
|
|
2223
|
-
});
|
|
2224
|
-
}
|
|
2225
|
-
//#endregion
|
|
2226
|
-
//#region src/client/panel/analyze-token-card.tsx
|
|
2227
|
-
/**
|
|
2228
|
-
* 日志分析接口令牌编辑器:值经 Host 的 DSH 凭据服务写入本机凭据库,面板只显示
|
|
2229
|
-
* 「已配置 / 未配置」,不回显已存值;保存或移除后下一次接口请求即生效,不触发重连。
|
|
2230
|
-
*
|
|
2231
|
-
* @module dsh-channels/client/panel/analyze-token-card
|
|
2232
|
-
*/
|
|
2233
|
-
function AnalyzeTokenCard(props) {
|
|
2234
|
-
const { panel, token } = props;
|
|
2235
|
-
const state = panel.config.credentials.analyzeToken;
|
|
2236
|
-
const disabled = token.busy !== null || token.locked || !state.writable;
|
|
2237
|
-
const issue = token.issues.find((item) => item.key === "analyzeToken");
|
|
2238
|
-
const inputClass = `dsh-channels-cfg__input${issue === void 0 ? "" : " dsh-channels-cfg__input--invalid"}`;
|
|
2239
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2240
|
-
className: "dsh-channels-cfg__card",
|
|
2241
|
-
children: [
|
|
2242
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2243
|
-
className: "dsh-channels-cfg__cardHead",
|
|
2244
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2245
|
-
className: "dsh-channels-cfg__cardTitle",
|
|
2246
|
-
children: PANEL_COPY.analyzeTokenCard
|
|
2247
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2248
|
-
className: `dsh-channels-cfg__badge dsh-channels-cfg__badge--${state.configured ? "ok" : "muted"}`,
|
|
2249
|
-
children: state.configured ? PANEL_COPY.credentialConfigured : PANEL_COPY.credentialMissing
|
|
2250
|
-
})]
|
|
2251
|
-
}),
|
|
2252
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2253
|
-
className: "dsh-channels-cfg__help",
|
|
2254
|
-
children: PANEL_COPY.analyzeTokenCardHelp
|
|
2255
|
-
}),
|
|
2256
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2257
|
-
className: "dsh-channels-cfg__field",
|
|
2258
|
-
children: [
|
|
2259
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2260
|
-
className: "dsh-channels-cfg__label",
|
|
2261
|
-
children: PANEL_COPY.analyzeTokenLabel
|
|
2262
|
-
}),
|
|
2263
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2264
|
-
className: "dsh-channels-cfg__cred",
|
|
2265
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2266
|
-
type: "password",
|
|
2267
|
-
className: inputClass,
|
|
2268
|
-
value: token.value,
|
|
2269
|
-
placeholder: PANEL_COPY.analyzeTokenPlaceholder,
|
|
2270
|
-
autoComplete: "new-password",
|
|
2271
|
-
"aria-label": PANEL_COPY.analyzeTokenLabel,
|
|
2272
|
-
disabled,
|
|
2273
|
-
onChange: (event) => token.onChange(event.target.value)
|
|
2274
|
-
}), state.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2275
|
-
type: "button",
|
|
2276
|
-
className: "dsh-channels-cfg__btn",
|
|
2277
|
-
disabled,
|
|
2278
|
-
onClick: token.onClear,
|
|
2279
|
-
children: token.busy === "credentials-clear" ? PANEL_COPY.removingCredential : PANEL_COPY.removeCredential
|
|
2280
|
-
}) : null]
|
|
2281
|
-
}),
|
|
2282
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2283
|
-
className: "dsh-channels-cfg__help",
|
|
2284
|
-
children: [
|
|
2285
|
-
PANEL_COPY.credentialsRef,
|
|
2286
|
-
":",
|
|
2287
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2288
|
-
className: "dsh-channels-cfg__mono",
|
|
2289
|
-
children: state.ref
|
|
2290
|
-
}),
|
|
2291
|
-
state.source === void 0 ? "" : ` · ${state.source}`
|
|
2292
|
-
]
|
|
2293
|
-
}),
|
|
2294
|
-
issue === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2295
|
-
className: "dsh-channels-cfg__fieldError",
|
|
2296
|
-
children: issue.message
|
|
2297
|
-
})
|
|
2298
|
-
]
|
|
2299
|
-
}),
|
|
2300
|
-
state.writable ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2301
|
-
className: "dsh-channels-cfg__fieldError",
|
|
2302
|
-
children: PANEL_COPY.credentialReadOnly
|
|
2303
|
-
}),
|
|
2304
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2305
|
-
className: "dsh-channels-cfg__footer",
|
|
2306
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2307
|
-
type: "button",
|
|
2308
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
2309
|
-
onClick: token.onSave,
|
|
2310
|
-
disabled: disabled || token.value.trim() === "",
|
|
2311
|
-
children: token.busy === "credentials" ? PANEL_COPY.savingCredentials : PANEL_COPY.saveAnalyzeToken
|
|
2312
|
-
})
|
|
2313
|
-
})
|
|
2314
|
-
]
|
|
2131
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2132
|
+
className: "dsh-channels-cfg__bannerText",
|
|
2133
|
+
children: [PANEL_COPY.restartBanner, keys]
|
|
2134
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2135
|
+
type: "button",
|
|
2136
|
+
className: "dsh-channels-cfg__btn",
|
|
2137
|
+
onClick: onReconnect,
|
|
2138
|
+
disabled: busy !== null || locked,
|
|
2139
|
+
children: busy === "reconnect" ? PANEL_COPY.reconnecting : PANEL_COPY.reconnect
|
|
2140
|
+
})]
|
|
2315
2141
|
});
|
|
2316
2142
|
}
|
|
2317
2143
|
//#endregion
|
|
2318
|
-
//#region src/client/panel/
|
|
2319
|
-
/**
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
*
|
|
2324
|
-
* @module dsh-channels/client/panel/sessions-tab
|
|
2325
|
-
*/
|
|
2326
|
-
function SessionsTab(props) {
|
|
2327
|
-
const { form, analyzeToken } = props;
|
|
2144
|
+
//#region src/client/panel/fields-tab.tsx
|
|
2145
|
+
/** Footnotes under one group's section, per group. */
|
|
2146
|
+
const FOOTNOTES = { model: PANEL_COPY.modelFootnote };
|
|
2147
|
+
function FieldsTab(props) {
|
|
2148
|
+
const { form, tab, note } = props;
|
|
2328
2149
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2329
2150
|
className: "dsh-channels-cfg__tabBody",
|
|
2330
|
-
children: [
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
tab: "sessions"
|
|
2339
|
-
}),
|
|
2340
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnalyzeTokenCard, {
|
|
2341
|
-
panel: form.panel,
|
|
2342
|
-
token: analyzeToken
|
|
2343
|
-
})
|
|
2344
|
-
]
|
|
2151
|
+
children: [note === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2152
|
+
className: "dsh-channels-cfg__secFoot",
|
|
2153
|
+
children: note
|
|
2154
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldSections, {
|
|
2155
|
+
form,
|
|
2156
|
+
groups: TAB_FIELD_GROUPS[tab],
|
|
2157
|
+
footnotes: FOOTNOTES
|
|
2158
|
+
})]
|
|
2345
2159
|
});
|
|
2346
2160
|
}
|
|
2347
2161
|
//#endregion
|
|
2348
2162
|
//#region src/client/panel/wiki-tab.tsx
|
|
2349
2163
|
/**
|
|
2350
|
-
* 「知识库」tab
|
|
2351
|
-
*
|
|
2164
|
+
* 「知识库」tab:知识库工具字段(读取 / 编辑开关、可访问范围)+ 知识库
|
|
2165
|
+
* 白名单(「可使用的人」)。字段保存走全局 draft,白名单增删是独立即时
|
|
2166
|
+
* mutation。
|
|
2352
2167
|
*
|
|
2353
2168
|
* @module dsh-channels/client/panel/wiki-tab
|
|
2354
2169
|
*/
|
|
@@ -2359,75 +2174,36 @@ window.__ModuleLoader__.load({
|
|
|
2359
2174
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2360
2175
|
className: "dsh-channels-cfg__tabBody",
|
|
2361
2176
|
children: [
|
|
2362
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
groups: TAB_FIELD_GROUPS.wiki
|
|
2177
|
+
accessNotice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NoticeBar, {
|
|
2178
|
+
kind: "ok",
|
|
2179
|
+
text: accessNotice
|
|
2366
2180
|
}),
|
|
2367
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
2368
|
-
|
|
2369
|
-
|
|
2181
|
+
accessError === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NoticeBar, {
|
|
2182
|
+
kind: "error",
|
|
2183
|
+
text: accessError
|
|
2370
2184
|
}),
|
|
2371
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2372
|
-
className: "dsh-channels-cfg__card",
|
|
2373
|
-
children: [
|
|
2374
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2375
|
-
className: "dsh-channels-cfg__cardHead",
|
|
2376
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2377
|
-
className: "dsh-channels-cfg__cardTitle",
|
|
2378
|
-
children: PANEL_COPY.wikiUsersCard
|
|
2379
|
-
})
|
|
2380
|
-
}),
|
|
2381
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2382
|
-
className: "dsh-channels-cfg__help",
|
|
2383
|
-
children: PANEL_COPY.wikiUsersHelp
|
|
2384
|
-
}),
|
|
2385
|
-
!access.loaded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, { text: PANEL_COPY.accessNotLoaded }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserWhitelist, {
|
|
2386
|
-
title: PANEL_COPY.wikiUsersCard,
|
|
2387
|
-
empty: PANEL_COPY.wikiUsersEmpty,
|
|
2388
|
-
addLabel: PANEL_COPY.accessAddUser,
|
|
2389
|
-
users: access.wikiUsers,
|
|
2390
|
-
disabled,
|
|
2391
|
-
working: form.busy === "access",
|
|
2392
|
-
onAdd: onAddUser,
|
|
2393
|
-
onRemove: onRemoveUser
|
|
2394
|
-
}),
|
|
2395
|
-
accessNotice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2396
|
-
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--ok",
|
|
2397
|
-
role: "status",
|
|
2398
|
-
"aria-live": "polite",
|
|
2399
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: accessNotice })
|
|
2400
|
-
}),
|
|
2401
|
-
accessError === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2402
|
-
className: "dsh-channels-cfg__fieldError",
|
|
2403
|
-
role: "alert",
|
|
2404
|
-
children: accessError
|
|
2405
|
-
})
|
|
2406
|
-
]
|
|
2407
|
-
})
|
|
2408
|
-
]
|
|
2409
|
-
});
|
|
2410
|
-
}
|
|
2411
|
-
//#endregion
|
|
2412
|
-
//#region src/client/panel/workspace-tab.tsx
|
|
2413
|
-
/**
|
|
2414
|
-
* 「工作区」tab:工作区根目录字段与保存栏。
|
|
2415
|
-
*
|
|
2416
|
-
* @module dsh-channels/client/panel/workspace-tab
|
|
2417
|
-
*/
|
|
2418
|
-
function WorkspaceTab(props) {
|
|
2419
|
-
const { form } = props;
|
|
2420
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2421
|
-
className: "dsh-channels-cfg__tabBody",
|
|
2422
|
-
children: [
|
|
2423
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabHelp, { text: PANEL_COPY.workspaceTabHelp }),
|
|
2424
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldsCard, {
|
|
2185
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldSections, {
|
|
2425
2186
|
form,
|
|
2426
|
-
groups: TAB_FIELD_GROUPS.
|
|
2187
|
+
groups: TAB_FIELD_GROUPS.wiki
|
|
2427
2188
|
}),
|
|
2428
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
2429
|
-
|
|
2430
|
-
|
|
2189
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Whitelist, {
|
|
2190
|
+
title: PANEL_COPY.wikiUsersTitle,
|
|
2191
|
+
countLabel: fillCopy(PANEL_COPY.wikiUsersAdminPlus, { n: access.wikiUsers.length }),
|
|
2192
|
+
entries: access.wikiUsers.map((user) => ({
|
|
2193
|
+
id: user.userId,
|
|
2194
|
+
name: user.name,
|
|
2195
|
+
source: user.source
|
|
2196
|
+
})),
|
|
2197
|
+
empty: PANEL_COPY.wikiUsersEmpty,
|
|
2198
|
+
loaded: access.loaded,
|
|
2199
|
+
addLabel: PANEL_COPY.accessAddUser,
|
|
2200
|
+
idPlaceholder: PANEL_COPY.accessUserIdPlaceholder,
|
|
2201
|
+
namePlaceholder: PANEL_COPY.accessUserNamePlaceholder,
|
|
2202
|
+
footnote: PANEL_COPY.wikiUsersHint,
|
|
2203
|
+
disabled,
|
|
2204
|
+
working: form.busy === "access",
|
|
2205
|
+
onAdd: onAddUser,
|
|
2206
|
+
onRemove: onRemoveUser
|
|
2431
2207
|
})
|
|
2432
2208
|
]
|
|
2433
2209
|
});
|
|
@@ -2435,8 +2211,8 @@ window.__ModuleLoader__.load({
|
|
|
2435
2211
|
//#endregion
|
|
2436
2212
|
//#region src/client/panel/index.tsx
|
|
2437
2213
|
/**
|
|
2438
|
-
* 设置 →「飞书渠道」页面:数据加载、5 秒状态轮询、全局 draft
|
|
2439
|
-
* tab
|
|
2214
|
+
* 设置 →「飞书渠道」页面:数据加载、5 秒状态轮询、全局 draft、顶部状态头 /
|
|
2215
|
+
* tab 导航与底部保存条的外壳。
|
|
2440
2216
|
*
|
|
2441
2217
|
* 所有 Host 交互(panel / status / catalog / config / credentials / access /
|
|
2442
2218
|
* pick-directory)都集中在这里;tab 组件只接收渲染所需的数据与
|
|
@@ -2702,22 +2478,14 @@ window.__ModuleLoader__.load({
|
|
|
2702
2478
|
setBusy(null);
|
|
2703
2479
|
}
|
|
2704
2480
|
}, [draft, panel]);
|
|
2705
|
-
/** Drop the draft
|
|
2706
|
-
const
|
|
2481
|
+
/** Drop the whole draft (nothing is written). */
|
|
2482
|
+
const discardDraft = (0, react.useCallback)(() => {
|
|
2707
2483
|
if (panel === null) return;
|
|
2708
|
-
|
|
2709
|
-
const keys = new Set(panel.config.fields.filter((field) => groups.has(field.group)).map((field) => field.key));
|
|
2710
|
-
setDraft((previous) => {
|
|
2711
|
-
const next = { ...previous };
|
|
2712
|
-
for (const key of keys) delete next[key];
|
|
2713
|
-
return next;
|
|
2714
|
-
});
|
|
2484
|
+
setDraft({});
|
|
2715
2485
|
setNotice(null);
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
setCatalogBusy(false);
|
|
2720
|
-
}
|
|
2486
|
+
catalogTicket.current += 1;
|
|
2487
|
+
setCatalog(catalogFromFields(panel.config.fields));
|
|
2488
|
+
setCatalogBusy(false);
|
|
2721
2489
|
}, [panel]);
|
|
2722
2490
|
/**
|
|
2723
2491
|
* Open the host's native directory chooser (workspace root「选择目录…」) and
|
|
@@ -2753,6 +2521,7 @@ window.__ModuleLoader__.load({
|
|
|
2753
2521
|
}, []);
|
|
2754
2522
|
/** Drop every Web override and return the refreshed panel. */
|
|
2755
2523
|
const onClear = (0, react.useCallback)(async () => {
|
|
2524
|
+
if (!(typeof window.confirm === "function" ? window.confirm(PANEL_COPY.clearConfirm) : true)) return;
|
|
2756
2525
|
setBusy("clear");
|
|
2757
2526
|
setNotice(null);
|
|
2758
2527
|
try {
|
|
@@ -3036,6 +2805,7 @@ window.__ModuleLoader__.load({
|
|
|
3036
2805
|
}, []);
|
|
3037
2806
|
const issues = (0, react.useMemo)(() => notice?.kind === "error" ? notice.issues ?? [] : [], [notice]);
|
|
3038
2807
|
const dirtyCount = Object.keys(draft).length;
|
|
2808
|
+
const restartCount = (0, react.useMemo)(() => (panel?.config.fields ?? []).filter((field) => field.restartRequired && field.key in draft).length, [draft, panel]);
|
|
3039
2809
|
/** Which tabs currently own a dirty key (the dot is per tab, not global). */
|
|
3040
2810
|
const dirtyTabs = (0, react.useMemo)(() => {
|
|
3041
2811
|
const tabs = /* @__PURE__ */ new Set();
|
|
@@ -3061,23 +2831,15 @@ window.__ModuleLoader__.load({
|
|
|
3061
2831
|
issues,
|
|
3062
2832
|
busy,
|
|
3063
2833
|
locked: hostBusy,
|
|
3064
|
-
dirtyCount,
|
|
3065
2834
|
onChange: updateDraft,
|
|
3066
|
-
onSave: () => {
|
|
3067
|
-
onSave();
|
|
3068
|
-
},
|
|
3069
|
-
onDiscardTab: discardTab,
|
|
3070
2835
|
onPickDirectory
|
|
3071
2836
|
};
|
|
3072
2837
|
}, [
|
|
3073
2838
|
busy,
|
|
3074
|
-
discardTab,
|
|
3075
|
-
dirtyCount,
|
|
3076
2839
|
draft,
|
|
3077
2840
|
hostBusy,
|
|
3078
2841
|
issues,
|
|
3079
2842
|
onPickDirectory,
|
|
3080
|
-
onSave,
|
|
3081
2843
|
panel,
|
|
3082
2844
|
renderedFields,
|
|
3083
2845
|
updateDraft
|
|
@@ -3167,9 +2929,17 @@ window.__ModuleLoader__.load({
|
|
|
3167
2929
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabNav, {
|
|
3168
2930
|
activeTab,
|
|
3169
2931
|
dirtyTabs,
|
|
2932
|
+
counts: { access: load.panel.access.loaded ? load.panel.access.requests.length : 0 },
|
|
3170
2933
|
onChange: setActiveTab
|
|
3171
2934
|
}),
|
|
3172
|
-
|
|
2935
|
+
load.panel.access.mode === "open" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2936
|
+
className: "dsh-channels-cfg__banner",
|
|
2937
|
+
children: PANEL_COPY.debugModeWarning
|
|
2938
|
+
}) : null,
|
|
2939
|
+
notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NoticeBar, {
|
|
2940
|
+
kind: notice.kind,
|
|
2941
|
+
text: notice.text
|
|
2942
|
+
}),
|
|
3173
2943
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3174
2944
|
role: "tabpanel",
|
|
3175
2945
|
id: `dsh-channels-tabpanel-${activeTab}`,
|
|
@@ -3184,15 +2954,19 @@ window.__ModuleLoader__.load({
|
|
|
3184
2954
|
onReconnect();
|
|
3185
2955
|
}
|
|
3186
2956
|
}) : null,
|
|
3187
|
-
activeTab === "sessions" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
2957
|
+
activeTab === "sessions" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldsTab, {
|
|
2958
|
+
form,
|
|
2959
|
+
tab: "sessions"
|
|
2960
|
+
}) : null,
|
|
2961
|
+
activeTab === "model" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldsTab, {
|
|
3188
2962
|
form,
|
|
3189
|
-
|
|
2963
|
+
tab: "model",
|
|
2964
|
+
note: catalogBusy ? PANEL_COPY.catalogLoading : void 0
|
|
3190
2965
|
}) : null,
|
|
3191
|
-
activeTab === "
|
|
2966
|
+
activeTab === "attachments" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldsTab, {
|
|
3192
2967
|
form,
|
|
3193
|
-
|
|
2968
|
+
tab: "attachments"
|
|
3194
2969
|
}) : null,
|
|
3195
|
-
activeTab === "workspace" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceTab, { form }) : null,
|
|
3196
2970
|
activeTab === "access" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccessTab, {
|
|
3197
2971
|
form,
|
|
3198
2972
|
accessNotice,
|
|
@@ -3220,64 +2994,81 @@ window.__ModuleLoader__.load({
|
|
|
3220
2994
|
}) : null,
|
|
3221
2995
|
activeTab === "advanced" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdvancedTab, {
|
|
3222
2996
|
form,
|
|
2997
|
+
analyzeToken,
|
|
3223
2998
|
onClear: () => {
|
|
3224
2999
|
onClear();
|
|
3225
3000
|
}
|
|
3226
3001
|
}) : null
|
|
3227
3002
|
]
|
|
3003
|
+
}),
|
|
3004
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SaveBar, {
|
|
3005
|
+
dirtyCount,
|
|
3006
|
+
restartCount,
|
|
3007
|
+
busy,
|
|
3008
|
+
locked: hostBusy,
|
|
3009
|
+
onSave: () => {
|
|
3010
|
+
onSave();
|
|
3011
|
+
},
|
|
3012
|
+
onDiscard: discardDraft
|
|
3228
3013
|
})
|
|
3229
3014
|
]
|
|
3230
3015
|
});
|
|
3231
3016
|
}
|
|
3232
|
-
/** Page header: title
|
|
3017
|
+
/** Page header: title, phase pill, version and uptime, refresh; visible on every tab. */
|
|
3233
3018
|
function PanelHeader(props) {
|
|
3234
3019
|
const { status, pollError, busy, locked, onRefresh } = props;
|
|
3235
3020
|
const badge = phaseBadge(status.phase);
|
|
3236
3021
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
3237
3022
|
className: "dsh-channels-cfg__panelHead",
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3023
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3024
|
+
className: "dsh-channels-cfg__headRow",
|
|
3025
|
+
children: [
|
|
3026
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
3027
|
+
className: "dsh-channels-cfg__title",
|
|
3028
|
+
children: PANEL_COPY.title
|
|
3029
|
+
}),
|
|
3030
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pill, {
|
|
3031
|
+
tone: badge.tone,
|
|
3032
|
+
dot: true,
|
|
3033
|
+
children: badge.text
|
|
3034
|
+
}),
|
|
3035
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3036
|
+
className: "dsh-channels-cfg__meta dsh-channels-cfg__mono",
|
|
3037
|
+
children: ["v", status.version]
|
|
3038
|
+
}),
|
|
3039
|
+
status.uptimeMs === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3040
|
+
className: "dsh-channels-cfg__meta",
|
|
3041
|
+
children: [
|
|
3042
|
+
"· ",
|
|
3043
|
+
PANEL_COPY.uptimePrefix,
|
|
3044
|
+
" ",
|
|
3045
|
+
formatUptime(status.uptimeMs)
|
|
3046
|
+
]
|
|
3047
|
+
}),
|
|
3048
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsh-channels-cfg__spacer" }),
|
|
3049
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3050
|
+
type: "button",
|
|
3051
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost",
|
|
3052
|
+
onClick: onRefresh,
|
|
3053
|
+
disabled: busy !== null || locked,
|
|
3054
|
+
children: PANEL_COPY.refresh
|
|
3055
|
+
})
|
|
3056
|
+
]
|
|
3057
|
+
}), pollError === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3058
|
+
className: "dsh-channels-cfg__fieldError",
|
|
3059
|
+
role: "status",
|
|
3060
|
+
"aria-live": "polite",
|
|
3061
|
+
children: [
|
|
3062
|
+
PANEL_COPY.pollFailed,
|
|
3063
|
+
":",
|
|
3064
|
+
pollError
|
|
3065
|
+
]
|
|
3066
|
+
})]
|
|
3276
3067
|
});
|
|
3277
3068
|
}
|
|
3278
3069
|
/** Top-level tab navigation; the dirty dot keeps unsaved work visible per tab. */
|
|
3279
3070
|
function TabNav(props) {
|
|
3280
|
-
const { activeTab, dirtyTabs, onChange } = props;
|
|
3071
|
+
const { activeTab, dirtyTabs, counts, onChange } = props;
|
|
3281
3072
|
const refs = (0, react.useRef)([]);
|
|
3282
3073
|
/** Roving-focus keyboard navigation between tabs (WAI-ARIA tabs pattern). */
|
|
3283
3074
|
const onKeyDown = (index, event) => {
|
|
@@ -3322,11 +3113,17 @@ window.__ModuleLoader__.load({
|
|
|
3322
3113
|
className: "dsh-channels-cfg__tab",
|
|
3323
3114
|
onClick: () => onChange(tab.id),
|
|
3324
3115
|
onKeyDown: (event) => onKeyDown(index, event),
|
|
3325
|
-
children: [
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3116
|
+
children: [
|
|
3117
|
+
tab.label,
|
|
3118
|
+
(counts[tab.id] ?? 0) > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3119
|
+
className: "dsh-channels-cfg__tabCount",
|
|
3120
|
+
children: counts[tab.id]
|
|
3121
|
+
}) : null,
|
|
3122
|
+
dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3123
|
+
className: "dsh-channels-cfg__tabDot",
|
|
3124
|
+
"aria-hidden": "true"
|
|
3125
|
+
}) : null
|
|
3126
|
+
]
|
|
3330
3127
|
}, tab.id);
|
|
3331
3128
|
})
|
|
3332
3129
|
});
|
|
@@ -3335,108 +3132,125 @@ window.__ModuleLoader__.load({
|
|
|
3335
3132
|
//#region src/client/styles.ts
|
|
3336
3133
|
/**
|
|
3337
3134
|
* Settings panel styling. DSH semantic tokens (`--dsw-alias-*`) with neutral
|
|
3338
|
-
* fallbacks
|
|
3339
|
-
*
|
|
3135
|
+
* fallbacks, aliased once to local `--dc-*` variables, so the page follows
|
|
3136
|
+
* the active theme.
|
|
3340
3137
|
*
|
|
3341
3138
|
* @module dsh-channels/client/styles
|
|
3342
3139
|
*/
|
|
3343
3140
|
const STYLE_ATTRIBUTE = "data-dsh-channels-css";
|
|
3344
3141
|
const CSS = `
|
|
3345
|
-
.dsh-channels-cfg{container-type:inline-size;display:flex;flex-direction:column;gap:
|
|
3346
|
-
.dsh-channels-cfg__panelHead{position:sticky;top:0;z-index:2;display:flex;flex-direction:column;gap:4px;padding:10px 0
|
|
3142
|
+
.dsh-channels-cfg{--dc-fg:var(--dsw-alias-label-primary,#1f2328);--dc-fg2:var(--dsw-alias-label-secondary,#5c6169);--dc-fg3:var(--dsw-alias-label-tertiary,#8a8f98);--dc-dim:var(--dsw-alias-label-dimmed,#b0b4ba);--dc-bg:var(--dsw-alias-bg-base,#fff);--dc-line:var(--dsw-alias-border-l2,rgba(0,0,0,.12));--dc-hover:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.05));--dc-accent:var(--dsw-alias-state-business-primary,#0a67d3);--dc-ok:var(--dsw-alias-state-success-primary,#0a7d33);--dc-warn:var(--dsw-alias-state-warn-primary,#a15c00);--dc-bad:var(--dsw-alias-state-error-primary,#c62828);--dc-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;container-type:inline-size;display:flex;flex-direction:column;gap:14px;width:100%;max-width:720px;color:var(--dc-fg);font-size:13px;line-height:20px}
|
|
3143
|
+
.dsh-channels-cfg__panelHead{position:sticky;top:0;z-index:2;display:flex;flex-direction:column;gap:4px;padding:10px 0 6px;background:var(--dc-bg)}
|
|
3347
3144
|
.dsh-channels-cfg__headRow{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
3348
|
-
.dsh-channels-cfg__title{margin:0;font-size:
|
|
3349
|
-
.dsh-channels-
|
|
3350
|
-
.dsh-channels-cfg__tabs{display:flex;align-items:center;gap:2px;flex-wrap:wrap;border-bottom:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12))}
|
|
3351
|
-
.dsh-channels-cfg__tab{display:inline-flex;align-items:center;gap:5px;min-height:32px;padding:0 10px;border:0;border-bottom:2px solid transparent;background:transparent;color:var(--dsw-alias-label-secondary,#5c6169);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap}
|
|
3352
|
-
.dsh-channels-cfg__tab:hover{color:var(--dsw-alias-label-primary,#1f2328)}
|
|
3353
|
-
.dsh-channels-cfg__tab:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0a67d3);outline-offset:-2px}
|
|
3354
|
-
.dsh-channels-cfg__tab[aria-selected="true"]{color:var(--dsw-alias-state-business-primary,#0a67d3);border-bottom-color:currentColor;font-weight:500}
|
|
3355
|
-
.dsh-channels-cfg__tabDot{width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-state-warning-primary,#a15c00);flex:none}
|
|
3356
|
-
.dsh-channels-cfg__tabBody{display:flex;flex-direction:column;gap:12px}
|
|
3357
|
-
.dsh-channels-cfg__tabHelp{margin:0;color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:12px;line-height:18px}
|
|
3358
|
-
.dsh-channels-cfg__card{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:12px}
|
|
3359
|
-
.dsh-channels-cfg__cardHead{display:flex;align-items:center;gap:8px;min-height:20px;flex-wrap:wrap}
|
|
3360
|
-
.dsh-channels-cfg__cardTitle{font-size:13px;font-weight:500}
|
|
3361
|
-
.dsh-channels-cfg__cardMeta{margin-left:auto;color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:12px}
|
|
3362
|
-
.dsh-channels-cfg__group{display:flex;flex-direction:column;gap:8px}
|
|
3363
|
-
.dsh-channels-cfg__badge{display:inline-flex;align-items:center;gap:5px;padding:1px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));font-size:12px;line-height:18px;white-space:nowrap}
|
|
3364
|
-
.dsh-channels-cfg__dot{width:7px;height:7px;border-radius:50%;background:currentColor;flex:none}
|
|
3365
|
-
.dsh-channels-cfg__badge--ok{color:var(--dsw-alias-state-business-primary,#0a7d33);border-color:currentColor}
|
|
3366
|
-
.dsh-channels-cfg__badge--warn{color:var(--dsw-alias-state-warning-primary,#a15c00);border-color:currentColor}
|
|
3367
|
-
.dsh-channels-cfg__badge--bad{color:var(--dsw-alias-state-danger-primary,#c62828);border-color:currentColor}
|
|
3368
|
-
.dsh-channels-cfg__badge--muted{color:var(--dsw-alias-label-tertiary,#8a8f98)}
|
|
3369
|
-
.dsh-channels-cfg__facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px 16px}
|
|
3370
|
-
.dsh-channels-cfg__fact{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
3371
|
-
.dsh-channels-cfg__factLabel{color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:12px;line-height:16px}
|
|
3372
|
-
.dsh-channels-cfg__factValue{font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
|
|
3373
|
-
.dsh-channels-cfg__actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
3374
|
-
.dsh-channels-cfg__btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;min-height:28px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:8px;background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer;white-space:nowrap}
|
|
3375
|
-
.dsh-channels-cfg__btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.05))}
|
|
3376
|
-
.dsh-channels-cfg__btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0a67d3);outline-offset:2px}
|
|
3377
|
-
.dsh-channels-cfg__btn:disabled{color:var(--dsw-alias-label-dimmed,#b0b4ba);cursor:default}
|
|
3378
|
-
.dsh-channels-cfg__btn--primary{background:var(--dsw-alias-button-primary-fill,#0a67d3);border-color:transparent;color:var(--dsw-alias-label-primary-foreground,#fff)}
|
|
3379
|
-
.dsh-channels-cfg__btn--primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,#0b5cba)}
|
|
3380
|
-
.dsh-channels-cfg__btn--danger{color:var(--dsw-alias-state-danger-primary,#c62828);border-color:currentColor}
|
|
3381
|
-
.dsh-channels-cfg__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 20px;align-items:start}
|
|
3382
|
-
.dsh-channels-cfg__field{display:flex;flex-direction:column;gap:4px;min-width:0}
|
|
3383
|
-
.dsh-channels-cfg__field--wide{grid-column:1/-1}
|
|
3384
|
-
.dsh-channels-cfg__labelRow{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-width:0}
|
|
3385
|
-
.dsh-channels-cfg__label{font-size:12px;color:var(--dsw-alias-label-secondary,#5c6169)}
|
|
3386
|
-
.dsh-channels-cfg__tag{display:inline-flex;align-items:center;padding:0 5px;border-radius:4px;border:1px dashed currentColor;font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#8a8f98);white-space:nowrap}
|
|
3387
|
-
.dsh-channels-cfg__tag--restart{color:var(--dsw-alias-state-warning-primary,#a15c00)}
|
|
3388
|
-
.dsh-channels-cfg__tag--dirty{color:var(--dsw-alias-state-warning-primary,#a15c00);border-style:solid}
|
|
3389
|
-
.dsh-channels-cfg__help{color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:11px;line-height:16px}
|
|
3390
|
-
.dsh-channels-cfg__reasonBad{color:var(--dsw-alias-state-danger-primary,#c62828)}
|
|
3391
|
-
.dsh-channels-cfg__input{box-sizing:border-box;width:100%;min-height:28px;padding:3px 10px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:inherit;font:inherit;font-size:13px;outline:none}
|
|
3392
|
-
.dsh-channels-cfg__inputRow{display:flex;align-items:center;gap:8px;min-width:0}
|
|
3393
|
-
.dsh-channels-cfg__inputRow .dsh-channels-cfg__input{flex:1 1 auto;width:auto;min-width:0}
|
|
3394
|
-
.dsh-channels-cfg__btn--browse{flex:none}
|
|
3395
|
-
.dsh-channels-cfg__list{display:flex;flex-direction:column;gap:6px;min-width:0}
|
|
3396
|
-
.dsh-channels-cfg__listItem{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
3397
|
-
.dsh-channels-cfg__btn--listRemove{flex:none}
|
|
3398
|
-
.dsh-channels-cfg__input:focus{border-color:var(--dsw-alias-state-business-primary,#0a67d3)}
|
|
3399
|
-
.dsh-channels-cfg__input--invalid{border-color:var(--dsw-alias-state-danger-primary,#c62828)}
|
|
3400
|
-
select.dsh-channels-cfg__input{appearance:auto;cursor:pointer}
|
|
3401
|
-
.dsh-channels-cfg__check{display:inline-flex;align-items:center;gap:6px;min-height:28px;cursor:pointer;font-size:13px}
|
|
3402
|
-
.dsh-channels-cfg__checkbox{width:15px;height:15px;margin:0;accent-color:var(--dsw-alias-state-business-primary,#0a67d3);cursor:pointer}
|
|
3403
|
-
.dsh-channels-cfg__fieldError{color:var(--dsw-alias-state-danger-primary,#c62828);font-size:11px;line-height:16px}
|
|
3404
|
-
.dsh-channels-cfg__notice{display:flex;flex-direction:column;gap:2px;padding:8px 10px;border-radius:8px;font-size:12px;line-height:18px;overflow-wrap:anywhere}
|
|
3405
|
-
.dsh-channels-cfg__notice--ok{color:var(--dsw-alias-state-business-primary,#0a7d33);background:color-mix(in srgb,currentColor 8%,transparent)}
|
|
3406
|
-
.dsh-channels-cfg__notice--error{color:var(--dsw-alias-state-danger-primary,#c62828);background:color-mix(in srgb,currentColor 8%,transparent)}
|
|
3407
|
-
.dsh-channels-cfg__notice a{color:inherit;text-decoration:underline}
|
|
3408
|
-
.dsh-channels-cfg__scopeList{margin:2px 0;padding-left:18px;display:flex;flex-direction:column;gap:2px}
|
|
3409
|
-
.dsh-channels-cfg__banner{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 10px;border-radius:8px;border:1px dashed var(--dsw-alias-state-warning-primary,#a15c00);color:var(--dsw-alias-state-warning-primary,#a15c00);font-size:12px;line-height:18px}
|
|
3410
|
-
.dsh-channels-cfg__footer{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding-top:2px}
|
|
3145
|
+
.dsh-channels-cfg__title{margin:0 2px 0 0;font-size:17px;font-weight:600;line-height:26px}
|
|
3146
|
+
.dsh-channels-cfg__meta{color:var(--dc-fg3);font-size:12px}
|
|
3411
3147
|
.dsh-channels-cfg__spacer{flex:1}
|
|
3148
|
+
.dsh-channels-cfg__mono{font-family:var(--dc-mono);font-size:12px}
|
|
3149
|
+
.dsh-channels-cfg__pill{display:inline-flex;align-items:center;gap:5px;padding:0 8px;border-radius:999px;font-size:12px;line-height:20px;font-weight:400;white-space:nowrap;color:var(--dc-fg3);background:color-mix(in srgb,currentColor 10%,transparent)}
|
|
3150
|
+
.dsh-channels-cfg__pill--ok{color:var(--dc-ok)}
|
|
3151
|
+
.dsh-channels-cfg__pill--warn{color:var(--dc-warn)}
|
|
3152
|
+
.dsh-channels-cfg__pill--bad{color:var(--dc-bad)}
|
|
3153
|
+
.dsh-channels-cfg__dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
|
|
3154
|
+
.dsh-channels-cfg__tabs{display:flex;align-items:center;gap:18px;border-bottom:1px solid var(--dc-line);overflow-x:auto;scrollbar-width:none}
|
|
3155
|
+
.dsh-channels-cfg__tab{display:inline-flex;align-items:center;gap:5px;min-height:34px;padding:0;border:0;border-bottom:2px solid transparent;background:transparent;color:var(--dc-fg2);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap}
|
|
3156
|
+
.dsh-channels-cfg__tab:hover{color:var(--dc-fg)}
|
|
3157
|
+
.dsh-channels-cfg__tab[aria-selected="true"]{color:var(--dc-fg);border-bottom-color:currentColor;font-weight:600}
|
|
3158
|
+
.dsh-channels-cfg__tabCount{padding:0 5px;border-radius:8px;font-size:11px;line-height:16px;font-weight:500;color:var(--dc-warn);background:color-mix(in srgb,var(--dc-warn) 12%,transparent)}
|
|
3159
|
+
.dsh-channels-cfg__tabDot{width:6px;height:6px;border-radius:50%;background:var(--dc-accent);flex:none}
|
|
3160
|
+
.dsh-channels-cfg__tabBody{display:flex;flex-direction:column;gap:22px;padding:4px 0 8px}
|
|
3161
|
+
.dsh-channels-cfg__sec{display:flex;flex-direction:column;gap:6px;margin:0}
|
|
3162
|
+
.dsh-channels-cfg__secTitle{margin:0;font-size:12px;font-weight:600;line-height:18px;color:var(--dc-fg3);letter-spacing:.02em}
|
|
3163
|
+
.dsh-channels-cfg__secFoot{margin:0;color:var(--dc-fg3);font-size:12px;line-height:18px}
|
|
3164
|
+
.dsh-channels-cfg__list{display:flex;flex-direction:column;border:1px solid var(--dc-line);border-radius:10px}
|
|
3165
|
+
.dsh-channels-cfg__list>*+*{border-top:1px solid var(--dc-line)}
|
|
3166
|
+
.dsh-channels-cfg__list--warn{border-color:color-mix(in srgb,var(--dc-warn) 45%,transparent);background:color-mix(in srgb,var(--dc-warn) 7%,transparent)}
|
|
3167
|
+
.dsh-channels-cfg__row{display:flex;align-items:center;gap:16px;padding:11px 14px;min-height:28px}
|
|
3168
|
+
.dsh-channels-cfg__row--wide{flex-direction:column;align-items:stretch;gap:8px}
|
|
3169
|
+
.dsh-channels-cfg__rowText{display:flex;flex-direction:column;gap:1px;flex:1 1 auto;min-width:0}
|
|
3170
|
+
.dsh-channels-cfg__rowLabel{display:flex;align-items:center;gap:6px;flex-wrap:wrap;overflow-wrap:anywhere}
|
|
3171
|
+
.dsh-channels-cfg__rowSummary{color:var(--dc-fg3);font-size:12px;line-height:18px;overflow-wrap:anywhere}
|
|
3172
|
+
.dsh-channels-cfg__rowCtl{flex:none;display:flex;align-items:center;justify-content:flex-end;min-width:0;max-width:62%}
|
|
3173
|
+
.dsh-channels-cfg__row--wide .dsh-channels-cfg__rowCtl{max-width:none;justify-content:stretch}
|
|
3174
|
+
.dsh-channels-cfg__row--wide .dsh-channels-cfg__rowCtl>*{flex:1 1 auto}
|
|
3175
|
+
.dsh-channels-cfg__details{padding:0 14px 12px;color:var(--dc-fg2);font-size:12px;line-height:19px;overflow-wrap:anywhere}
|
|
3176
|
+
.dsh-channels-cfg__rowError{padding:0 14px 10px;color:var(--dc-bad);font-size:12px;line-height:18px}
|
|
3177
|
+
.dsh-channels-cfg__list>.dsh-channels-cfg__rowWrap{border-top-width:0}
|
|
3178
|
+
.dsh-channels-cfg__list>.dsh-channels-cfg__rowWrap+*{border-top-width:1px}
|
|
3179
|
+
.dsh-channels-cfg__tag{display:inline-flex;align-items:center;padding:0 5px;border-radius:4px;font-size:11px;line-height:16px;white-space:nowrap;color:var(--dc-warn);background:color-mix(in srgb,var(--dc-warn) 12%,transparent)}
|
|
3180
|
+
.dsh-channels-cfg__more{padding:0;border:0;background:none;color:var(--dc-fg3);font:inherit;font-size:12px;line-height:18px;cursor:pointer;text-decoration:underline dotted;text-underline-offset:3px;white-space:nowrap}
|
|
3181
|
+
.dsh-channels-cfg__more:hover{color:var(--dc-fg2)}
|
|
3182
|
+
.dsh-channels-cfg__link{padding:0;border:0;background:none;color:var(--dc-accent);font:inherit;font-size:13px;cursor:pointer;text-decoration:none;white-space:nowrap}
|
|
3183
|
+
.dsh-channels-cfg__link:disabled{color:var(--dc-dim);cursor:default}
|
|
3184
|
+
.dsh-channels-cfg__switch{position:relative;flex:none;width:34px;height:20px;padding:0;border:0;border-radius:10px;background:var(--dc-dim);cursor:pointer;transition:background .15s}
|
|
3185
|
+
.dsh-channels-cfg__switch::after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s}
|
|
3186
|
+
.dsh-channels-cfg__switch[aria-checked="true"]{background:var(--dc-accent)}
|
|
3187
|
+
.dsh-channels-cfg__switch[aria-checked="true"]::after{transform:translateX(14px)}
|
|
3188
|
+
.dsh-channels-cfg__switch:disabled{opacity:.5;cursor:default}
|
|
3189
|
+
.dsh-channels-cfg__input{box-sizing:border-box;width:100%;min-width:0;height:30px;padding:0 10px;border:1px solid var(--dc-line);border-radius:7px;background:var(--dsw-alias-bg-layer-1,transparent);color:inherit;font:inherit;font-size:13px;outline:none}
|
|
3190
|
+
.dsh-channels-cfg__input:focus{border-color:var(--dc-accent)}
|
|
3191
|
+
.dsh-channels-cfg__input--invalid{border-color:var(--dc-bad)}
|
|
3192
|
+
.dsh-channels-cfg__input--num{width:120px;text-align:right;font-variant-numeric:tabular-nums}
|
|
3193
|
+
.dsh-channels-cfg__input--text{width:260px}
|
|
3194
|
+
.dsh-channels-cfg__select{width:220px;appearance:auto;cursor:pointer}
|
|
3195
|
+
.dsh-channels-cfg__unit{min-width:36px;color:var(--dc-fg3);font-size:12px}
|
|
3196
|
+
.dsh-channels-cfg__bytes{color:var(--dc-fg3);font-size:12px;white-space:nowrap}
|
|
3197
|
+
.dsh-channels-cfg__inputRow{display:flex;align-items:center;gap:8px;min-width:0}
|
|
3198
|
+
.dsh-channels-cfg__inputRow>.dsh-channels-cfg__input:not(.dsh-channels-cfg__input--num){flex:1 1 auto;width:auto}
|
|
3199
|
+
.dsh-channels-cfg__stringList{display:flex;flex-direction:column;gap:6px;min-width:0}
|
|
3200
|
+
.dsh-channels-cfg__stringListItem{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
3201
|
+
.dsh-channels-cfg__fieldError{color:var(--dc-bad);font-size:12px;line-height:18px}
|
|
3202
|
+
.dsh-channels-cfg__textBad{color:var(--dc-bad)}
|
|
3203
|
+
.dsh-channels-cfg__btn{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:28px;padding:0 12px;border:1px solid var(--dc-line);border-radius:7px;background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;text-decoration:none}
|
|
3204
|
+
.dsh-channels-cfg__btn:hover:not(:disabled){background:var(--dc-hover)}
|
|
3205
|
+
.dsh-channels-cfg__btn:disabled{color:var(--dc-dim);cursor:default}
|
|
3206
|
+
.dsh-channels-cfg__btn--primary{background:var(--dsw-alias-button-primary-fill,var(--dc-accent));border-color:transparent;color:var(--dsw-alias-label-primary-foreground,#fff)}
|
|
3207
|
+
.dsh-channels-cfg__btn--primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,#0b5cba)}
|
|
3208
|
+
.dsh-channels-cfg__btn--primary:disabled{opacity:.45;color:var(--dsw-alias-label-primary-foreground,#fff)}
|
|
3209
|
+
.dsh-channels-cfg__btn--ghost{border-color:transparent;color:var(--dc-fg2)}
|
|
3210
|
+
.dsh-channels-cfg__btn--danger{color:var(--dc-bad)}
|
|
3211
|
+
.dsh-channels-cfg__btn--remove{color:var(--dc-bad);border-color:color-mix(in srgb,var(--dc-bad) 55%,transparent);background:color-mix(in srgb,var(--dc-bad) 8%,transparent)}
|
|
3212
|
+
.dsh-channels-cfg__btn--remove:hover:not(:disabled){background:color-mix(in srgb,var(--dc-bad) 15%,transparent)}
|
|
3213
|
+
.dsh-channels-cfg__btn--remove:disabled{opacity:.5;color:var(--dc-bad)}
|
|
3214
|
+
.dsh-channels-cfg__btn:focus-visible,.dsh-channels-cfg__tab:focus-visible,.dsh-channels-cfg__switch:focus-visible,.dsh-channels-cfg__link:focus-visible,.dsh-channels-cfg__more:focus-visible{outline:2px solid var(--dc-accent);outline-offset:2px}
|
|
3215
|
+
.dsh-channels-cfg__notice{padding:8px 12px;border-radius:8px;font-size:12px;line-height:18px;overflow-wrap:anywhere}
|
|
3216
|
+
.dsh-channels-cfg__notice--ok{color:var(--dc-ok);background:color-mix(in srgb,var(--dc-ok) 9%,transparent)}
|
|
3217
|
+
.dsh-channels-cfg__notice--error{color:var(--dc-bad);background:color-mix(in srgb,var(--dc-bad) 8%,transparent)}
|
|
3218
|
+
.dsh-channels-cfg__banner{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:9px 12px;border-radius:8px;background:color-mix(in srgb,var(--dc-warn) 12%,transparent);color:var(--dc-fg);font-size:12px;line-height:18px}
|
|
3219
|
+
.dsh-channels-cfg__bannerText{flex:1;min-width:0}
|
|
3220
|
+
.dsh-channels-cfg__hero{display:flex;flex-direction:column;gap:12px;padding:14px 16px;border:1px solid var(--dc-line);border-radius:10px}
|
|
3221
|
+
.dsh-channels-cfg__heroTop{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
|
3222
|
+
.dsh-channels-cfg__heroTop strong{font-size:14px;font-weight:600}
|
|
3223
|
+
.dsh-channels-cfg__stats{display:flex;gap:28px;flex-wrap:wrap}
|
|
3224
|
+
.dsh-channels-cfg__stat{display:flex;flex-direction:column}
|
|
3225
|
+
.dsh-channels-cfg__stat span{color:var(--dc-fg3);font-size:12px}
|
|
3226
|
+
.dsh-channels-cfg__stat strong{font-size:18px;font-weight:600;line-height:26px;font-variant-numeric:tabular-nums}
|
|
3227
|
+
.dsh-channels-cfg__stat strong.dsh-channels-cfg__mono{font-size:14px}
|
|
3228
|
+
.dsh-channels-cfg__checkIcon{display:grid;place-items:center;flex:none;width:18px;height:18px;border-radius:50%;font-size:11px;font-weight:700;align-self:flex-start;margin-top:1px}
|
|
3229
|
+
.dsh-channels-cfg__checkIcon--ok{color:var(--dc-ok);background:color-mix(in srgb,var(--dc-ok) 12%,transparent)}
|
|
3230
|
+
.dsh-channels-cfg__checkIcon--warn{color:var(--dc-warn);background:color-mix(in srgb,var(--dc-warn) 14%,transparent)}
|
|
3231
|
+
.dsh-channels-cfg__checkIcon--bad{color:var(--dc-bad);background:color-mix(in srgb,var(--dc-bad) 12%,transparent)}
|
|
3232
|
+
.dsh-channels-cfg__scopeList{margin:0;padding:0 14px 12px 46px;list-style:none;display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--dc-fg2)}
|
|
3233
|
+
.dsh-channels-cfg__scopeList li{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
|
|
3234
|
+
.dsh-channels-cfg__scopeList .dsh-channels-cfg__link{font-size:12px}
|
|
3235
|
+
.dsh-channels-cfg__list>.dsh-channels-cfg__scopeList{border-top-width:0}
|
|
3236
|
+
.dsh-channels-cfg__avatar{display:grid;place-items:center;flex:none;width:26px;height:26px;border-radius:50%;background:var(--dc-hover);color:var(--dc-fg2);font-size:12px}
|
|
3237
|
+
.dsh-channels-cfg__addRow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:10px 14px}
|
|
3238
|
+
.dsh-channels-cfg__addRow .dsh-channels-cfg__input{flex:1 1 160px;width:auto}
|
|
3239
|
+
.dsh-channels-cfg__emptyRow{padding:11px 14px;color:var(--dc-fg3);font-size:12px}
|
|
3240
|
+
.dsh-channels-cfg__saveBar{position:sticky;bottom:0;z-index:2;display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:10px 0;border-top:1px solid var(--dc-line);background:var(--dc-bg)}
|
|
3241
|
+
.dsh-channels-cfg__saveMsg{flex:1;min-width:0;color:var(--dc-fg2);font-size:12px}
|
|
3412
3242
|
.dsh-channels-cfg__steps{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}
|
|
3413
3243
|
.dsh-channels-cfg__step{display:flex;align-items:flex-start;gap:10px}
|
|
3414
|
-
.dsh-channels-cfg__stepIndex{display:
|
|
3244
|
+
.dsh-channels-cfg__stepIndex{display:grid;place-items:center;flex:none;width:20px;height:20px;border-radius:50%;color:var(--dc-accent);background:color-mix(in srgb,var(--dc-accent) 12%,transparent);font-size:12px;font-variant-numeric:tabular-nums}
|
|
3415
3245
|
.dsh-channels-cfg__stepBody{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
3416
|
-
.dsh-channels-cfg__stepTitle{font-
|
|
3417
|
-
.dsh-channels-
|
|
3418
|
-
.dsh-channels-
|
|
3419
|
-
.dsh-channels-
|
|
3420
|
-
.dsh-channels-cfg__kvKey{color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:12px;line-height:16px}
|
|
3421
|
-
.dsh-channels-cfg__kvValue{overflow-wrap:anywhere}
|
|
3422
|
-
.dsh-channels-cfg__mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}
|
|
3423
|
-
.dsh-channels-cfg__cred{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
3424
|
-
.dsh-channels-cfg__cred .dsh-channels-cfg__input{flex:1 1 240px;min-width:0;width:auto}
|
|
3425
|
-
.dsh-channels-cfg__accessSection{display:flex;flex-direction:column;gap:6px}
|
|
3426
|
-
.dsh-channels-cfg__accessList{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
|
|
3427
|
-
.dsh-channels-cfg__accessRow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:6px 8px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:8px}
|
|
3428
|
-
.dsh-channels-cfg__accessMain{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1 1 200px;overflow-wrap:anywhere}
|
|
3429
|
-
.dsh-channels-cfg__accessMeta{color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:11px;line-height:16px;overflow-wrap:anywhere}
|
|
3430
|
-
.dsh-channels-cfg__accessActions{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
3431
|
-
.dsh-channels-cfg__accessAdd{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
3432
|
-
.dsh-channels-cfg__accessInput{flex:1 1 140px;min-width:0;width:auto}
|
|
3433
|
-
.dsh-channels-cfg__error{padding:10px 12px;border:1px solid var(--dsw-alias-state-danger-primary,#c62828);border-radius:10px;color:var(--dsw-alias-state-danger-primary,#c62828);font-size:12px;line-height:18px;display:flex;flex-direction:column;gap:8px}
|
|
3434
|
-
.dsh-channels-cfg__loading{padding:16px 0;color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:12px}
|
|
3435
|
-
.dsh-channels-cfg__issues{margin:0;padding-left:18px}
|
|
3246
|
+
.dsh-channels-cfg__stepTitle{font-weight:500}
|
|
3247
|
+
.dsh-channels-cfg__error{padding:10px 12px;border:1px solid var(--dc-bad);border-radius:10px;color:var(--dc-bad);font-size:12px;line-height:18px;display:flex;flex-direction:column;gap:8px}
|
|
3248
|
+
.dsh-channels-cfg__loading{padding:16px 0;color:var(--dc-fg3);font-size:12px}
|
|
3249
|
+
@media (prefers-reduced-motion:reduce){.dsh-channels-cfg__switch,.dsh-channels-cfg__switch::after{transition:none}}
|
|
3436
3250
|
@container (max-width:560px){
|
|
3437
|
-
.dsh-channels-
|
|
3438
|
-
.dsh-channels-
|
|
3439
|
-
.dsh-channels-
|
|
3251
|
+
.dsh-channels-cfg__row:not(.dsh-channels-cfg__row--wide){flex-wrap:wrap}
|
|
3252
|
+
.dsh-channels-cfg__rowCtl{max-width:100%}
|
|
3253
|
+
.dsh-channels-cfg__select,.dsh-channels-cfg__input--text{width:100%}
|
|
3440
3254
|
}
|
|
3441
3255
|
`;
|
|
3442
3256
|
/** Append the panel stylesheet once (idempotent across HMR / remounts). */
|