@modesto_ma/dsh-channels 0.3.0 → 0.5.0-beta.1
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 +123 -53
- package/docs/access-control-design.md +7 -5
- package/docs/attachment-pipeline-v2.md +310 -0
- package/docs/feasibility-and-design.md +1 -1
- package/docs/feishu-onboarding.md +127 -21
- package/docs/persistence.md +6 -4
- package/docs/refactor-plan.md +2 -2
- package/docs/workspace-isolation-design.md +13 -11
- package/lib/client.js +179 -288
- package/lib/client.js.map +1 -1
- package/lib/index.js +12125 -6382
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/access/cards.ts +9 -9
- package/src/access/controller.ts +10 -10
- package/src/access/index.ts +1 -0
- package/src/access/policy.ts +10 -10
- package/src/access/requests.ts +32 -31
- package/src/access/store.ts +12 -12
- package/src/access/targets.ts +56 -0
- package/src/cards/approval.ts +96 -26
- package/src/cards/index.ts +1 -0
- package/src/cards/onboarding.ts +1 -0
- package/src/cards/picker.ts +115 -0
- package/src/cards/streaming.ts +56 -7
- package/src/client/api.ts +5 -25
- package/src/client/panel/access-card.tsx +24 -15
- package/src/client/panel/access-tab.tsx +3 -3
- package/src/client/panel/connection-tab.tsx +70 -1
- package/src/client/panel/index.tsx +11 -62
- package/src/client/panel/shared.tsx +0 -23
- package/src/client/panel/workspace-tab.tsx +2 -18
- package/src/client/styles.ts +2 -0
- package/src/commands/registry.ts +2 -2
- package/src/config.ts +190 -32
- package/src/domain.ts +28 -18
- package/src/feishu/agent-context.ts +65 -19
- package/src/feishu/inbound-attachments.ts +440 -3
- package/src/feishu/manifest.ts +247 -0
- package/src/feishu/message-read.ts +1330 -0
- package/src/feishu/native-attachments.ts +998 -0
- package/src/feishu/scopes.ts +171 -0
- package/src/feishu/send-file.ts +543 -0
- package/src/feishu/slash-commands.ts +134 -0
- package/src/feishu/tools.ts +2305 -0
- package/src/feishu-channel.ts +322 -6
- package/src/host/agent.ts +55 -0
- package/src/host/attachments.ts +210 -0
- package/src/host/index.ts +1 -0
- package/src/index.ts +5 -4
- package/src/interactions/approvals.ts +187 -7
- package/src/interactions/pending.ts +3 -3
- package/src/routing/inbound.ts +620 -49
- package/src/service.ts +808 -375
- package/src/session/manager.ts +67 -13
- package/src/session/routes.ts +2 -3
- package/src/settings/contract.ts +90 -89
- package/src/settings/overlay.ts +124 -13
- package/src/settings/routes.ts +3 -17
- package/src/settings/surface.ts +156 -82
- package/src/streamer.ts +94 -5
- package/src/workspaces.ts +39 -26
- package/src/cleanup.ts +0 -535
- package/src/client/panel/cleanup-card.tsx +0 -106
package/lib/client.js
CHANGED
|
@@ -28,11 +28,9 @@ window.__ModuleLoader__.load({
|
|
|
28
28
|
const RECONNECT_ROUTE = "/dsh-channels/reconnect";
|
|
29
29
|
/** 模型/推理档位/预设选择器的宿主目录(provider → model → effort 级联刷新)。 */
|
|
30
30
|
const CATALOG_ROUTE = "/dsh-channels/catalog";
|
|
31
|
-
/** 手动执行一次过期目录清理(POST,无请求体)。 */
|
|
32
|
-
const CLEANUP_ROUTE = "/dsh-channels/cleanup";
|
|
33
31
|
/** 群白名单增删(POST/DELETE,body `{ chatId, name? }` / `{ chatId }`)。 */
|
|
34
32
|
const ACCESS_GROUPS_ROUTE = "/dsh-channels/access/groups";
|
|
35
|
-
/** 用户白名单增删(POST/DELETE,body `{
|
|
33
|
+
/** 用户白名单增删(POST/DELETE,body `{ userId, name? }` / `{ userId }`)。 */
|
|
36
34
|
const ACCESS_USERS_ROUTE = "/dsh-channels/access/users";
|
|
37
35
|
/** 结算一条待处理授权申请(POST,body `{ token, decision }`)。 */
|
|
38
36
|
const ACCESS_REQUESTS_ROUTE = "/dsh-channels/access/requests";
|
|
@@ -78,7 +76,11 @@ window.__ModuleLoader__.load({
|
|
|
78
76
|
/** Which editable field groups each tab renders (the tabs own one or two). */
|
|
79
77
|
const TAB_FIELD_GROUPS = {
|
|
80
78
|
connection: [],
|
|
81
|
-
sessions: [
|
|
79
|
+
sessions: [
|
|
80
|
+
"behavior",
|
|
81
|
+
"content",
|
|
82
|
+
"tools"
|
|
83
|
+
],
|
|
82
84
|
model: ["model"],
|
|
83
85
|
workspace: ["workspace"],
|
|
84
86
|
access: ["access"],
|
|
@@ -89,6 +91,7 @@ window.__ModuleLoader__.load({
|
|
|
89
91
|
workspace: "工作区与文件",
|
|
90
92
|
behavior: "行为",
|
|
91
93
|
content: "内容与附件",
|
|
94
|
+
tools: "模型工具",
|
|
92
95
|
model: "模型与推理",
|
|
93
96
|
streaming: "流式卡片与日志",
|
|
94
97
|
access: "准入与授权"
|
|
@@ -111,6 +114,21 @@ window.__ModuleLoader__.load({
|
|
|
111
114
|
statusSummaryConnecting: "正在连接飞书长连接…",
|
|
112
115
|
statusSummaryError: "最近一次连接飞书失败,请检查凭据与网络后重试。",
|
|
113
116
|
lastError: "最近一次连接失败",
|
|
117
|
+
scopeCheckTitle: "权限自检",
|
|
118
|
+
scopeCheckOk: "已开通插件需要的全部飞书权限。",
|
|
119
|
+
scopeCheckMissing: "以下飞书权限未开通,开通后需在开发者后台发布新版本:",
|
|
120
|
+
scopeCheckOpenAll: "在开发者后台开通全部缺失权限",
|
|
121
|
+
scopeCheckOpenOne: "去开通",
|
|
122
|
+
scopeCheckOptional: "可选",
|
|
123
|
+
scopeCheckRequired: "必需",
|
|
124
|
+
scopeCheckError: "无法读取应用的权限授予状态:",
|
|
125
|
+
slashCommandSyncTitle: "斜杠指令面板",
|
|
126
|
+
slashCommandSyncOk: "插件命令已同步到飞书原生斜杠指令面板,在聊天框输入 / 即可选择(飞书客户端约 5 分钟后刷新)。",
|
|
127
|
+
slashCommandSyncTotal: "命令数",
|
|
128
|
+
slashCommandSyncCreated: "本次新增",
|
|
129
|
+
slashCommandSyncUpdated: "本次更新",
|
|
130
|
+
slashCommandSyncRemoved: "本次删除",
|
|
131
|
+
slashCommandSyncError: "同步飞书原生斜杠指令面板失败(不影响直接输入命令):",
|
|
114
132
|
missingConfig: "缺少配置",
|
|
115
133
|
statusHintUnconfigured: "配置完成后插件会自动连接;也可以稍后在本页手动连接。",
|
|
116
134
|
connectionInfo: "连接信息",
|
|
@@ -156,9 +174,9 @@ window.__ModuleLoader__.load({
|
|
|
156
174
|
appSecretPlaceholder: "输入 App Secret",
|
|
157
175
|
credentialsPendingHint: "填写后点「保存凭据」;两项齐全时插件会自动连接飞书。",
|
|
158
176
|
connectionTabHelp: "连接状态与飞书应用凭据。凭据不完整时不会连接飞书。",
|
|
159
|
-
sessionsTabHelp: "
|
|
177
|
+
sessionsTabHelp: "私聊、群聊触发方式与入站内容:附件存储/预取、模型工具开关与读取预算;「需重连」项在下次连接时读取。",
|
|
160
178
|
modelTabHelp: "飞书会话使用的模型服务商、模型、推理档位与 Agent 预设;下拉选项来自宿主机实时目录,保存后对新会话生效。",
|
|
161
|
-
workspaceTabHelp: "
|
|
179
|
+
workspaceTabHelp: "每个用户一个工作区(模型/工具产生文件的落点),每个话题在其中拥有独立工作目录;只影响之后新建的话题。入站附件默认存于 DSH 原生附件库,不占用话题目录。插件不会自动删除工作区或话题目录;过期目录可手动删除,话题下次对话时会重建空目录。",
|
|
162
180
|
accessTabHelp: "准入采用 fail-closed:管理员不受白名单限制,非管理员私聊会被拒绝,群聊需要「群已授权 + 用户在白名单」两层同时命中。",
|
|
163
181
|
advancedTabHelp: "流式刷新等低层参数、Web 覆盖文件与只读信息。",
|
|
164
182
|
save: "保存更改",
|
|
@@ -204,35 +222,11 @@ window.__ModuleLoader__.load({
|
|
|
204
222
|
advancedCard: "只读与高级信息",
|
|
205
223
|
advancedHelp: "这些值来自 profile 配置,不能在面板里编辑。",
|
|
206
224
|
readOnlyCard: "部署级参数",
|
|
207
|
-
cleanupCard: "目录清理",
|
|
208
|
-
cleanupCardHelp: "删除超过保留期且不再活跃的飞书话题目录(以及旧版本按话题建立的工作区目录);用户工作区本身、DSH 会话日志都不会删除。删除不可恢复:相关旧话题会失效并提示已过期。",
|
|
209
|
-
cleanupRetention: "保留期",
|
|
210
|
-
cleanupAutoLabel: "自动清理",
|
|
211
|
-
cleanupCandidates: "可清理目录",
|
|
212
|
-
cleanupWorkspaces: "插件工作区",
|
|
213
|
-
cleanupCandidateBytes: "预计释放空间",
|
|
214
|
-
cleanupRetentionOff: "关闭(不删除)",
|
|
215
|
-
cleanupAutoOn: "已开启(启动时 + 每天一次)",
|
|
216
|
-
cleanupAutoOff: "已关闭(仅手动执行)",
|
|
217
|
-
cleanupAutoInactive: "未执行(保留期为关闭)",
|
|
218
|
-
cleanupNow: "立即清理",
|
|
219
|
-
cleanupRunning: "清理中…",
|
|
220
|
-
cleanupConfirm: "确定清理过期目录吗?该操作不可恢复,会删除这些目录内的全部文件(包括 attachments/ 附件)。",
|
|
221
|
-
cleanupConfirmHint: "删除前会再次确认;保留期为「关闭」或没有过期目录时按钮不可用。",
|
|
222
|
-
cleanupDisabledOff: "保留策略为「关闭」:请先在上方「过期目录保留期」中选择保留天数。",
|
|
223
|
-
cleanupDisabledEmpty: "没有超过保留期的过期目录。",
|
|
224
|
-
cleanupPreviewFailed: "无法读取清理预览",
|
|
225
|
-
cleanupDone: "清理完成",
|
|
226
|
-
cleanupFailed: "清理失败",
|
|
227
|
-
cleanupDeleted: "已删除工作区",
|
|
228
|
-
cleanupDeletedDirs: "话题目录",
|
|
229
|
-
cleanupRetired: "已清理话题路由",
|
|
230
|
-
cleanupFreed: "释放空间",
|
|
231
|
-
cleanupFailures: "以下目录未能删除:",
|
|
232
225
|
accessCard: "访问与授权",
|
|
233
|
-
accessCardHelp: "准入采用 fail-closed:管理员不受白名单限制(私聊、任意群聊都可直接对话);非管理员私聊会被拒绝,群聊需要「群已授权 + 用户在白名单」两层同时命中。管理员与准入模式在上方「准入与授权」中编辑;私聊机器人发送 /userid 获取自己的
|
|
226
|
+
accessCardHelp: "准入采用 fail-closed:管理员不受白名单限制(私聊、任意群聊都可直接对话);非管理员私聊会被拒绝,群聊需要「群已授权 + 用户在白名单」两层同时命中。管理员与准入模式在上方「准入与授权」中编辑;私聊机器人发送 /userid 获取自己的 user_id。",
|
|
234
227
|
accessModeOpenWarning: "当前准入模式为开放(open):应用可用范围内的任何人都能开启对话。该模式不安全,仅建议调试时使用。",
|
|
235
|
-
accessAdminMissingWarning: "当前准入=guarded
|
|
228
|
+
accessAdminMissingWarning: "当前准入=guarded 但未配置管理员,也无法自动使用应用所有者,所有对话都会被拒绝。请在上方「准入与授权」填写管理员 user_id(私聊机器人发送 /userid 获取;私聊模式关闭时 /userid 不可用,可在飞书管理后台的成员详情里查看用户 ID)。",
|
|
229
|
+
accessAdminOwnerNotice: "未配置管理员,已自动使用应用所有者作为管理员:",
|
|
236
230
|
accessGroupsTitle: "已授权群",
|
|
237
231
|
accessGroupsEmpty: "尚未授权任何群。机器人被拉进群后,管理员会收到带 chat_id 的群信息卡;也可在群内发送 /groupid 获取。",
|
|
238
232
|
accessUsersTitle: "已授权用户",
|
|
@@ -241,7 +235,7 @@ window.__ModuleLoader__.load({
|
|
|
241
235
|
accessRequestsEmpty: "没有待处理的授权申请。",
|
|
242
236
|
accessGroupChatIdPlaceholder: "群 chat_id(oc_…)",
|
|
243
237
|
accessGroupNamePlaceholder: "群名(可选)",
|
|
244
|
-
|
|
238
|
+
accessUserIdPlaceholder: "用户 user_id",
|
|
245
239
|
accessUserNamePlaceholder: "昵称(可选)",
|
|
246
240
|
accessAddGroup: "添加群",
|
|
247
241
|
accessAddUser: "添加用户",
|
|
@@ -263,7 +257,7 @@ window.__ModuleLoader__.load({
|
|
|
263
257
|
accessRequestAllowed: "已授权",
|
|
264
258
|
accessRequestDenied: "已忽略",
|
|
265
259
|
accessHints: "三种授权入口:群内 /auth 命令、入群与用户授权卡、本面板。撤销立即生效;白名单只约束非管理员(管理员在任何私聊/群聊都直接放行),非管理员需要群和用户两层同时命中。",
|
|
266
|
-
accessChatIdHint: "chat_id 可从入群群信息卡或群内 /groupid 获取;
|
|
260
|
+
accessChatIdHint: "chat_id 可从入群群信息卡或群内 /groupid 获取;user_id 可私聊机器人发送 /userid 获取。"
|
|
267
261
|
};
|
|
268
262
|
//#endregion
|
|
269
263
|
//#region src/client/api.ts
|
|
@@ -373,24 +367,6 @@ window.__ModuleLoader__.load({
|
|
|
373
367
|
return JSON.parse(text);
|
|
374
368
|
}
|
|
375
369
|
/**
|
|
376
|
-
* Run one workspace cleanup. No request body: the Host derives the candidate
|
|
377
|
-
* set from the effective retention config. Irreversible, so the caller asks
|
|
378
|
-
* for confirmation first; partial failures come back inside `result.failures`.
|
|
379
|
-
*/
|
|
380
|
-
async function runCleanup() {
|
|
381
|
-
const response = await request(CLEANUP_ROUTE, { method: "POST" });
|
|
382
|
-
const text = await response.text();
|
|
383
|
-
if (!response.ok) try {
|
|
384
|
-
return JSON.parse(text);
|
|
385
|
-
} catch {
|
|
386
|
-
return {
|
|
387
|
-
ok: false,
|
|
388
|
-
error: errorFromBody(response.status, text)
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
return JSON.parse(text);
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
370
|
* Ask the Host to open its native directory chooser (the panel's「选择目录…」
|
|
395
371
|
* button). The request stays open while the OS dialog does; a cancel comes back
|
|
396
372
|
* as `{ ok: true, canceled: true }`, not as an error.
|
|
@@ -476,16 +452,16 @@ window.__ModuleLoader__.load({
|
|
|
476
452
|
async function removeAccessGroup(chatId) {
|
|
477
453
|
return mutateAccess(ACCESS_GROUPS_ROUTE, "DELETE", { chatId });
|
|
478
454
|
}
|
|
479
|
-
/** Authorize one user
|
|
480
|
-
async function addAccessUser(
|
|
481
|
-
return mutateAccess(ACCESS_USERS_ROUTE, "POST", name === void 0 || name === "" ? {
|
|
482
|
-
|
|
455
|
+
/** Authorize one user `user_id` (global across authorized groups). */
|
|
456
|
+
async function addAccessUser(userId, name) {
|
|
457
|
+
return mutateAccess(ACCESS_USERS_ROUTE, "POST", name === void 0 || name === "" ? { userId } : {
|
|
458
|
+
userId,
|
|
483
459
|
name
|
|
484
460
|
});
|
|
485
461
|
}
|
|
486
462
|
/** Revoke one user authorization (takes effect on the next message). */
|
|
487
|
-
async function removeAccessUser(
|
|
488
|
-
return mutateAccess(ACCESS_USERS_ROUTE, "DELETE", {
|
|
463
|
+
async function removeAccessUser(userId) {
|
|
464
|
+
return mutateAccess(ACCESS_USERS_ROUTE, "DELETE", { userId });
|
|
489
465
|
}
|
|
490
466
|
/** Settle one pending authorization request (same resolver as the Feishu card). */
|
|
491
467
|
async function resolveAccessRequest(token, decision) {
|
|
@@ -601,25 +577,6 @@ window.__ModuleLoader__.load({
|
|
|
601
577
|
function messageOf(error) {
|
|
602
578
|
return error instanceof Error ? error.message : String(error);
|
|
603
579
|
}
|
|
604
|
-
/** Human-readable byte size for the cleanup preview/result (base-2 units). */
|
|
605
|
-
function formatBytes(bytes) {
|
|
606
|
-
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
607
|
-
const units = [
|
|
608
|
-
"B",
|
|
609
|
-
"KB",
|
|
610
|
-
"MB",
|
|
611
|
-
"GB",
|
|
612
|
-
"TB"
|
|
613
|
-
];
|
|
614
|
-
let value = bytes;
|
|
615
|
-
let unit = 0;
|
|
616
|
-
while (value >= 1024 && unit < units.length - 1) {
|
|
617
|
-
value /= 1024;
|
|
618
|
-
unit += 1;
|
|
619
|
-
}
|
|
620
|
-
const digits = unit === 0 || value >= 10 ? 0 : 1;
|
|
621
|
-
return `${value.toFixed(digits)} ${units[unit] ?? "B"}`;
|
|
622
|
-
}
|
|
623
580
|
/** Compact duration for the connection card. */
|
|
624
581
|
function formatUptime(ms) {
|
|
625
582
|
if (ms === void 0 || !Number.isFinite(ms)) return PANEL_COPY.unknownValue;
|
|
@@ -990,10 +947,6 @@ window.__ModuleLoader__.load({
|
|
|
990
947
|
};
|
|
991
948
|
}
|
|
992
949
|
}
|
|
993
|
-
/** Retention label, reusing the Host field options so the copy stays on the Host. */
|
|
994
|
-
function retentionLabel(panel, cleanup) {
|
|
995
|
-
return panel.config.fields.find((item) => item.key === "workspaceRetention")?.options?.find((option) => option.value === cleanup.retention)?.label ?? (cleanup.retention === "off" ? PANEL_COPY.cleanupRetentionOff : cleanup.retention);
|
|
996
|
-
}
|
|
997
950
|
//#endregion
|
|
998
951
|
//#region src/client/panel/access-card.tsx
|
|
999
952
|
/**
|
|
@@ -1025,7 +978,7 @@ window.__ModuleLoader__.load({
|
|
|
1025
978
|
const { access } = panel;
|
|
1026
979
|
const [groupChatId, setGroupChatId] = (0, react.useState)("");
|
|
1027
980
|
const [groupName, setGroupName] = (0, react.useState)("");
|
|
1028
|
-
const [
|
|
981
|
+
const [userUserId, setUserUserId] = (0, react.useState)("");
|
|
1029
982
|
const [userName, setUserName] = (0, react.useState)("");
|
|
1030
983
|
const groups = access.groups;
|
|
1031
984
|
const users = access.users;
|
|
@@ -1040,9 +993,9 @@ window.__ModuleLoader__.load({
|
|
|
1040
993
|
}
|
|
1041
994
|
};
|
|
1042
995
|
const submitUser = async () => {
|
|
1043
|
-
if (
|
|
1044
|
-
if (await onAddUser(
|
|
1045
|
-
|
|
996
|
+
if (userUserId.trim() === "") return;
|
|
997
|
+
if (await onAddUser(userUserId, userName)) {
|
|
998
|
+
setUserUserId("");
|
|
1046
999
|
setUserName("");
|
|
1047
1000
|
}
|
|
1048
1001
|
};
|
|
@@ -1067,9 +1020,16 @@ window.__ModuleLoader__.load({
|
|
|
1067
1020
|
className: "dsh-channels-cfg__banner",
|
|
1068
1021
|
children: PANEL_COPY.accessModeOpenWarning
|
|
1069
1022
|
}) : null,
|
|
1070
|
-
access.loaded && access.mode === "guarded" && !access.admin.configured ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1023
|
+
access.loaded && access.mode === "guarded" && !access.admin.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1071
1024
|
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--error",
|
|
1072
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: PANEL_COPY.accessAdminMissingWarning })
|
|
1025
|
+
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 })]
|
|
1026
|
+
}) : null,
|
|
1027
|
+
access.mode === "guarded" && access.admin.source === "owner" && access.admin.userId !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1028
|
+
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--ok",
|
|
1029
|
+
children: [PANEL_COPY.accessAdminOwnerNotice, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1030
|
+
className: "dsh-channels-cfg__mono",
|
|
1031
|
+
children: access.admin.userId
|
|
1032
|
+
})]
|
|
1073
1033
|
}) : null,
|
|
1074
1034
|
!access.loaded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, { text: PANEL_COPY.accessNotLoaded }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1075
1035
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1166,21 +1126,21 @@ window.__ModuleLoader__.load({
|
|
|
1166
1126
|
className: "dsh-channels-cfg__accessRow",
|
|
1167
1127
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1168
1128
|
className: "dsh-channels-cfg__accessMain",
|
|
1169
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: user.name ?? user.
|
|
1129
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: user.name ?? user.userId }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1170
1130
|
className: "dsh-channels-cfg__accessMeta",
|
|
1171
1131
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1172
1132
|
className: "dsh-channels-cfg__mono",
|
|
1173
|
-
children: user.
|
|
1133
|
+
children: user.userId
|
|
1174
1134
|
}), ` · ${accessSourceLabel(user.source)} · ${formatAccessDate(user.addedAt)}`]
|
|
1175
1135
|
})]
|
|
1176
1136
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1177
1137
|
type: "button",
|
|
1178
1138
|
className: "dsh-channels-cfg__btn",
|
|
1179
1139
|
disabled,
|
|
1180
|
-
onClick: () => onRemoveUser(user.
|
|
1140
|
+
onClick: () => onRemoveUser(user.userId),
|
|
1181
1141
|
children: working ? PANEL_COPY.accessWorking : PANEL_COPY.accessRemove
|
|
1182
1142
|
})]
|
|
1183
|
-
}, user.
|
|
1143
|
+
}, user.userId))
|
|
1184
1144
|
}),
|
|
1185
1145
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1186
1146
|
className: "dsh-channels-cfg__accessAdd",
|
|
@@ -1188,11 +1148,11 @@ window.__ModuleLoader__.load({
|
|
|
1188
1148
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1189
1149
|
type: "text",
|
|
1190
1150
|
className: "dsh-channels-cfg__input dsh-channels-cfg__accessInput",
|
|
1191
|
-
value:
|
|
1192
|
-
placeholder: PANEL_COPY.
|
|
1193
|
-
"aria-label": PANEL_COPY.
|
|
1151
|
+
value: userUserId,
|
|
1152
|
+
placeholder: PANEL_COPY.accessUserIdPlaceholder,
|
|
1153
|
+
"aria-label": PANEL_COPY.accessUserIdPlaceholder,
|
|
1194
1154
|
disabled,
|
|
1195
|
-
onChange: (event) =>
|
|
1155
|
+
onChange: (event) => setUserUserId(event.target.value)
|
|
1196
1156
|
}),
|
|
1197
1157
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1198
1158
|
type: "text",
|
|
@@ -1206,7 +1166,7 @@ window.__ModuleLoader__.load({
|
|
|
1206
1166
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1207
1167
|
type: "button",
|
|
1208
1168
|
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--primary",
|
|
1209
|
-
disabled: disabled ||
|
|
1169
|
+
disabled: disabled || userUserId.trim() === "",
|
|
1210
1170
|
onClick: () => {
|
|
1211
1171
|
submitUser();
|
|
1212
1172
|
},
|
|
@@ -1297,7 +1257,7 @@ window.__ModuleLoader__.load({
|
|
|
1297
1257
|
//#endregion
|
|
1298
1258
|
//#region src/client/panel/access-tab.tsx
|
|
1299
1259
|
/**
|
|
1300
|
-
* 「访问授权」tab:准入与授权字段表单(管理员
|
|
1260
|
+
* 「访问授权」tab:准入与授权字段表单(管理员 user_id / 准入模式 /
|
|
1301
1261
|
* 申请有效期)+ 白名单与待处理申请卡。表单保存走全局 draft,白名单
|
|
1302
1262
|
* 增删是独立即时 mutation。
|
|
1303
1263
|
*
|
|
@@ -1582,6 +1542,7 @@ window.__ModuleLoader__.load({
|
|
|
1582
1542
|
* - error:连接信息卡 + 最近一次连接失败 + 「重试」
|
|
1583
1543
|
* - connected:连接信息卡 + 「重启飞书连接」+ 凭据卡
|
|
1584
1544
|
* - connecting:禁用按钮显示「连接中…」
|
|
1545
|
+
* - 已连接且权限自检完成:权限自检卡(缺失权限红字 + 开发者后台深链)
|
|
1585
1546
|
*
|
|
1586
1547
|
* @module dsh-channels/client/panel/connection-tab
|
|
1587
1548
|
*/
|
|
@@ -1602,6 +1563,8 @@ window.__ModuleLoader__.load({
|
|
|
1602
1563
|
busy,
|
|
1603
1564
|
onReconnect
|
|
1604
1565
|
}),
|
|
1566
|
+
status.scopeCheck === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScopeCheckCard, { check: status.scopeCheck }),
|
|
1567
|
+
status.slashCommandSync === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SlashCommandSyncCard, { sync: status.slashCommandSync }),
|
|
1605
1568
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CredentialsCard, {
|
|
1606
1569
|
panel,
|
|
1607
1570
|
credentials
|
|
@@ -1792,6 +1755,109 @@ window.__ModuleLoader__.load({
|
|
|
1792
1755
|
]
|
|
1793
1756
|
});
|
|
1794
1757
|
}
|
|
1758
|
+
/** Post-connect scope self-check: missing scopes in red, each with a console deep link. */
|
|
1759
|
+
function ScopeCheckCard(props) {
|
|
1760
|
+
const { check } = props;
|
|
1761
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1762
|
+
className: "dsh-channels-cfg__card",
|
|
1763
|
+
children: [
|
|
1764
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1765
|
+
className: "dsh-channels-cfg__cardHead",
|
|
1766
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1767
|
+
className: "dsh-channels-cfg__cardTitle",
|
|
1768
|
+
children: PANEL_COPY.scopeCheckTitle
|
|
1769
|
+
})
|
|
1770
|
+
}),
|
|
1771
|
+
check.state === "ok" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1772
|
+
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--ok",
|
|
1773
|
+
children: PANEL_COPY.scopeCheckOk
|
|
1774
|
+
}) : null,
|
|
1775
|
+
check.state === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1776
|
+
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--error",
|
|
1777
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: PANEL_COPY.scopeCheckError }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1778
|
+
className: "dsh-channels-cfg__mono",
|
|
1779
|
+
children: check.error
|
|
1780
|
+
})]
|
|
1781
|
+
}) : null,
|
|
1782
|
+
check.state === "missing" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1783
|
+
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--error",
|
|
1784
|
+
children: [
|
|
1785
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: PANEL_COPY.scopeCheckMissing }),
|
|
1786
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
1787
|
+
className: "dsh-channels-cfg__scopeList",
|
|
1788
|
+
children: check.missing.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
1789
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1790
|
+
"[",
|
|
1791
|
+
item.level === "required" ? PANEL_COPY.scopeCheckRequired : PANEL_COPY.scopeCheckOptional,
|
|
1792
|
+
"] "
|
|
1793
|
+
] }),
|
|
1794
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1795
|
+
className: "dsh-channels-cfg__mono",
|
|
1796
|
+
children: item.scopes.join(" / ")
|
|
1797
|
+
}),
|
|
1798
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1799
|
+
":",
|
|
1800
|
+
item.purpose,
|
|
1801
|
+
" "
|
|
1802
|
+
] }),
|
|
1803
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
1804
|
+
href: item.consoleUrl,
|
|
1805
|
+
target: "_blank",
|
|
1806
|
+
rel: "noreferrer noopener",
|
|
1807
|
+
children: PANEL_COPY.scopeCheckOpenOne
|
|
1808
|
+
})
|
|
1809
|
+
] }, item.scopes.join(",")))
|
|
1810
|
+
}),
|
|
1811
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
1812
|
+
href: check.consoleUrl,
|
|
1813
|
+
target: "_blank",
|
|
1814
|
+
rel: "noreferrer noopener",
|
|
1815
|
+
children: PANEL_COPY.scopeCheckOpenAll
|
|
1816
|
+
})
|
|
1817
|
+
]
|
|
1818
|
+
}) : null
|
|
1819
|
+
]
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
/** Post-connect native slash-command panel sync: counts on success, the Feishu reason on failure. */
|
|
1823
|
+
function SlashCommandSyncCard(props) {
|
|
1824
|
+
const { sync } = props;
|
|
1825
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1826
|
+
className: "dsh-channels-cfg__card",
|
|
1827
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1828
|
+
className: "dsh-channels-cfg__cardHead",
|
|
1829
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1830
|
+
className: "dsh-channels-cfg__cardTitle",
|
|
1831
|
+
children: PANEL_COPY.slashCommandSyncTitle
|
|
1832
|
+
})
|
|
1833
|
+
}), sync.state === "ok" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1834
|
+
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--ok",
|
|
1835
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [PANEL_COPY.slashCommandSyncOk, " "] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1836
|
+
PANEL_COPY.slashCommandSyncTotal,
|
|
1837
|
+
" ",
|
|
1838
|
+
sync.total,
|
|
1839
|
+
" · ",
|
|
1840
|
+
PANEL_COPY.slashCommandSyncCreated,
|
|
1841
|
+
" ",
|
|
1842
|
+
sync.created,
|
|
1843
|
+
" · ",
|
|
1844
|
+
PANEL_COPY.slashCommandSyncUpdated,
|
|
1845
|
+
" ",
|
|
1846
|
+
sync.updated,
|
|
1847
|
+
" · ",
|
|
1848
|
+
PANEL_COPY.slashCommandSyncRemoved,
|
|
1849
|
+
" ",
|
|
1850
|
+
sync.removed
|
|
1851
|
+
] })]
|
|
1852
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1853
|
+
className: "dsh-channels-cfg__notice dsh-channels-cfg__notice--error",
|
|
1854
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: PANEL_COPY.slashCommandSyncError }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1855
|
+
className: "dsh-channels-cfg__mono",
|
|
1856
|
+
children: sync.error
|
|
1857
|
+
})]
|
|
1858
|
+
})]
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1795
1861
|
/** Config saved but only applied on the next connect: offer the one-click restart. */
|
|
1796
1862
|
function RestartBanner(props) {
|
|
1797
1863
|
const { panel, busy, locked, onReconnect } = props;
|
|
@@ -1870,135 +1936,14 @@ window.__ModuleLoader__.load({
|
|
|
1870
1936
|
});
|
|
1871
1937
|
}
|
|
1872
1938
|
//#endregion
|
|
1873
|
-
//#region src/client/panel/cleanup-card.tsx
|
|
1874
|
-
/**
|
|
1875
|
-
* 目录清理卡:展示清理预览(保留期 / 自动清理 / 候选数量与空间),
|
|
1876
|
-
* 执行前二次确认,结果与部分失败逐条展示。`cleanup.error` 非空时
|
|
1877
|
-
* (例如未连接、无法扫描工作区)按钮天然禁用并显示原因。
|
|
1878
|
-
*
|
|
1879
|
-
* @module dsh-channels/client/panel/cleanup-card
|
|
1880
|
-
*/
|
|
1881
|
-
function CleanupCard(props) {
|
|
1882
|
-
const { panel, cleanup, busy, locked, result, error, onRun } = props;
|
|
1883
|
-
const autoText = cleanup.retention === "off" ? PANEL_COPY.cleanupAutoInactive : cleanup.auto ? PANEL_COPY.cleanupAutoOn : PANEL_COPY.cleanupAutoOff;
|
|
1884
|
-
const blocked = cleanup.error !== void 0 ? `${PANEL_COPY.cleanupPreviewFailed}:${cleanup.error}` : cleanup.retention === "off" ? PANEL_COPY.cleanupDisabledOff : cleanup.candidates === 0 ? PANEL_COPY.cleanupDisabledEmpty : null;
|
|
1885
|
-
const facts = [
|
|
1886
|
-
{
|
|
1887
|
-
label: PANEL_COPY.cleanupRetention,
|
|
1888
|
-
value: retentionLabel(panel, cleanup)
|
|
1889
|
-
},
|
|
1890
|
-
{
|
|
1891
|
-
label: PANEL_COPY.cleanupAutoLabel,
|
|
1892
|
-
value: autoText
|
|
1893
|
-
},
|
|
1894
|
-
{
|
|
1895
|
-
label: PANEL_COPY.cleanupCandidates,
|
|
1896
|
-
value: `${cleanup.candidates} 个`
|
|
1897
|
-
},
|
|
1898
|
-
{
|
|
1899
|
-
label: PANEL_COPY.cleanupWorkspaces,
|
|
1900
|
-
value: `${cleanup.workspaces} 个`
|
|
1901
|
-
},
|
|
1902
|
-
{
|
|
1903
|
-
label: PANEL_COPY.cleanupCandidateBytes,
|
|
1904
|
-
value: formatBytes(cleanup.candidateBytes)
|
|
1905
|
-
}
|
|
1906
|
-
];
|
|
1907
|
-
const running = busy === "cleanup";
|
|
1908
|
-
const failed = result !== null && (!result.ok || error !== null);
|
|
1909
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1910
|
-
className: "dsh-channels-cfg__card",
|
|
1911
|
-
children: [
|
|
1912
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1913
|
-
className: "dsh-channels-cfg__cardHead",
|
|
1914
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1915
|
-
className: "dsh-channels-cfg__cardTitle",
|
|
1916
|
-
children: PANEL_COPY.cleanupCard
|
|
1917
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1918
|
-
className: "dsh-channels-cfg__cardMeta",
|
|
1919
|
-
children: retentionLabel(panel, cleanup)
|
|
1920
|
-
})]
|
|
1921
|
-
}),
|
|
1922
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1923
|
-
className: "dsh-channels-cfg__help",
|
|
1924
|
-
children: PANEL_COPY.cleanupCardHelp
|
|
1925
|
-
}),
|
|
1926
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1927
|
-
className: "dsh-channels-cfg__facts",
|
|
1928
|
-
children: facts.map((fact) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1929
|
-
className: "dsh-channels-cfg__fact",
|
|
1930
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1931
|
-
className: "dsh-channels-cfg__factLabel",
|
|
1932
|
-
children: fact.label
|
|
1933
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1934
|
-
className: "dsh-channels-cfg__factValue",
|
|
1935
|
-
children: fact.value
|
|
1936
|
-
})]
|
|
1937
|
-
}, fact.label))
|
|
1938
|
-
}),
|
|
1939
|
-
blocked === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1940
|
-
className: "dsh-channels-cfg__help",
|
|
1941
|
-
children: blocked
|
|
1942
|
-
}),
|
|
1943
|
-
result === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1944
|
-
className: `dsh-channels-cfg__notice dsh-channels-cfg__notice--${failed ? "error" : "ok"}`,
|
|
1945
|
-
children: [result.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1946
|
-
PANEL_COPY.cleanupDone,
|
|
1947
|
-
":",
|
|
1948
|
-
PANEL_COPY.cleanupDeleted,
|
|
1949
|
-
" ",
|
|
1950
|
-
result.deletedWorkspaces,
|
|
1951
|
-
" 个,",
|
|
1952
|
-
PANEL_COPY.cleanupDeletedDirs,
|
|
1953
|
-
" ",
|
|
1954
|
-
result.deletedSessionDirs,
|
|
1955
|
-
" 个,",
|
|
1956
|
-
PANEL_COPY.cleanupRetired,
|
|
1957
|
-
" ",
|
|
1958
|
-
result.retiredRoutes,
|
|
1959
|
-
" 条,",
|
|
1960
|
-
PANEL_COPY.cleanupFreed,
|
|
1961
|
-
" ",
|
|
1962
|
-
formatBytes(result.freedBytes),
|
|
1963
|
-
"。"
|
|
1964
|
-
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [PANEL_COPY.cleanupFailed, result.error === void 0 ? "" : `:${result.error}`] }), result.failures.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: PANEL_COPY.cleanupFailures }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
1965
|
-
className: "dsh-channels-cfg__issues",
|
|
1966
|
-
children: result.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
1967
|
-
className: "dsh-channels-cfg__mono",
|
|
1968
|
-
children: failure
|
|
1969
|
-
}, `${index}-${failure}`))
|
|
1970
|
-
})] })]
|
|
1971
|
-
}),
|
|
1972
|
-
error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1973
|
-
className: "dsh-channels-cfg__fieldError",
|
|
1974
|
-
children: error
|
|
1975
|
-
}),
|
|
1976
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1977
|
-
className: "dsh-channels-cfg__actions",
|
|
1978
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1979
|
-
type: "button",
|
|
1980
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--danger",
|
|
1981
|
-
onClick: onRun,
|
|
1982
|
-
disabled: running || busy !== null || locked || blocked !== null,
|
|
1983
|
-
children: running ? PANEL_COPY.cleanupRunning : PANEL_COPY.cleanupNow
|
|
1984
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1985
|
-
className: "dsh-channels-cfg__help",
|
|
1986
|
-
children: PANEL_COPY.cleanupConfirmHint
|
|
1987
|
-
})]
|
|
1988
|
-
})
|
|
1989
|
-
]
|
|
1990
|
-
});
|
|
1991
|
-
}
|
|
1992
|
-
//#endregion
|
|
1993
1939
|
//#region src/client/panel/workspace-tab.tsx
|
|
1994
1940
|
/**
|
|
1995
|
-
* 「工作区」tab
|
|
1996
|
-
* 表单保存栏在字段卡正下方,清理卡(独立动作,不走 draft)在其后。
|
|
1941
|
+
* 「工作区」tab:工作区根目录字段与保存栏。
|
|
1997
1942
|
*
|
|
1998
1943
|
* @module dsh-channels/client/panel/workspace-tab
|
|
1999
1944
|
*/
|
|
2000
1945
|
function WorkspaceTab(props) {
|
|
2001
|
-
const { form
|
|
1946
|
+
const { form } = props;
|
|
2002
1947
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2003
1948
|
className: "dsh-channels-cfg__tabBody",
|
|
2004
1949
|
children: [
|
|
@@ -2010,15 +1955,6 @@ window.__ModuleLoader__.load({
|
|
|
2010
1955
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SaveBar, {
|
|
2011
1956
|
form,
|
|
2012
1957
|
tab: "workspace"
|
|
2013
|
-
}),
|
|
2014
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CleanupCard, {
|
|
2015
|
-
panel: form.panel,
|
|
2016
|
-
cleanup,
|
|
2017
|
-
busy: form.busy,
|
|
2018
|
-
locked: form.locked,
|
|
2019
|
-
result: cleanupResult,
|
|
2020
|
-
error: cleanupError,
|
|
2021
|
-
onRun: onRunCleanup
|
|
2022
1958
|
})
|
|
2023
1959
|
]
|
|
2024
1960
|
});
|
|
@@ -2029,8 +1965,8 @@ window.__ModuleLoader__.load({
|
|
|
2029
1965
|
* 设置 →「飞书渠道」页面:数据加载、5 秒状态轮询、全局 draft 与顶部状态头 /
|
|
2030
1966
|
* tab 导航的外壳。
|
|
2031
1967
|
*
|
|
2032
|
-
* 所有 Host 交互(panel / status / catalog / config / credentials /
|
|
2033
|
-
*
|
|
1968
|
+
* 所有 Host 交互(panel / status / catalog / config / credentials / access /
|
|
1969
|
+
* pick-directory)都集中在这里;tab 组件只接收渲染所需的数据与
|
|
2034
1970
|
* 回调。页面不 import 任何 DSH 客户端包,只依赖 react 与 `settings.section`
|
|
2035
1971
|
* 槽位。
|
|
2036
1972
|
*
|
|
@@ -2049,8 +1985,6 @@ window.__ModuleLoader__.load({
|
|
|
2049
1985
|
const [appSecretInput, setAppSecretInput] = (0, react.useState)("");
|
|
2050
1986
|
const [catalog, setCatalog] = (0, react.useState)(null);
|
|
2051
1987
|
const [catalogBusy, setCatalogBusy] = (0, react.useState)(false);
|
|
2052
|
-
const [cleanupResult, setCleanupResult] = (0, react.useState)(null);
|
|
2053
|
-
const [cleanupError, setCleanupError] = (0, react.useState)(null);
|
|
2054
1988
|
const [accessNotice, setAccessNotice] = (0, react.useState)(null);
|
|
2055
1989
|
const [accessError, setAccessError] = (0, react.useState)(null);
|
|
2056
1990
|
const catalogTicket = (0, react.useRef)(0);
|
|
@@ -2065,8 +1999,6 @@ window.__ModuleLoader__.load({
|
|
|
2065
1999
|
const loadPanel = (0, react.useCallback)(async () => {
|
|
2066
2000
|
setLoad({ phase: "loading" });
|
|
2067
2001
|
setNotice(null);
|
|
2068
|
-
setCleanupResult(null);
|
|
2069
|
-
setCleanupError(null);
|
|
2070
2002
|
setAccessNotice(null);
|
|
2071
2003
|
setAccessError(null);
|
|
2072
2004
|
try {
|
|
@@ -2227,8 +2159,8 @@ window.__ModuleLoader__.load({
|
|
|
2227
2159
|
}
|
|
2228
2160
|
}, []);
|
|
2229
2161
|
/**
|
|
2230
|
-
* Manual refresh: reload the whole panel payload so access lists
|
|
2231
|
-
*
|
|
2162
|
+
* Manual refresh: reload the whole panel payload so access lists and
|
|
2163
|
+
* `restartPendingKeys` catch up with the Host. The dirty draft is
|
|
2232
2164
|
* deliberately preserved — refreshing never discards user input.
|
|
2233
2165
|
*/
|
|
2234
2166
|
const onRefresh = (0, react.useCallback)(async () => {
|
|
@@ -2277,8 +2209,6 @@ window.__ModuleLoader__.load({
|
|
|
2277
2209
|
panel: result.panel
|
|
2278
2210
|
});
|
|
2279
2211
|
setStatus(result.panel.status);
|
|
2280
|
-
setCleanupResult(null);
|
|
2281
|
-
setCleanupError(null);
|
|
2282
2212
|
setDraft((previous) => {
|
|
2283
2213
|
const next = { ...previous };
|
|
2284
2214
|
for (const key of Object.keys(values)) delete next[key];
|
|
@@ -2365,8 +2295,6 @@ window.__ModuleLoader__.load({
|
|
|
2365
2295
|
});
|
|
2366
2296
|
setStatus(result.panel.status);
|
|
2367
2297
|
setDraft({});
|
|
2368
|
-
setCleanupResult(null);
|
|
2369
|
-
setCleanupError(null);
|
|
2370
2298
|
setNotice({
|
|
2371
2299
|
kind: "ok",
|
|
2372
2300
|
text: PANEL_COPY.cleared
|
|
@@ -2421,37 +2349,6 @@ window.__ModuleLoader__.load({
|
|
|
2421
2349
|
setBusy(null);
|
|
2422
2350
|
}
|
|
2423
2351
|
}, [refreshStatus, running]);
|
|
2424
|
-
/**
|
|
2425
|
-
* Delete stale workspaces after an explicit confirmation. The Host refreshes
|
|
2426
|
-
* the panel in the same response, so the preview counts update immediately;
|
|
2427
|
-
* partial failures stay visible in the card instead of being swallowed.
|
|
2428
|
-
*/
|
|
2429
|
-
const onRunCleanup = (0, react.useCallback)(async () => {
|
|
2430
|
-
if (panel === null) return;
|
|
2431
|
-
const { cleanup } = panel;
|
|
2432
|
-
if (!(typeof window.confirm === "function" ? window.confirm(`${PANEL_COPY.cleanupConfirm}\n${PANEL_COPY.cleanupCandidates}:${cleanup.candidates} · ${PANEL_COPY.cleanupCandidateBytes}:${formatBytes(cleanup.candidateBytes)}`) : true)) return;
|
|
2433
|
-
setBusy("cleanup");
|
|
2434
|
-
setNotice(null);
|
|
2435
|
-
setCleanupResult(null);
|
|
2436
|
-
setCleanupError(null);
|
|
2437
|
-
try {
|
|
2438
|
-
const response = await runCleanup();
|
|
2439
|
-
if (!response.ok) {
|
|
2440
|
-
setCleanupError(response.error);
|
|
2441
|
-
return;
|
|
2442
|
-
}
|
|
2443
|
-
setLoad({
|
|
2444
|
-
phase: "ready",
|
|
2445
|
-
panel: response.panel
|
|
2446
|
-
});
|
|
2447
|
-
setStatus(response.panel.status);
|
|
2448
|
-
setCleanupResult(response.result);
|
|
2449
|
-
} catch (error) {
|
|
2450
|
-
setCleanupError(messageOf(error));
|
|
2451
|
-
} finally {
|
|
2452
|
-
setBusy(null);
|
|
2453
|
-
}
|
|
2454
|
-
}, [panel]);
|
|
2455
2352
|
/** Replace the embedded access snapshot after a whitelist mutation. */
|
|
2456
2353
|
const applyAccess = (0, react.useCallback)((access) => {
|
|
2457
2354
|
setLoad((previous) => previous.phase === "ready" ? {
|
|
@@ -2500,12 +2397,12 @@ window.__ModuleLoader__.load({
|
|
|
2500
2397
|
setBusy(null);
|
|
2501
2398
|
}
|
|
2502
2399
|
}, [applyAccess]);
|
|
2503
|
-
const onAddAccessUser = (0, react.useCallback)(async (
|
|
2400
|
+
const onAddAccessUser = (0, react.useCallback)(async (userId, name) => {
|
|
2504
2401
|
setBusy("access");
|
|
2505
2402
|
setAccessError(null);
|
|
2506
2403
|
setAccessNotice(null);
|
|
2507
2404
|
try {
|
|
2508
|
-
const result = await addAccessUser(
|
|
2405
|
+
const result = await addAccessUser(userId, name);
|
|
2509
2406
|
if (!result.ok) {
|
|
2510
2407
|
setAccessError(result.error);
|
|
2511
2408
|
return false;
|
|
@@ -2520,12 +2417,12 @@ window.__ModuleLoader__.load({
|
|
|
2520
2417
|
setBusy(null);
|
|
2521
2418
|
}
|
|
2522
2419
|
}, [applyAccess]);
|
|
2523
|
-
const onRemoveAccessUser = (0, react.useCallback)(async (
|
|
2420
|
+
const onRemoveAccessUser = (0, react.useCallback)(async (userId) => {
|
|
2524
2421
|
setBusy("access");
|
|
2525
2422
|
setAccessError(null);
|
|
2526
2423
|
setAccessNotice(null);
|
|
2527
2424
|
try {
|
|
2528
|
-
const result = await removeAccessUser(
|
|
2425
|
+
const result = await removeAccessUser(userId);
|
|
2529
2426
|
if (!result.ok) {
|
|
2530
2427
|
setAccessError(result.error);
|
|
2531
2428
|
return;
|
|
@@ -2762,15 +2659,7 @@ window.__ModuleLoader__.load({
|
|
|
2762
2659
|
form,
|
|
2763
2660
|
catalogBusy
|
|
2764
2661
|
}) : null,
|
|
2765
|
-
activeTab === "workspace" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceTab, {
|
|
2766
|
-
form,
|
|
2767
|
-
cleanup: form.panel.cleanup,
|
|
2768
|
-
cleanupResult,
|
|
2769
|
-
cleanupError,
|
|
2770
|
-
onRunCleanup: () => {
|
|
2771
|
-
onRunCleanup();
|
|
2772
|
-
}
|
|
2773
|
-
}) : null,
|
|
2662
|
+
activeTab === "workspace" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceTab, { form }) : null,
|
|
2774
2663
|
activeTab === "access" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccessTab, {
|
|
2775
2664
|
form,
|
|
2776
2665
|
accessNotice,
|
|
@@ -2780,8 +2669,8 @@ window.__ModuleLoader__.load({
|
|
|
2780
2669
|
onRemoveAccessGroup(chatId);
|
|
2781
2670
|
},
|
|
2782
2671
|
onAddUser: onAddAccessUser,
|
|
2783
|
-
onRemoveUser: (
|
|
2784
|
-
onRemoveAccessUser(
|
|
2672
|
+
onRemoveUser: (userId) => {
|
|
2673
|
+
onRemoveAccessUser(userId);
|
|
2785
2674
|
},
|
|
2786
2675
|
onResolveRequest: (token, decision) => {
|
|
2787
2676
|
onResolveAccessRequest(token, decision);
|
|
@@ -2970,6 +2859,8 @@ select.dsh-channels-cfg__input{appearance:auto;cursor:pointer}
|
|
|
2970
2859
|
.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}
|
|
2971
2860
|
.dsh-channels-cfg__notice--ok{color:var(--dsw-alias-state-business-primary,#0a7d33);background:color-mix(in srgb,currentColor 8%,transparent)}
|
|
2972
2861
|
.dsh-channels-cfg__notice--error{color:var(--dsw-alias-state-danger-primary,#c62828);background:color-mix(in srgb,currentColor 8%,transparent)}
|
|
2862
|
+
.dsh-channels-cfg__notice a{color:inherit;text-decoration:underline}
|
|
2863
|
+
.dsh-channels-cfg__scopeList{margin:2px 0;padding-left:18px;display:flex;flex-direction:column;gap:2px}
|
|
2973
2864
|
.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}
|
|
2974
2865
|
.dsh-channels-cfg__footer{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding-top:2px}
|
|
2975
2866
|
.dsh-channels-cfg__spacer{flex:1}
|