@sparkelf/dsh-dataops-managed 0.3.19 → 0.3.21
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/lib/client.js +3974 -255
- package/lib/client.js.map +1 -1
- package/lib/index.js +1415 -24
- package/lib/types/auth-state.d.ts +37 -0
- package/lib/types/client/NotificationBell.d.ts +19 -0
- package/lib/types/client/PermissionCenterIcon.d.ts +9 -0
- package/lib/types/client/PermissionCenterSection.d.ts +15 -0
- package/lib/types/client/ReloginPrompt.d.ts +29 -0
- package/lib/types/client/ReviewSettingsCard.d.ts +12 -0
- package/lib/types/client/SkillPlazaSection.d.ts +3 -5
- package/lib/types/client/WanxiangBrand.d.ts +7 -1
- package/lib/types/client/WorkspaceUpgradePrompt.d.ts +21 -0
- package/lib/types/client/combobox.d.ts +17 -0
- package/lib/types/client/index.d.ts +17 -2
- package/lib/types/client/locales.d.ts +103 -0
- package/lib/types/client/review-client.d.ts +155 -0
- package/lib/types/client/review-demo.d.ts +13 -0
- package/lib/types/client/review-notification-store.d.ts +23 -0
- package/lib/types/client/review-types.d.ts +114 -0
- package/lib/types/client/settings-gate.d.ts +21 -0
- package/lib/types/client/skill-plaza-client.d.ts +21 -0
- package/lib/types/client/workspace-upgrade-client.d.ts +29 -0
- package/lib/types/index.d.ts +36 -0
- package/lib/types/review-api.d.ts +32 -0
- package/lib/types/skill-plaza.d.ts +31 -0
- package/lib/types/tools.d.ts +21 -0
- package/lib/types/workspace-upgrade-state.d.ts +66 -0
- package/lib/types/workspace-upgrade.d.ts +83 -0
- package/package.json +2 -1
- package/skills/dataops-query/i18n/zh.json +10 -0
package/lib/client.js
CHANGED
|
@@ -4,6 +4,26 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __esmMin = (fn, res, err) => () => {
|
|
10
|
+
if (err) throw err[0];
|
|
11
|
+
try {
|
|
12
|
+
return fn && (res = fn(fn = 0)), res;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
throw err = [e], e;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var __exportAll = (all, no_symbols) => {
|
|
18
|
+
let target = {};
|
|
19
|
+
for (var name in all) __defProp(target, name, {
|
|
20
|
+
get: all[name],
|
|
21
|
+
enumerable: true
|
|
22
|
+
});
|
|
23
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
24
|
+
return target;
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
7
27
|
let react = require("react");
|
|
8
28
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
29
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -20,14 +40,14 @@ window.__ModuleLoader__.load({
|
|
|
20
40
|
}
|
|
21
41
|
}
|
|
22
42
|
let preference = initialPreference();
|
|
23
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
43
|
+
const listeners$1 = /* @__PURE__ */ new Set();
|
|
24
44
|
/** Observable preference compatible with React's useSyncExternalStore. */
|
|
25
45
|
const wanxiangBrandPreference = {
|
|
26
46
|
getSnapshot: () => preference,
|
|
27
47
|
subscribe: (listener) => {
|
|
28
|
-
listeners.add(listener);
|
|
48
|
+
listeners$1.add(listener);
|
|
29
49
|
return () => {
|
|
30
|
-
listeners.delete(listener);
|
|
50
|
+
listeners$1.delete(listener);
|
|
31
51
|
};
|
|
32
52
|
}
|
|
33
53
|
};
|
|
@@ -38,7 +58,7 @@ window.__ModuleLoader__.load({
|
|
|
38
58
|
if (typeof localStorage !== "undefined") try {
|
|
39
59
|
localStorage.setItem(WANXIANG_BRAND_PREFERENCE_KEY, JSON.stringify(preference));
|
|
40
60
|
} catch {}
|
|
41
|
-
for (const listener of listeners) listener();
|
|
61
|
+
for (const listener of listeners$1) listener();
|
|
42
62
|
}
|
|
43
63
|
//#endregion
|
|
44
64
|
//#region src/client/model-sync-client.ts
|
|
@@ -46,7 +66,7 @@ window.__ModuleLoader__.load({
|
|
|
46
66
|
/** Route the plugin's Host half serves for model sharing. */
|
|
47
67
|
const MODEL_SYNC_PATH = "/integrations/dataops/model-sync";
|
|
48
68
|
/** Narrow a response body to the status shape, rejecting anything else. */
|
|
49
|
-
function asStatus$
|
|
69
|
+
function asStatus$2(value) {
|
|
50
70
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
51
71
|
const record = value;
|
|
52
72
|
if (record.followState !== "following" && record.followState !== "detached") return null;
|
|
@@ -86,7 +106,7 @@ window.__ModuleLoader__.load({
|
|
|
86
106
|
const response = await fetch(MODEL_SYNC_PATH, { headers: { accept: "application/json" } });
|
|
87
107
|
if (response.status === 503) return null;
|
|
88
108
|
if (!response.ok) throw new Error("model sync status failed with HTTP " + String(response.status));
|
|
89
|
-
return asStatus$
|
|
109
|
+
return asStatus$2(await response.json());
|
|
90
110
|
}
|
|
91
111
|
/**
|
|
92
112
|
* Change what this workspace shares.
|
|
@@ -104,7 +124,7 @@ window.__ModuleLoader__.load({
|
|
|
104
124
|
});
|
|
105
125
|
if (response.status === 503) return null;
|
|
106
126
|
if (!response.ok) throw new Error("model sync action failed with HTTP " + String(response.status));
|
|
107
|
-
return asStatus$
|
|
127
|
+
return asStatus$2(await response.json());
|
|
108
128
|
}
|
|
109
129
|
//#endregion
|
|
110
130
|
//#region src/client/workspace-limits-client.ts
|
|
@@ -112,7 +132,7 @@ window.__ModuleLoader__.load({
|
|
|
112
132
|
/** Route the plugin's Host half serves for workspace limits. */
|
|
113
133
|
const WORKSPACE_LIMITS_PATH = "/integrations/dataops/workspace-limits";
|
|
114
134
|
/** Narrow a response body to the status, rejecting anything else. */
|
|
115
|
-
function asStatus(value) {
|
|
135
|
+
function asStatus$1(value) {
|
|
116
136
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
117
137
|
const record = value;
|
|
118
138
|
const limits = record.limits;
|
|
@@ -129,7 +149,7 @@ window.__ModuleLoader__.load({
|
|
|
129
149
|
readOnlyReason: typeof record.readOnlyReason === "string" ? record.readOnlyReason : null
|
|
130
150
|
};
|
|
131
151
|
}
|
|
132
|
-
function readJson(value) {
|
|
152
|
+
function readJson$1(value) {
|
|
133
153
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
134
154
|
}
|
|
135
155
|
/**
|
|
@@ -147,7 +167,7 @@ window.__ModuleLoader__.load({
|
|
|
147
167
|
/** Read the reason from a refused response, falling back to the status line. */
|
|
148
168
|
async function refusalReason$1(response, fallback) {
|
|
149
169
|
try {
|
|
150
|
-
const error = readJson(await response.json())?.error;
|
|
170
|
+
const error = readJson$1(await response.json())?.error;
|
|
151
171
|
if (typeof error === "string" && error.trim() !== "") return error;
|
|
152
172
|
} catch {}
|
|
153
173
|
return `${fallback} with HTTP ${String(response.status)}`;
|
|
@@ -160,7 +180,7 @@ window.__ModuleLoader__.load({
|
|
|
160
180
|
const response = await fetch(WORKSPACE_LIMITS_PATH, { credentials: "same-origin" });
|
|
161
181
|
if (response.status === 503) return null;
|
|
162
182
|
if (!response.ok) throw new Error(`Workspace limits read failed with HTTP ${String(response.status)}`);
|
|
163
|
-
return asStatus(await response.json());
|
|
183
|
+
return asStatus$1(await response.json());
|
|
164
184
|
}
|
|
165
185
|
/**
|
|
166
186
|
* Store new workspace limits.
|
|
@@ -177,8 +197,8 @@ window.__ModuleLoader__.load({
|
|
|
177
197
|
if (response.status === 503) return null;
|
|
178
198
|
if (response.status === 400) throw new WorkspaceLimitsRejection(await refusalReason$1(response, "Workspace limits write refused"));
|
|
179
199
|
if (!response.ok) throw new Error(await refusalReason$1(response, "Workspace limits write failed"));
|
|
180
|
-
const body = readJson(await response.json());
|
|
181
|
-
return body === null ? null : asStatus(body);
|
|
200
|
+
const body = readJson$1(await response.json());
|
|
201
|
+
return body === null ? null : asStatus$1(body);
|
|
182
202
|
}
|
|
183
203
|
//#endregion
|
|
184
204
|
//#region src/client/tool-timeout-client.ts
|
|
@@ -230,31 +250,643 @@ window.__ModuleLoader__.load({
|
|
|
230
250
|
return asTimeout(await response.json());
|
|
231
251
|
}
|
|
232
252
|
//#endregion
|
|
233
|
-
//#region
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
253
|
+
//#region src/client/review-demo.ts
|
|
254
|
+
var review_demo_exports = /* @__PURE__ */ __exportAll({
|
|
255
|
+
demoPlazaSkills: () => demoPlazaSkills,
|
|
256
|
+
demoRbacOverview: () => demoRbacOverview
|
|
257
|
+
});
|
|
258
|
+
/** A pending skill with an AI report, an approved global skill, and a rejected private one. */
|
|
259
|
+
function demoPlazaSkills() {
|
|
260
|
+
return [
|
|
261
|
+
{
|
|
262
|
+
id: 9101,
|
|
263
|
+
name: "SQL 取数助手",
|
|
264
|
+
description: "把自然语言问题改写成只读 SQL,并解释每个过滤条件。",
|
|
265
|
+
instruction: "你是数据分析助手。只生成只读 SELECT,不要写 UPDATE/DELETE。",
|
|
266
|
+
tags: ["数据", "SQL"],
|
|
267
|
+
sharedByMe: false,
|
|
268
|
+
updatedAt: "2026-10-23T04:40:00.000Z",
|
|
269
|
+
reviewStatus: "pending",
|
|
270
|
+
visibility: "department",
|
|
271
|
+
departmentCode: "dept-rd",
|
|
272
|
+
departmentName: "研发部",
|
|
273
|
+
departmentPath: [{
|
|
274
|
+
code: "dept-root",
|
|
275
|
+
name: "万相"
|
|
276
|
+
}, {
|
|
277
|
+
code: "dept-rd",
|
|
278
|
+
name: "研发部"
|
|
279
|
+
}],
|
|
280
|
+
ownerName: "林工",
|
|
281
|
+
riskReport: {
|
|
282
|
+
score: 62,
|
|
283
|
+
severity: "medium",
|
|
284
|
+
summary: "指令包含数据库读取,需确认连接凭据的最小权限。",
|
|
285
|
+
findings: [{
|
|
286
|
+
category: "credential_access",
|
|
287
|
+
severity: "medium",
|
|
288
|
+
evidence: "提示词要求读取 DATABASE_URL 以连接数据库。",
|
|
289
|
+
recommendation: "改为由平台注入只读连接,不要在提示词中出现凭据。"
|
|
290
|
+
}, {
|
|
291
|
+
category: "destructive_action",
|
|
292
|
+
severity: "low",
|
|
293
|
+
evidence: "明确禁止写操作,但未限制 DDL。",
|
|
294
|
+
recommendation: "补充「禁止 CREATE/ALTER/DROP」。"
|
|
295
|
+
}],
|
|
296
|
+
scannedAt: "2026-10-23T04:41:00.000Z"
|
|
297
|
+
},
|
|
298
|
+
reviewNote: null,
|
|
299
|
+
submittedAt: "2026-10-23T04:40:00.000Z"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: 9102,
|
|
303
|
+
name: "经营日报生成",
|
|
304
|
+
description: "按日汇总营收、订单与退款,输出 Markdown 日报。",
|
|
305
|
+
instruction: "读取昨日指标,生成经营日报。",
|
|
306
|
+
tags: ["经营", "日报"],
|
|
307
|
+
sharedByMe: true,
|
|
308
|
+
updatedAt: "2026-10-21T02:00:00.000Z",
|
|
309
|
+
reviewStatus: "approved",
|
|
310
|
+
visibility: "global",
|
|
311
|
+
departmentCode: null,
|
|
312
|
+
ownerName: "admin",
|
|
313
|
+
riskReport: null
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: 9103,
|
|
317
|
+
name: "私域名单清洗",
|
|
318
|
+
description: "按内部规则去重并标记高价值客户。",
|
|
319
|
+
instruction: "清洗名单并输出 CSV。",
|
|
320
|
+
tags: ["客户"],
|
|
321
|
+
sharedByMe: false,
|
|
322
|
+
updatedAt: "2026-10-20T06:00:00.000Z",
|
|
323
|
+
reviewStatus: "rejected",
|
|
324
|
+
visibility: "private",
|
|
325
|
+
departmentCode: "dept-rd",
|
|
326
|
+
departmentName: "研发部",
|
|
327
|
+
ownerName: "周工",
|
|
328
|
+
reviewNote: "包含真实手机号样例,请脱敏后重新提交。"
|
|
329
|
+
}
|
|
330
|
+
];
|
|
331
|
+
}
|
|
332
|
+
/** Four roles, the permission catalog, three members, two departments, and three resources. */
|
|
333
|
+
function demoRbacOverview() {
|
|
334
|
+
return {
|
|
335
|
+
matrix: {
|
|
336
|
+
permissions: [
|
|
337
|
+
{
|
|
338
|
+
key: "skill.review",
|
|
339
|
+
name: "审核技能",
|
|
340
|
+
group: "技能广场"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
key: "workflow.review",
|
|
344
|
+
name: "审核工作流",
|
|
345
|
+
group: "工作流广场"
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
key: "rbac.manage",
|
|
349
|
+
name: "管理权限",
|
|
350
|
+
group: "系统"
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
key: "kb.write",
|
|
354
|
+
name: "写入知识库",
|
|
355
|
+
group: "知识库"
|
|
356
|
+
}
|
|
357
|
+
],
|
|
358
|
+
roles: [
|
|
359
|
+
{
|
|
360
|
+
code: "SUPER_ADMIN",
|
|
361
|
+
name: "超级管理员",
|
|
362
|
+
systemKey: "SUPER_ADMIN",
|
|
363
|
+
dataScope: "ALL",
|
|
364
|
+
permissions: [
|
|
365
|
+
"skill.review",
|
|
366
|
+
"workflow.review",
|
|
367
|
+
"rbac.manage",
|
|
368
|
+
"kb.write"
|
|
369
|
+
]
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
code: "ADMIN",
|
|
373
|
+
name: "管理员",
|
|
374
|
+
systemKey: "ADMIN",
|
|
375
|
+
dataScope: "ALL",
|
|
376
|
+
permissions: [
|
|
377
|
+
"skill.review",
|
|
378
|
+
"workflow.review",
|
|
379
|
+
"kb.write"
|
|
380
|
+
]
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
code: "DEPT_ADMIN",
|
|
384
|
+
name: "部门管理员",
|
|
385
|
+
systemKey: "DEPT_ADMIN",
|
|
386
|
+
dataScope: "DEPARTMENT_TREE",
|
|
387
|
+
permissions: ["skill.review", "workflow.review"]
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
code: "APP_USER",
|
|
391
|
+
name: "应用人员",
|
|
392
|
+
systemKey: "APP_USER",
|
|
393
|
+
dataScope: "SELF",
|
|
394
|
+
permissions: []
|
|
395
|
+
}
|
|
396
|
+
]
|
|
397
|
+
},
|
|
398
|
+
members: [
|
|
399
|
+
{
|
|
400
|
+
userId: "u1",
|
|
401
|
+
username: "admin",
|
|
402
|
+
displayName: "超级管理员",
|
|
403
|
+
roleCodes: ["SUPER_ADMIN"],
|
|
404
|
+
departmentCode: "dept-root",
|
|
405
|
+
departmentPath: [{
|
|
406
|
+
code: "dept-root",
|
|
407
|
+
name: "万相"
|
|
408
|
+
}]
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
userId: "u2",
|
|
412
|
+
username: "lin",
|
|
413
|
+
displayName: "林工",
|
|
414
|
+
roleCodes: ["DEPT_ADMIN"],
|
|
415
|
+
departmentCode: "dept-rd",
|
|
416
|
+
departmentPath: [{
|
|
417
|
+
code: "dept-root",
|
|
418
|
+
name: "万相"
|
|
419
|
+
}, {
|
|
420
|
+
code: "dept-rd",
|
|
421
|
+
name: "研发部"
|
|
422
|
+
}]
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
userId: "u3",
|
|
426
|
+
username: "zhou",
|
|
427
|
+
displayName: "周工",
|
|
428
|
+
roleCodes: ["APP_USER"],
|
|
429
|
+
departmentCode: "dept-rd",
|
|
430
|
+
departmentPath: [{
|
|
431
|
+
code: "dept-root",
|
|
432
|
+
name: "万相"
|
|
433
|
+
}, {
|
|
434
|
+
code: "dept-rd",
|
|
435
|
+
name: "研发部"
|
|
436
|
+
}]
|
|
437
|
+
}
|
|
438
|
+
],
|
|
439
|
+
departments: [{
|
|
440
|
+
code: "dept-root",
|
|
441
|
+
name: "万相",
|
|
442
|
+
path: [{
|
|
443
|
+
code: "dept-root",
|
|
444
|
+
name: "万相"
|
|
445
|
+
}],
|
|
446
|
+
memberCount: 3,
|
|
447
|
+
deptAdmins: []
|
|
448
|
+
}, {
|
|
449
|
+
code: "dept-rd",
|
|
450
|
+
name: "研发部",
|
|
451
|
+
parentCode: "dept-root",
|
|
452
|
+
path: [{
|
|
453
|
+
code: "dept-root",
|
|
454
|
+
name: "万相"
|
|
455
|
+
}, {
|
|
456
|
+
code: "dept-rd",
|
|
457
|
+
name: "研发部"
|
|
458
|
+
}],
|
|
459
|
+
memberCount: 2,
|
|
460
|
+
deptAdmins: [{
|
|
461
|
+
userId: "u2",
|
|
462
|
+
username: "lin",
|
|
463
|
+
displayName: "林工"
|
|
464
|
+
}]
|
|
465
|
+
}],
|
|
466
|
+
resources: [
|
|
467
|
+
{
|
|
468
|
+
kind: "skill",
|
|
469
|
+
id: 9101,
|
|
470
|
+
name: "SQL 取数助手",
|
|
471
|
+
ownerName: "林工",
|
|
472
|
+
departmentCode: "dept-rd",
|
|
473
|
+
visibility: "department",
|
|
474
|
+
reviewStatus: "pending"
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
kind: "skill",
|
|
478
|
+
id: 9102,
|
|
479
|
+
name: "经营日报生成",
|
|
480
|
+
ownerName: "admin",
|
|
481
|
+
departmentCode: null,
|
|
482
|
+
visibility: "global",
|
|
483
|
+
reviewStatus: "approved"
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
kind: "workflow",
|
|
487
|
+
id: "wf-1",
|
|
488
|
+
name: "每日经营日报",
|
|
489
|
+
ownerName: "admin",
|
|
490
|
+
departmentCode: null,
|
|
491
|
+
visibility: "global",
|
|
492
|
+
reviewStatus: "approved"
|
|
493
|
+
}
|
|
494
|
+
]
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
var init_review_demo = __esmMin((() => {}));
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region src/client/review-client.ts
|
|
500
|
+
/** Caller identity and review capability. */
|
|
501
|
+
const REVIEW_CONTEXT_PATH = "/integrations/dataops/review-context";
|
|
502
|
+
/** Notification list and read receipts. */
|
|
503
|
+
const NOTIFICATIONS_PATH = "/integrations/dataops/review-notifications";
|
|
504
|
+
/** AI scan and the human decision. */
|
|
505
|
+
const REVIEW_ACTION_PATH = "/integrations/dataops/review";
|
|
506
|
+
/** Role matrix, members, departments, and resource scopes. */
|
|
507
|
+
const RBAC_PATH = "/integrations/dataops/rbac";
|
|
508
|
+
/** The three plaza-review switches in the DataOps settings panel. */
|
|
509
|
+
const REVIEW_SETTINGS_PATH = "/integrations/dataops/review-settings";
|
|
510
|
+
/** Department CRUD bridge (task-67). */
|
|
511
|
+
const DEPARTMENTS_PATH = "/integrations/dataops/departments";
|
|
512
|
+
/** The product defaults, used until DataOps returns its own value. */
|
|
513
|
+
const DEFAULT_REVIEW_SETTINGS = {
|
|
514
|
+
skillPlaza: true,
|
|
515
|
+
workflowPlaza: true,
|
|
516
|
+
knowledgeBase: true
|
|
517
|
+
};
|
|
518
|
+
/**
|
|
519
|
+
* Whether the page was opened with the demo fixture.
|
|
520
|
+
*
|
|
521
|
+
* The fixture exists so the review dialog and the permission matrix can be reviewed before the
|
|
522
|
+
* backend contract is deployed; every fixture row is labelled and the mode is never the default.
|
|
523
|
+
*/
|
|
524
|
+
function reviewDemoMode() {
|
|
525
|
+
if (typeof window === "undefined") return false;
|
|
526
|
+
try {
|
|
527
|
+
return new URLSearchParams(window.location.search).get("dataopsReviewDemo") === "1" || window.localStorage.getItem("dataopsReviewDemo") === "1";
|
|
528
|
+
} catch {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
/** Read JSON, mapping "not active" and "backend not ready" answers to null. */
|
|
533
|
+
async function readJson(path, init = {}) {
|
|
534
|
+
const response = await fetch(path, {
|
|
535
|
+
...init,
|
|
536
|
+
headers: {
|
|
537
|
+
accept: "application/json",
|
|
538
|
+
...init.body === void 0 ? {} : { "content-type": "application/json" },
|
|
539
|
+
...init.headers
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
if (response.status === 503 || response.status === 404 || response.status === 501) return null;
|
|
543
|
+
if (!response.ok) throw new Error("DataOps review request failed with HTTP " + String(response.status));
|
|
544
|
+
return await response.json();
|
|
545
|
+
}
|
|
546
|
+
/** Read the caller's role and review capability. */
|
|
547
|
+
async function fetchReviewContext() {
|
|
548
|
+
if (reviewDemoMode()) return {
|
|
549
|
+
role: "ADMIN",
|
|
550
|
+
departmentCode: "dept-rd",
|
|
551
|
+
departmentPath: ["万相", "研发部"],
|
|
552
|
+
canReview: true,
|
|
553
|
+
canManagePermissions: true
|
|
554
|
+
};
|
|
555
|
+
const body = await readJson(REVIEW_CONTEXT_PATH);
|
|
556
|
+
if (body === null) return null;
|
|
557
|
+
const codes = Array.isArray(body.roleCodes) ? body.roleCodes : [];
|
|
558
|
+
const primaryKey = body.roles?.find((role) => role.code === body.primaryRole)?.systemKey ?? body.roles?.find((role) => role.code !== void 0 && codes.includes(role.code))?.systemKey;
|
|
559
|
+
const role = primaryKey === "SUPER_ADMIN" || codes.includes("超级管理员") ? "SUPER_ADMIN" : primaryKey === "ADMIN" || codes.includes("管理员") ? "ADMIN" : primaryKey === "DEPT_ADMIN" || codes.includes("部门管理员") ? "DEPT_ADMIN" : "APP_USER";
|
|
560
|
+
return {
|
|
561
|
+
userId: body.userId,
|
|
562
|
+
role,
|
|
563
|
+
departmentCode: body.departmentCode ?? void 0,
|
|
564
|
+
departmentPath: body.departmentPath?.map((entry) => entry.name),
|
|
565
|
+
canReview: body.review?.canReview === true || role === "SUPER_ADMIN" || role === "ADMIN" || role === "DEPT_ADMIN",
|
|
566
|
+
canManagePermissions: body.review?.canManagePermissions === true || role === "SUPER_ADMIN" || role === "ADMIN"
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
/** Read the current user's notifications. */
|
|
570
|
+
async function fetchNotifications() {
|
|
571
|
+
const body = await readJson(NOTIFICATIONS_PATH);
|
|
572
|
+
if (body === null || !Array.isArray(body.items)) return null;
|
|
573
|
+
return {
|
|
574
|
+
items: body.items,
|
|
575
|
+
unread: typeof body.unread === "number" ? body.unread : body.items.filter((item) => !item.read).length
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
/** Mark some notifications, or every one, as read. */
|
|
579
|
+
async function markNotificationsRead(ids) {
|
|
580
|
+
const response = await fetch(NOTIFICATIONS_PATH, {
|
|
581
|
+
method: "POST",
|
|
582
|
+
headers: {
|
|
583
|
+
"content-type": "application/json",
|
|
584
|
+
accept: "application/json"
|
|
585
|
+
},
|
|
586
|
+
body: JSON.stringify(ids === "all" ? { all: true } : { ids })
|
|
587
|
+
});
|
|
588
|
+
return response.ok && response.status !== 503;
|
|
589
|
+
}
|
|
590
|
+
/** Run the AI review for one or more resources. */
|
|
591
|
+
async function scanReview(kind, ids) {
|
|
592
|
+
const report = (await readJson(REVIEW_ACTION_PATH, {
|
|
593
|
+
method: "POST",
|
|
594
|
+
body: JSON.stringify({
|
|
595
|
+
action: "scan",
|
|
596
|
+
skills: kind === "skill" ? ids : [],
|
|
597
|
+
workflows: kind === "workflow" ? ids : []
|
|
598
|
+
})
|
|
599
|
+
}))?.reports?.[0];
|
|
600
|
+
return report !== void 0 && Array.isArray(report.findings) ? report : null;
|
|
601
|
+
}
|
|
602
|
+
/** Record the administrator's decision. The backend enforces the authorization flag too. */
|
|
603
|
+
async function decideReview(kind, ids, decision, note) {
|
|
604
|
+
const response = await fetch(REVIEW_ACTION_PATH, {
|
|
605
|
+
method: "POST",
|
|
606
|
+
headers: {
|
|
607
|
+
"content-type": "application/json",
|
|
608
|
+
accept: "application/json"
|
|
609
|
+
},
|
|
610
|
+
body: JSON.stringify({
|
|
611
|
+
action: "decide",
|
|
612
|
+
skills: kind === "skill" ? ids : [],
|
|
613
|
+
workflows: kind === "workflow" ? ids : [],
|
|
614
|
+
decision,
|
|
615
|
+
note,
|
|
616
|
+
authorization: {
|
|
617
|
+
confirmed: true,
|
|
618
|
+
reason: "permission-center"
|
|
619
|
+
}
|
|
620
|
+
})
|
|
621
|
+
});
|
|
622
|
+
return response.ok && response.status !== 503;
|
|
623
|
+
}
|
|
624
|
+
/** Read the permission centre. */
|
|
625
|
+
async function fetchRbacOverview() {
|
|
626
|
+
if (reviewDemoMode()) {
|
|
627
|
+
const { demoRbacOverview } = await Promise.resolve().then(() => (init_review_demo(), review_demo_exports));
|
|
628
|
+
return demoRbacOverview();
|
|
629
|
+
}
|
|
630
|
+
const [matrix, members, departments, resources] = await Promise.all([
|
|
631
|
+
readJson(RBAC_PATH + "?section=matrix&scope=dsh"),
|
|
632
|
+
readJson(RBAC_PATH + "?section=members"),
|
|
633
|
+
readJson(RBAC_PATH + "?section=departments"),
|
|
634
|
+
readJson(RBAC_PATH + "?section=resources")
|
|
635
|
+
]);
|
|
636
|
+
if (matrix === null || members === null || departments === null || resources === null) return null;
|
|
637
|
+
return {
|
|
638
|
+
matrix: {
|
|
639
|
+
roles: matrix.roles ?? [],
|
|
640
|
+
permissions: matrix.permissions ?? []
|
|
641
|
+
},
|
|
642
|
+
members: members.items ?? [],
|
|
643
|
+
departments: departments.items ?? [],
|
|
644
|
+
resources: resources.items ?? []
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
/** Persist one permission-centre section. */
|
|
648
|
+
async function saveRbacSection(section, value) {
|
|
649
|
+
if (reviewDemoMode()) return { ok: true };
|
|
650
|
+
const response = await fetch(RBAC_PATH, {
|
|
651
|
+
method: "PUT",
|
|
652
|
+
headers: {
|
|
653
|
+
"content-type": "application/json",
|
|
654
|
+
accept: "application/json"
|
|
655
|
+
},
|
|
656
|
+
body: JSON.stringify({
|
|
657
|
+
section,
|
|
658
|
+
value
|
|
659
|
+
})
|
|
660
|
+
});
|
|
661
|
+
if (response.ok && response.status !== 503) return { ok: true };
|
|
662
|
+
try {
|
|
663
|
+
const body = await response.json();
|
|
664
|
+
const reason = body.details?.reason ?? body.message;
|
|
665
|
+
return {
|
|
666
|
+
ok: false,
|
|
667
|
+
...typeof reason === "string" ? { reason } : {}
|
|
668
|
+
};
|
|
669
|
+
} catch {
|
|
670
|
+
return { ok: false };
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
/** Read the three plaza-review switches. */
|
|
674
|
+
async function fetchReviewSettings() {
|
|
675
|
+
if (reviewDemoMode()) return DEFAULT_REVIEW_SETTINGS;
|
|
676
|
+
return await readJson(REVIEW_SETTINGS_PATH);
|
|
677
|
+
}
|
|
678
|
+
/** Persist the three plaza-review switches. */
|
|
679
|
+
async function saveReviewSettings(settings) {
|
|
680
|
+
if (reviewDemoMode()) return true;
|
|
681
|
+
const response = await fetch(REVIEW_SETTINGS_PATH, {
|
|
682
|
+
method: "PUT",
|
|
683
|
+
headers: {
|
|
684
|
+
"content-type": "application/json",
|
|
685
|
+
accept: "application/json"
|
|
686
|
+
},
|
|
687
|
+
body: JSON.stringify(settings)
|
|
688
|
+
});
|
|
689
|
+
return response.ok && response.status !== 503;
|
|
690
|
+
}
|
|
691
|
+
/** Create one department through the same-origin bridge. */
|
|
692
|
+
async function createDepartment(input) {
|
|
693
|
+
return await resultOf(await fetch(DEPARTMENTS_PATH, {
|
|
694
|
+
method: "POST",
|
|
695
|
+
headers: {
|
|
696
|
+
"content-type": "application/json",
|
|
697
|
+
accept: "application/json"
|
|
698
|
+
},
|
|
699
|
+
body: JSON.stringify(input)
|
|
700
|
+
}));
|
|
701
|
+
}
|
|
702
|
+
/** Update one department; the Host fills `updatedAt` from the current row when omitted. */
|
|
703
|
+
async function updateDepartment(input) {
|
|
704
|
+
return await resultOf(await fetch(DEPARTMENTS_PATH, {
|
|
705
|
+
method: "PATCH",
|
|
706
|
+
headers: {
|
|
707
|
+
"content-type": "application/json",
|
|
708
|
+
accept: "application/json"
|
|
709
|
+
},
|
|
710
|
+
body: JSON.stringify(input)
|
|
711
|
+
}));
|
|
712
|
+
}
|
|
713
|
+
/** Delete one department after the confirmation dialog. */
|
|
714
|
+
async function deleteDepartment(code) {
|
|
715
|
+
return await resultOf(await fetch(DEPARTMENTS_PATH, {
|
|
716
|
+
method: "DELETE",
|
|
717
|
+
headers: {
|
|
718
|
+
"content-type": "application/json",
|
|
719
|
+
accept: "application/json"
|
|
720
|
+
},
|
|
721
|
+
body: JSON.stringify({ code })
|
|
722
|
+
}));
|
|
723
|
+
}
|
|
724
|
+
/** Map one bridge response to `{ok, reason}` so callers can show the backend text. */
|
|
725
|
+
async function resultOf(response) {
|
|
726
|
+
if (response.ok && response.status !== 503) return { ok: true };
|
|
727
|
+
try {
|
|
728
|
+
const body = await response.json();
|
|
729
|
+
const reason = body.details?.reason ?? body.message;
|
|
730
|
+
return {
|
|
731
|
+
ok: false,
|
|
732
|
+
...typeof reason === "string" ? { reason } : {}
|
|
733
|
+
};
|
|
734
|
+
} catch {
|
|
735
|
+
return { ok: false };
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
//#endregion
|
|
739
|
+
//#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/ManagedDataOpsSection.module.css.mjs
|
|
740
|
+
const css$5 = "._2dqzjG_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}._2dqzjG_title{margin:0;font-size:18px;font-weight:600;line-height:28px}._2dqzjG_status{color:var(--dsw-alias-label-secondary);align-items:center;gap:8px;font-size:13px;line-height:22px;display:flex}._2dqzjG_card{border:.5px solid var(--dsw-alias-settings-card-stroke);border-radius:var(--dsw-radius-xl);background:var(--dsw-alias-settings-card-fill);flex-direction:column;gap:12px;padding:12px 16px;display:flex}._2dqzjG_heading{margin:0;font-size:13px;font-weight:600;line-height:22px}._2dqzjG_row{justify-content:space-between;align-items:center;gap:16px;display:flex}._2dqzjG_copy{flex-direction:column;min-width:0;display:flex}._2dqzjG_label{font-size:13px;line-height:22px}._2dqzjG_meta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}._2dqzjG_fields{grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr));gap:0 16px;display:grid}._2dqzjG_fields>div{min-width:0}._2dqzjG_fields input{font-variant-numeric:tabular-nums;min-width:0}._2dqzjG_actions{justify-content:flex-end;align-items:center;gap:12px;display:flex}._2dqzjG_refusal{color:var(--dsw-alias-state-error-primary);align-items:flex-start;gap:6px;margin-right:auto;font-size:12px;line-height:18px;display:flex}._2dqzjG_reviewHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:17px}._2dqzjG_reviewRows{flex-direction:column;gap:2px;display:flex}._2dqzjG_reviewRow{justify-content:space-between;align-items:center;gap:12px;min-height:30px;display:flex}._2dqzjG_reviewActions{justify-content:flex-end;display:flex}";
|
|
741
|
+
const tagId$5 = "@sparkelf/dsh-dataops-managed/ManagedDataOpsSection.module.css";
|
|
742
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$5) + "]") === null) {
|
|
237
743
|
const tag = document.createElement("style");
|
|
238
744
|
tag.dataset.plugin = "@sparkelf/dsh-dataops-managed";
|
|
239
|
-
tag.dataset.pluginCss = tagId$
|
|
240
|
-
tag.textContent = css$
|
|
745
|
+
tag.dataset.pluginCss = tagId$5;
|
|
746
|
+
tag.textContent = css$5;
|
|
241
747
|
document.head.appendChild(tag);
|
|
242
748
|
}
|
|
243
749
|
var ManagedDataOpsSection_module_css_default = {
|
|
244
|
-
"
|
|
245
|
-
"
|
|
246
|
-
"
|
|
247
|
-
"
|
|
248
|
-
"
|
|
249
|
-
"
|
|
250
|
-
"
|
|
251
|
-
"
|
|
252
|
-
"
|
|
253
|
-
"
|
|
254
|
-
"
|
|
255
|
-
"
|
|
750
|
+
"refusal": "_2dqzjG_refusal",
|
|
751
|
+
"heading": "_2dqzjG_heading",
|
|
752
|
+
"section": "_2dqzjG_section",
|
|
753
|
+
"status": "_2dqzjG_status",
|
|
754
|
+
"card": "_2dqzjG_card",
|
|
755
|
+
"meta": "_2dqzjG_meta",
|
|
756
|
+
"title": "_2dqzjG_title",
|
|
757
|
+
"reviewActions": "_2dqzjG_reviewActions",
|
|
758
|
+
"reviewRows": "_2dqzjG_reviewRows",
|
|
759
|
+
"reviewHint": "_2dqzjG_reviewHint",
|
|
760
|
+
"row": "_2dqzjG_row",
|
|
761
|
+
"label": "_2dqzjG_label",
|
|
762
|
+
"fields": "_2dqzjG_fields",
|
|
763
|
+
"actions": "_2dqzjG_actions",
|
|
764
|
+
"reviewRow": "_2dqzjG_reviewRow",
|
|
765
|
+
"copy": "_2dqzjG_copy"
|
|
256
766
|
};
|
|
257
767
|
//#endregion
|
|
768
|
+
//#region src/client/ReviewSettingsCard.tsx
|
|
769
|
+
/**
|
|
770
|
+
* The three plaza-review switches.
|
|
771
|
+
*
|
|
772
|
+
* The card is independent of the review queue so the settings page owns the switches while the
|
|
773
|
+
* plaza owns the rows. Defaults are on; a 503 keeps the switches usable and says the API is
|
|
774
|
+
* waiting rather than pretending the save happened.
|
|
775
|
+
*/
|
|
776
|
+
function ReviewSettingsCard({ t }) {
|
|
777
|
+
const [settings, setSettings] = (0, react.useState)(DEFAULT_REVIEW_SETTINGS);
|
|
778
|
+
const [saved, setSaved] = (0, react.useState)(DEFAULT_REVIEW_SETTINGS);
|
|
779
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
780
|
+
const [unavailable, setUnavailable] = (0, react.useState)(false);
|
|
781
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
782
|
+
const [message, setMessage] = (0, react.useState)(null);
|
|
783
|
+
(0, react.useEffect)(() => {
|
|
784
|
+
let live = true;
|
|
785
|
+
fetchReviewSettings().then((value) => {
|
|
786
|
+
if (!live) return;
|
|
787
|
+
if (value === null) setUnavailable(true);
|
|
788
|
+
else {
|
|
789
|
+
setSettings(value);
|
|
790
|
+
setSaved(value);
|
|
791
|
+
}
|
|
792
|
+
}).catch(() => {
|
|
793
|
+
if (live) setUnavailable(true);
|
|
794
|
+
}).finally(() => {
|
|
795
|
+
if (live) setLoading(false);
|
|
796
|
+
});
|
|
797
|
+
return () => {
|
|
798
|
+
live = false;
|
|
799
|
+
};
|
|
800
|
+
}, []);
|
|
801
|
+
const changed = settings.skillPlaza !== saved.skillPlaza || settings.workflowPlaza !== saved.workflowPlaza || settings.knowledgeBase !== saved.knowledgeBase;
|
|
802
|
+
const save = async () => {
|
|
803
|
+
setBusy(true);
|
|
804
|
+
setMessage(null);
|
|
805
|
+
try {
|
|
806
|
+
if (await saveReviewSettings(settings)) {
|
|
807
|
+
setSaved(settings);
|
|
808
|
+
setMessage(t("reviewSettingsSaved"));
|
|
809
|
+
} else setMessage(t("reviewSettingsWaiting"));
|
|
810
|
+
} catch {
|
|
811
|
+
setMessage(t("reviewSettingsFailed"));
|
|
812
|
+
} finally {
|
|
813
|
+
setBusy(false);
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
const rows = [
|
|
817
|
+
{
|
|
818
|
+
key: "skillPlaza",
|
|
819
|
+
label: t("reviewSettingsSkill")
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
key: "workflowPlaza",
|
|
823
|
+
label: t("reviewSettingsWorkflow")
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
key: "knowledgeBase",
|
|
827
|
+
label: t("reviewSettingsKb")
|
|
828
|
+
}
|
|
829
|
+
];
|
|
830
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
831
|
+
className: ManagedDataOpsSection_module_css_default.card,
|
|
832
|
+
"data-review-settings-card": "",
|
|
833
|
+
children: [
|
|
834
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
835
|
+
className: ManagedDataOpsSection_module_css_default.heading,
|
|
836
|
+
children: t("reviewSettingsTitle")
|
|
837
|
+
}),
|
|
838
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
839
|
+
className: ManagedDataOpsSection_module_css_default.reviewHint,
|
|
840
|
+
children: t("reviewSettingsHint")
|
|
841
|
+
}),
|
|
842
|
+
loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
843
|
+
className: ManagedDataOpsSection_module_css_default.status,
|
|
844
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "…" })
|
|
845
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
846
|
+
className: ManagedDataOpsSection_module_css_default.reviewRows,
|
|
847
|
+
children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
848
|
+
className: ManagedDataOpsSection_module_css_default.reviewRow,
|
|
849
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
850
|
+
className: ManagedDataOpsSection_module_css_default.label,
|
|
851
|
+
children: row.label
|
|
852
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
853
|
+
checked: settings[row.key],
|
|
854
|
+
label: row.label,
|
|
855
|
+
onChange: (next) => {
|
|
856
|
+
setSettings((current) => ({
|
|
857
|
+
...current,
|
|
858
|
+
[row.key]: next
|
|
859
|
+
}));
|
|
860
|
+
}
|
|
861
|
+
})]
|
|
862
|
+
}, row.key))
|
|
863
|
+
}),
|
|
864
|
+
unavailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
865
|
+
className: ManagedDataOpsSection_module_css_default.reviewHint,
|
|
866
|
+
role: "status",
|
|
867
|
+
children: t("reviewSettingsWaiting")
|
|
868
|
+
}) : null,
|
|
869
|
+
message === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
870
|
+
className: ManagedDataOpsSection_module_css_default.reviewHint,
|
|
871
|
+
role: "status",
|
|
872
|
+
children: message
|
|
873
|
+
}),
|
|
874
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
875
|
+
className: ManagedDataOpsSection_module_css_default.reviewActions,
|
|
876
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
877
|
+
variant: "primary",
|
|
878
|
+
size: "sm",
|
|
879
|
+
disabled: !changed || busy || loading,
|
|
880
|
+
onClick: () => {
|
|
881
|
+
save();
|
|
882
|
+
},
|
|
883
|
+
children: busy ? t("limitsSaving") : t("reviewSettingsSave")
|
|
884
|
+
})
|
|
885
|
+
})
|
|
886
|
+
]
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
//#endregion
|
|
258
890
|
//#region src/client/ManagedDataOpsSection.tsx
|
|
259
891
|
/** The panel's unit, so a stored byte count survives a round trip through the input. */
|
|
260
892
|
const BYTES_PER_MIB = 1048576;
|
|
@@ -302,6 +934,7 @@ window.__ModuleLoader__.load({
|
|
|
302
934
|
}),
|
|
303
935
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LimitsCard, { t }),
|
|
304
936
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimeoutCard, { t }),
|
|
937
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewSettingsCard, { t }),
|
|
305
938
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultsCard, { t })
|
|
306
939
|
]
|
|
307
940
|
});
|
|
@@ -667,8 +1300,102 @@ window.__ModuleLoader__.load({
|
|
|
667
1300
|
});
|
|
668
1301
|
}
|
|
669
1302
|
//#endregion
|
|
1303
|
+
//#region src/client/review-types.ts
|
|
1304
|
+
/** Treat a missing status as approved: rows written before the review state existed were live. */
|
|
1305
|
+
function normalizeReviewStatus(value) {
|
|
1306
|
+
return value === "pending" || value === "rejected" || value === "approved" ? value : "approved";
|
|
1307
|
+
}
|
|
1308
|
+
/** Treat a missing visibility as global: a shared row with no scope was visible to everyone. */
|
|
1309
|
+
function normalizeVisibility(value) {
|
|
1310
|
+
return value === "department" || value === "private" ? value : "global";
|
|
1311
|
+
}
|
|
1312
|
+
/** Whether the caller may see and act on the review queue. */
|
|
1313
|
+
function canReview(context) {
|
|
1314
|
+
if (context === null || context === void 0) return false;
|
|
1315
|
+
return context.canReview || context.role === "SUPER_ADMIN" || context.role === "ADMIN" || context.role === "DEPT_ADMIN";
|
|
1316
|
+
}
|
|
1317
|
+
/** Whether the caller may open the permission centre's write controls. */
|
|
1318
|
+
function canManagePermissions(context) {
|
|
1319
|
+
if (context === null || context === void 0) return false;
|
|
1320
|
+
return context.canManagePermissions || context.role === "SUPER_ADMIN" || context.role === "ADMIN";
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* The tabs this caller may see.
|
|
1324
|
+
*
|
|
1325
|
+
* A department administrator sees the queue too, but the Host narrows it to their subtree;
|
|
1326
|
+
* an ordinary user gets only the approved list, and no tab strip at all when there is one tab.
|
|
1327
|
+
*/
|
|
1328
|
+
function reviewTabsFor(context) {
|
|
1329
|
+
if (!canReview(context)) return ["approved"];
|
|
1330
|
+
return [
|
|
1331
|
+
"approved",
|
|
1332
|
+
"pending",
|
|
1333
|
+
"all"
|
|
1334
|
+
];
|
|
1335
|
+
}
|
|
1336
|
+
/** Filter rows by the selected review tab. */
|
|
1337
|
+
function filterByReview(rows, tab) {
|
|
1338
|
+
if (tab === "all") return rows;
|
|
1339
|
+
return rows.filter((row) => normalizeReviewStatus(row.reviewStatus) === tab);
|
|
1340
|
+
}
|
|
1341
|
+
/** Counts for the tab labels. */
|
|
1342
|
+
function reviewCounts(rows) {
|
|
1343
|
+
const counts = {
|
|
1344
|
+
approved: 0,
|
|
1345
|
+
pending: 0,
|
|
1346
|
+
all: rows.length
|
|
1347
|
+
};
|
|
1348
|
+
for (const row of rows) {
|
|
1349
|
+
const status = normalizeReviewStatus(row.reviewStatus);
|
|
1350
|
+
if (status === "pending") counts.pending += 1;
|
|
1351
|
+
else if (status === "rejected") counts.all += 0;
|
|
1352
|
+
else counts.approved += 1;
|
|
1353
|
+
}
|
|
1354
|
+
return counts;
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* The visibility tag in a card's top-right corner.
|
|
1358
|
+
*
|
|
1359
|
+
* A department tag names the department and keeps the full path in the tooltip; a global tag
|
|
1360
|
+
* is one word because it is the default and should not compete with the skill name.
|
|
1361
|
+
*/
|
|
1362
|
+
/** Read a department path from either the legacy string form or the backend object form. */
|
|
1363
|
+
function departmentPathNames(path) {
|
|
1364
|
+
if (path === void 0) return [];
|
|
1365
|
+
return path.map((entry) => typeof entry === "string" ? entry : entry.name);
|
|
1366
|
+
}
|
|
1367
|
+
function visibilityTag(row) {
|
|
1368
|
+
const visibility = normalizeVisibility(row.visibility);
|
|
1369
|
+
if (visibility === "private") return {
|
|
1370
|
+
label: "私有",
|
|
1371
|
+
detail: "仅自己可见"
|
|
1372
|
+
};
|
|
1373
|
+
if (visibility === "global") return {
|
|
1374
|
+
label: "全局",
|
|
1375
|
+
detail: "所有已登录用户可见"
|
|
1376
|
+
};
|
|
1377
|
+
const path = departmentPathNames(row.departmentPath);
|
|
1378
|
+
const name = row.departmentName ?? path.at(-1) ?? row.departmentCode ?? "部门";
|
|
1379
|
+
return {
|
|
1380
|
+
label: name + "部门专属",
|
|
1381
|
+
detail: path.length === 0 ? name : path.join(" / ")
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
/** Unread count for a bell badge. */
|
|
1385
|
+
function unreadCount(items) {
|
|
1386
|
+
return items.reduce((total, item) => total + (item.read ? 0 : 1), 0);
|
|
1387
|
+
}
|
|
1388
|
+
/** Newest first. Read state never reorders rows, so marking one read cannot move it to another page. */
|
|
1389
|
+
function sortNotifications(items) {
|
|
1390
|
+
return [...items].sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
1391
|
+
}
|
|
1392
|
+
/** The tab a notification should open, when its resource has one. */
|
|
1393
|
+
function notificationTab(item) {
|
|
1394
|
+
if (item.type === "submitted" || item.type === "review_pending" || item.type === "ai_report_ready" || item.type === "ai_report") return "pending";
|
|
1395
|
+
if (item.type === "approved" || item.type === "published" || item.type === "shared") return "approved";
|
|
1396
|
+
}
|
|
1397
|
+
//#endregion
|
|
670
1398
|
//#region src/client/skill-plaza-client.ts
|
|
671
|
-
/** Same-origin skill plaza calls from the DataOps skill center section. */
|
|
672
1399
|
/** Route the plugin's Host half serves for the plaza. */
|
|
673
1400
|
const SKILL_PLAZA_PATH = "/integrations/dataops/skill-plaza";
|
|
674
1401
|
/** Narrow an unknown value to a plaza entry. */
|
|
@@ -683,16 +1410,37 @@ window.__ModuleLoader__.load({
|
|
|
683
1410
|
instruction: typeof row.instruction === "string" ? row.instruction : "",
|
|
684
1411
|
tags: Array.isArray(row.tags) ? row.tags.filter((tag) => typeof tag === "string") : [],
|
|
685
1412
|
sharedByMe: row.sharedByMe === true,
|
|
686
|
-
updatedAt: typeof row.updatedAt === "string" ? row.updatedAt : ""
|
|
1413
|
+
updatedAt: typeof row.updatedAt === "string" ? row.updatedAt : "",
|
|
1414
|
+
reviewStatus: normalizeReviewStatus(row.reviewStatus),
|
|
1415
|
+
visibility: normalizeVisibility(row.visibility),
|
|
1416
|
+
departmentCode: typeof row.departmentCode === "string" ? row.departmentCode : null,
|
|
1417
|
+
departmentName: typeof row.departmentName === "string" ? row.departmentName : void 0,
|
|
1418
|
+
departmentPath: Array.isArray(row.departmentPath) ? row.departmentPath.flatMap((entry) => typeof entry === "object" && entry !== null && typeof entry.name === "string" ? [{
|
|
1419
|
+
code: String(entry.code ?? ""),
|
|
1420
|
+
name: String(entry.name)
|
|
1421
|
+
}] : []) : void 0,
|
|
1422
|
+
ownerName: typeof row.ownerName === "string" ? row.ownerName : void 0,
|
|
1423
|
+
riskReport: typeof row.riskReport === "object" && row.riskReport !== null ? row.riskReport : null,
|
|
1424
|
+
reviewNote: typeof row.reviewNote === "string" ? row.reviewNote : null,
|
|
1425
|
+
submittedAt: typeof row.submittedAt === "string" ? row.submittedAt : void 0
|
|
687
1426
|
};
|
|
688
1427
|
}
|
|
689
1428
|
/** Narrow an unknown value to the listing. */
|
|
690
1429
|
function asListing(value) {
|
|
691
1430
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
692
1431
|
const row = value;
|
|
1432
|
+
const skills = (Array.isArray(row.skills) ? row.skills : Array.isArray(row.items) ? row.items : []).flatMap((entry) => asSkill(entry) ?? []);
|
|
1433
|
+
const tags = Array.isArray(row.tags) ? row.tags.filter((tag) => typeof tag === "string") : [];
|
|
1434
|
+
const rawCounts = typeof row.counts === "object" && row.counts !== null ? row.counts : {};
|
|
693
1435
|
return {
|
|
694
|
-
skills
|
|
695
|
-
tags
|
|
1436
|
+
skills,
|
|
1437
|
+
tags,
|
|
1438
|
+
counts: Object.keys(rawCounts).length === 0 ? void 0 : {
|
|
1439
|
+
pending: Number(rawCounts.pending ?? 0),
|
|
1440
|
+
approved: Number(rawCounts.approved ?? 0),
|
|
1441
|
+
rejected: Number(rawCounts.rejected ?? 0),
|
|
1442
|
+
total: Number(rawCounts.total ?? skills.length)
|
|
1443
|
+
}
|
|
696
1444
|
};
|
|
697
1445
|
}
|
|
698
1446
|
/**
|
|
@@ -704,6 +1452,8 @@ window.__ModuleLoader__.load({
|
|
|
704
1452
|
const params = new URLSearchParams();
|
|
705
1453
|
if (filter.tag !== void 0 && filter.tag !== "") params.set("tag", filter.tag);
|
|
706
1454
|
if (filter.query !== void 0 && filter.query !== "") params.set("q", filter.query);
|
|
1455
|
+
if (filter.status !== void 0) params.set("status", filter.status);
|
|
1456
|
+
if (filter.scope !== void 0) params.set("scope", filter.scope);
|
|
707
1457
|
const suffix = params.size === 0 ? "" : "?" + params.toString();
|
|
708
1458
|
const response = await fetch(SKILL_PLAZA_PATH + suffix, { headers: { accept: "application/json" } });
|
|
709
1459
|
if (response.status === 503) return null;
|
|
@@ -729,95 +1479,587 @@ window.__ModuleLoader__.load({
|
|
|
729
1479
|
return await response.json();
|
|
730
1480
|
}
|
|
731
1481
|
//#endregion
|
|
732
|
-
//#region
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1482
|
+
//#region src/client/review-notification-store.ts
|
|
1483
|
+
init_review_demo();
|
|
1484
|
+
/**
|
|
1485
|
+
* One process-wide notification store shared by every bell.
|
|
1486
|
+
*
|
|
1487
|
+
* The skill plaza and the workflow plaza are separate React trees, but they show the same
|
|
1488
|
+
* inbox; keeping the read state in one store is what stops a badge in one place from staying
|
|
1489
|
+
* unread after the other opened the row.
|
|
1490
|
+
*/
|
|
1491
|
+
const DEMO_ITEMS = [
|
|
1492
|
+
{
|
|
1493
|
+
id: "demo-1",
|
|
1494
|
+
type: "review_pending",
|
|
1495
|
+
title: "技能「SQL 取数助手」等待审核",
|
|
1496
|
+
body: "来自研发部 · 提交于 10 分钟前",
|
|
1497
|
+
resourceKind: "skill",
|
|
1498
|
+
resourceId: 101,
|
|
1499
|
+
read: false,
|
|
1500
|
+
createdAt: "2026-10-23T04:50:00.000Z"
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
id: "demo-2",
|
|
1504
|
+
type: "ai_report",
|
|
1505
|
+
title: "AI 风险报告已生成",
|
|
1506
|
+
body: "风险等级:中 · 2 条建议",
|
|
1507
|
+
resourceKind: "skill",
|
|
1508
|
+
resourceId: 101,
|
|
1509
|
+
read: false,
|
|
1510
|
+
createdAt: "2026-10-23T04:49:00.000Z"
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
id: "demo-3",
|
|
1514
|
+
type: "approved",
|
|
1515
|
+
title: "工作流「每日经营日报」已通过",
|
|
1516
|
+
body: "审核人:admin",
|
|
1517
|
+
resourceKind: "workflow",
|
|
1518
|
+
resourceId: "wf-1",
|
|
1519
|
+
read: true,
|
|
1520
|
+
createdAt: "2026-10-22T09:10:00.000Z"
|
|
1521
|
+
}
|
|
1522
|
+
];
|
|
1523
|
+
let state = {
|
|
1524
|
+
status: "idle",
|
|
1525
|
+
items: [],
|
|
1526
|
+
unread: 0
|
|
1527
|
+
};
|
|
1528
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1529
|
+
function publish(next) {
|
|
1530
|
+
state = next;
|
|
1531
|
+
for (const listener of listeners) listener();
|
|
1532
|
+
}
|
|
1533
|
+
/** Current snapshot for `useSyncExternalStore`. */
|
|
1534
|
+
function notificationSnapshot() {
|
|
1535
|
+
return state;
|
|
1536
|
+
}
|
|
1537
|
+
/** Subscribe to store changes. */
|
|
1538
|
+
function subscribeNotifications(listener) {
|
|
1539
|
+
listeners.add(listener);
|
|
1540
|
+
return () => {
|
|
1541
|
+
listeners.delete(listener);
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
/** Load the inbox once; a second caller joins the first request. */
|
|
1545
|
+
let inflight;
|
|
1546
|
+
function loadNotifications() {
|
|
1547
|
+
if (inflight !== void 0) return inflight;
|
|
1548
|
+
publish({
|
|
1549
|
+
...state,
|
|
1550
|
+
status: "loading"
|
|
1551
|
+
});
|
|
1552
|
+
inflight = (async () => {
|
|
1553
|
+
try {
|
|
1554
|
+
if (reviewDemoMode()) {
|
|
1555
|
+
publish({
|
|
1556
|
+
status: "ready",
|
|
1557
|
+
items: sortNotifications(DEMO_ITEMS),
|
|
1558
|
+
unread: unreadCount(DEMO_ITEMS)
|
|
1559
|
+
});
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
const result = await fetchNotifications();
|
|
1563
|
+
if (result === null) {
|
|
1564
|
+
publish({
|
|
1565
|
+
status: "unavailable",
|
|
1566
|
+
items: [],
|
|
1567
|
+
unread: 0
|
|
1568
|
+
});
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
publish({
|
|
1572
|
+
status: "ready",
|
|
1573
|
+
items: sortNotifications(result.items),
|
|
1574
|
+
unread: unreadCount(result.items)
|
|
1575
|
+
});
|
|
1576
|
+
} catch {
|
|
1577
|
+
publish({
|
|
1578
|
+
status: "unavailable",
|
|
1579
|
+
items: [],
|
|
1580
|
+
unread: 0
|
|
1581
|
+
});
|
|
1582
|
+
} finally {
|
|
1583
|
+
inflight = void 0;
|
|
1584
|
+
}
|
|
1585
|
+
})();
|
|
1586
|
+
return inflight;
|
|
1587
|
+
}
|
|
1588
|
+
/** Mark one row or the whole inbox read, optimistically. */
|
|
1589
|
+
async function markRead(ids) {
|
|
1590
|
+
const items = ids === "all" ? state.items.map((item) => ({
|
|
1591
|
+
...item,
|
|
1592
|
+
read: true
|
|
1593
|
+
})) : state.items.map((item) => ids.includes(item.id) ? {
|
|
1594
|
+
...item,
|
|
1595
|
+
read: true
|
|
1596
|
+
} : item);
|
|
1597
|
+
publish({
|
|
1598
|
+
...state,
|
|
1599
|
+
items,
|
|
1600
|
+
unread: unreadCount(items)
|
|
1601
|
+
});
|
|
1602
|
+
try {
|
|
1603
|
+
if (!await markNotificationsRead(ids)) await loadNotifications();
|
|
1604
|
+
} catch {
|
|
1605
|
+
await loadNotifications();
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
//#endregion
|
|
1609
|
+
//#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/NotificationBell.module.css.mjs
|
|
1610
|
+
const css$4 = ".tE0trG_bell{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex;position:relative}.tE0trG_bell:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.tE0trG_bell:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.tE0trG_badge{background:var(--dsw-alias-state-error-primary);color:#fff;text-align:center;border-radius:7px;min-width:14px;height:14px;padding:0 3px;font-size:9px;font-weight:600;line-height:14px;position:absolute;top:0;right:-2px}.tE0trG_dialog{flex-direction:column;width:min(520px,100vw - 32px);height:70vh;max-height:70vh;display:flex;overflow:hidden}.tE0trG_demo{background:var(--dsw-alias-state-warn-secondary);color:var(--dsw-alias-state-warn-primary);border-radius:4px;margin:0 0 8px;padding:5px 8px;font-size:11px}.tE0trG_empty{color:var(--dsw-alias-label-tertiary);text-align:center;margin:0;padding:18px 0;font-size:12px;line-height:18px}.tE0trG_list{flex-direction:column;flex:none;gap:6px;max-height:calc(70vh - 210px);margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}.tE0trG_row{width:100%;color:var(--dsw-alias-label-secondary);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:0;align-items:flex-start;gap:9px;padding:4px 2px;display:flex}.tE0trG_row:hover .tE0trG_rowTitle{color:var(--dsw-alias-state-business-primary)}.tE0trG_rowIcon{width:18px;height:18px;color:var(--dsw-alias-label-secondary);flex:none;justify-content:center;align-items:center;margin-top:1px;display:inline-flex}.tE0trG_rowIcon[data-tone=success]{color:var(--dsw-alias-state-success-primary)}.tE0trG_rowIcon[data-tone=danger]{color:var(--dsw-alias-state-error-primary)}.tE0trG_rowIcon[data-tone=warn]{color:var(--dsw-alias-state-warn-primary)}.tE0trG_rowIcon[data-tone=info]{color:var(--dsw-alias-state-business-primary)}.tE0trG_dot{background:var(--dsw-alias-state-business-primary);border-radius:50%;flex:none;width:6px;height:6px}.tE0trG_rowBody{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.tE0trG_rowTitle{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:18px}.tE0trG_rowText{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px}.tE0trG_rowMeta{flex:none;align-items:center;gap:6px;margin-top:2px;display:flex}.tE0trG_rowTime{color:var(--dsw-alias-label-tertiary);flex:none;font-size:10px;line-height:16px}.tE0trG_actions{justify-content:flex-end;gap:6px;display:flex}.tE0trG_button,.tE0trG_linkButton{cursor:pointer;border-radius:5px;height:28px;padding:0 10px;font-size:12px}.tE0trG_button{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0}.tE0trG_linkButton{color:var(--dsw-alias-state-business-primary);background:0 0;border:0}.tE0trG_pager{color:var(--dsw-alias-label-secondary);justify-content:flex-end;align-items:center;gap:8px;padding-top:8px;font-size:11px;display:flex}.tE0trG_pager button{border:1px solid var(--dsw-alias-border-l2);height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:4px;padding:0 8px;font-size:11px}.tE0trG_pager button:disabled{opacity:.4;cursor:default}";
|
|
1611
|
+
const tagId$4 = "@sparkelf/dsh-dataops-managed/NotificationBell.module.css";
|
|
1612
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
|
|
736
1613
|
const tag = document.createElement("style");
|
|
737
1614
|
tag.dataset.plugin = "@sparkelf/dsh-dataops-managed";
|
|
738
|
-
tag.dataset.pluginCss = tagId$
|
|
739
|
-
tag.textContent = css$
|
|
1615
|
+
tag.dataset.pluginCss = tagId$4;
|
|
1616
|
+
tag.textContent = css$4;
|
|
740
1617
|
document.head.appendChild(tag);
|
|
741
1618
|
}
|
|
742
|
-
var
|
|
743
|
-
"
|
|
744
|
-
"
|
|
745
|
-
"
|
|
746
|
-
"
|
|
747
|
-
"
|
|
748
|
-
"
|
|
749
|
-
"
|
|
750
|
-
"
|
|
751
|
-
"
|
|
752
|
-
"
|
|
753
|
-
"
|
|
754
|
-
"
|
|
755
|
-
"
|
|
756
|
-
"
|
|
757
|
-
"
|
|
758
|
-
"
|
|
759
|
-
"
|
|
760
|
-
"
|
|
761
|
-
"detailRow": "pN-avG_detailRow",
|
|
762
|
-
"chip": "pN-avG_chip",
|
|
763
|
-
"section": "pN-avG_section",
|
|
764
|
-
"cardMarks": "pN-avG_cardMarks",
|
|
765
|
-
"detailLabel": "pN-avG_detailLabel",
|
|
766
|
-
"chipOn": "pN-avG_chipOn",
|
|
767
|
-
"cardDesc": "pN-avG_cardDesc",
|
|
768
|
-
"empty": "pN-avG_empty",
|
|
769
|
-
"title": "pN-avG_title",
|
|
770
|
-
"emptyText": "pN-avG_emptyText"
|
|
1619
|
+
var NotificationBell_module_css_default = {
|
|
1620
|
+
"empty": "tE0trG_empty",
|
|
1621
|
+
"button": "tE0trG_button",
|
|
1622
|
+
"rowTime": "tE0trG_rowTime",
|
|
1623
|
+
"pager": "tE0trG_pager",
|
|
1624
|
+
"rowText": "tE0trG_rowText",
|
|
1625
|
+
"demo": "tE0trG_demo",
|
|
1626
|
+
"row": "tE0trG_row",
|
|
1627
|
+
"rowTitle": "tE0trG_rowTitle",
|
|
1628
|
+
"actions": "tE0trG_actions",
|
|
1629
|
+
"dialog": "tE0trG_dialog",
|
|
1630
|
+
"rowMeta": "tE0trG_rowMeta",
|
|
1631
|
+
"dot": "tE0trG_dot",
|
|
1632
|
+
"rowIcon": "tE0trG_rowIcon",
|
|
1633
|
+
"list": "tE0trG_list",
|
|
1634
|
+
"badge": "tE0trG_badge",
|
|
1635
|
+
"bell": "tE0trG_bell",
|
|
1636
|
+
"rowBody": "tE0trG_rowBody",
|
|
1637
|
+
"linkButton": "tE0trG_linkButton"
|
|
771
1638
|
};
|
|
772
1639
|
//#endregion
|
|
773
|
-
//#region src/client/
|
|
774
|
-
/**
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1640
|
+
//#region src/client/NotificationBell.tsx
|
|
1641
|
+
/** Colour tone and glyph for one notification type, matching the automation-task rows. */
|
|
1642
|
+
function notificationTone(type) {
|
|
1643
|
+
if (type === "approved" || type === "published" || type === "success") return "success";
|
|
1644
|
+
if (type === "rejected") return "danger";
|
|
1645
|
+
if (type === "unpublished") return "warn";
|
|
1646
|
+
return "info";
|
|
1647
|
+
}
|
|
1648
|
+
function NotificationIcon({ tone }) {
|
|
1649
|
+
if (tone === "success") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1650
|
+
width: "14",
|
|
1651
|
+
height: "14",
|
|
1652
|
+
viewBox: "0 0 16 16",
|
|
1653
|
+
fill: "none",
|
|
1654
|
+
"aria-hidden": "true",
|
|
1655
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1656
|
+
cx: "8",
|
|
1657
|
+
cy: "8",
|
|
1658
|
+
r: "6.2",
|
|
1659
|
+
stroke: "currentColor",
|
|
1660
|
+
strokeWidth: "1.3"
|
|
1661
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1662
|
+
d: "m5.3 8.1 1.8 1.8 3.6-3.8",
|
|
1663
|
+
stroke: "currentColor",
|
|
1664
|
+
strokeWidth: "1.3",
|
|
1665
|
+
strokeLinecap: "round",
|
|
1666
|
+
strokeLinejoin: "round"
|
|
1667
|
+
})]
|
|
1668
|
+
});
|
|
1669
|
+
if (tone === "danger") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1670
|
+
width: "14",
|
|
1671
|
+
height: "14",
|
|
1672
|
+
viewBox: "0 0 16 16",
|
|
1673
|
+
fill: "none",
|
|
1674
|
+
"aria-hidden": "true",
|
|
789
1675
|
children: [
|
|
790
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1676
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1677
|
+
d: "M8 2.3 14 13H2L8 2.3Z",
|
|
1678
|
+
stroke: "currentColor",
|
|
1679
|
+
strokeWidth: "1.3",
|
|
1680
|
+
strokeLinejoin: "round"
|
|
1681
|
+
}),
|
|
1682
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1683
|
+
d: "M8 6v3.1",
|
|
1684
|
+
stroke: "currentColor",
|
|
1685
|
+
strokeWidth: "1.3",
|
|
1686
|
+
strokeLinecap: "round"
|
|
1687
|
+
}),
|
|
1688
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1689
|
+
cx: "8",
|
|
1690
|
+
cy: "11.2",
|
|
1691
|
+
r: ".7",
|
|
1692
|
+
fill: "currentColor"
|
|
1693
|
+
})
|
|
1694
|
+
]
|
|
1695
|
+
});
|
|
1696
|
+
if (tone === "warn") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1697
|
+
width: "14",
|
|
1698
|
+
height: "14",
|
|
1699
|
+
viewBox: "0 0 16 16",
|
|
1700
|
+
fill: "none",
|
|
1701
|
+
"aria-hidden": "true",
|
|
1702
|
+
children: [
|
|
1703
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1704
|
+
cx: "8",
|
|
1705
|
+
cy: "8",
|
|
1706
|
+
r: "6.2",
|
|
1707
|
+
stroke: "currentColor",
|
|
1708
|
+
strokeWidth: "1.3"
|
|
1709
|
+
}),
|
|
1710
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1711
|
+
d: "M8 4.8v3.6",
|
|
1712
|
+
stroke: "currentColor",
|
|
1713
|
+
strokeWidth: "1.3",
|
|
1714
|
+
strokeLinecap: "round"
|
|
1715
|
+
}),
|
|
1716
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1717
|
+
cx: "8",
|
|
1718
|
+
cy: "11.1",
|
|
1719
|
+
r: ".7",
|
|
1720
|
+
fill: "currentColor"
|
|
1721
|
+
})
|
|
1722
|
+
]
|
|
1723
|
+
});
|
|
1724
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1725
|
+
width: "14",
|
|
1726
|
+
height: "14",
|
|
1727
|
+
viewBox: "0 0 16 16",
|
|
1728
|
+
fill: "none",
|
|
1729
|
+
"aria-hidden": "true",
|
|
1730
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1731
|
+
d: "M4 2.5h5l3 3v8H4v-11Z",
|
|
1732
|
+
stroke: "currentColor",
|
|
1733
|
+
strokeWidth: "1.2",
|
|
1734
|
+
strokeLinejoin: "round"
|
|
1735
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1736
|
+
d: "M9 2.5v3h3M6 8h4M6 10.5h3",
|
|
1737
|
+
stroke: "currentColor",
|
|
1738
|
+
strokeWidth: "1.2",
|
|
1739
|
+
strokeLinecap: "round"
|
|
1740
|
+
})]
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* A bell with an unread badge and the shared inbox.
|
|
1745
|
+
*
|
|
1746
|
+
* The bell is a button rather than a menu so keyboard focus and Escape follow the dialog
|
|
1747
|
+
* conventions the rest of the page already uses. Rows mark themselves read when opened;
|
|
1748
|
+
* "全部已读" is the one bulk action.
|
|
1749
|
+
*/
|
|
1750
|
+
function NotificationBell({ label, onOpen }) {
|
|
1751
|
+
const state = (0, react.useSyncExternalStore)(subscribeNotifications, notificationSnapshot, notificationSnapshot);
|
|
1752
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1753
|
+
const [page, setPage] = (0, react.useState)(0);
|
|
1754
|
+
const items = sortNotifications(state.items);
|
|
1755
|
+
const unread = unreadCount(items);
|
|
1756
|
+
const pages = Math.max(1, Math.ceil(items.length / 20));
|
|
1757
|
+
const visible = items.slice(page * 20, page * 20 + 20);
|
|
1758
|
+
(0, react.useEffect)(() => {
|
|
1759
|
+
if (page >= pages) setPage(pages - 1);
|
|
1760
|
+
}, [page, pages]);
|
|
1761
|
+
(0, react.useEffect)(() => {
|
|
1762
|
+
loadNotifications();
|
|
1763
|
+
}, []);
|
|
1764
|
+
const openOne = (item) => {
|
|
1765
|
+
markRead([item.id]);
|
|
1766
|
+
setOpen(false);
|
|
1767
|
+
onOpen?.(item);
|
|
1768
|
+
};
|
|
1769
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1770
|
+
type: "button",
|
|
1771
|
+
className: NotificationBell_module_css_default.bell,
|
|
1772
|
+
"aria-label": unread === 0 ? label : label + "," + String(unread) + " 条未读",
|
|
1773
|
+
title: label,
|
|
1774
|
+
"data-notification-bell": "",
|
|
1775
|
+
"data-unread": unread,
|
|
1776
|
+
onClick: () => {
|
|
1777
|
+
setOpen(true);
|
|
1778
|
+
setPage(0);
|
|
1779
|
+
loadNotifications();
|
|
1780
|
+
},
|
|
1781
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1782
|
+
width: "16",
|
|
1783
|
+
height: "16",
|
|
1784
|
+
viewBox: "0 0 16 16",
|
|
1785
|
+
fill: "none",
|
|
1786
|
+
"aria-hidden": "true",
|
|
1787
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1788
|
+
d: "M8 2.2a4.1 4.1 0 0 0-4.1 4.1v2.3L2.7 10.8a.6.6 0 0 0 .5.9h9.6a.6.6 0 0 0 .5-.9L12.1 8.6V6.3A4.1 4.1 0 0 0 8 2.2Z",
|
|
1789
|
+
stroke: "currentColor",
|
|
1790
|
+
strokeWidth: "1.2",
|
|
1791
|
+
strokeLinejoin: "round"
|
|
1792
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1793
|
+
d: "M6.4 13.2a1.7 1.7 0 0 0 3.2 0",
|
|
1794
|
+
stroke: "currentColor",
|
|
1795
|
+
strokeWidth: "1.2",
|
|
1796
|
+
strokeLinecap: "round"
|
|
1797
|
+
})]
|
|
1798
|
+
}), unread === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1799
|
+
className: NotificationBell_module_css_default.badge,
|
|
1800
|
+
children: unread > 99 ? "99+" : unread
|
|
1801
|
+
})]
|
|
1802
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
1803
|
+
open,
|
|
1804
|
+
onClose: () => {
|
|
1805
|
+
setOpen(false);
|
|
1806
|
+
},
|
|
1807
|
+
title: label,
|
|
1808
|
+
closeLabel: "关闭",
|
|
1809
|
+
className: NotificationBell_module_css_default.dialog,
|
|
1810
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1811
|
+
className: NotificationBell_module_css_default.actions,
|
|
1812
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1813
|
+
type: "button",
|
|
1814
|
+
className: NotificationBell_module_css_default.linkButton,
|
|
1815
|
+
onClick: () => {
|
|
1816
|
+
markRead("all");
|
|
1817
|
+
},
|
|
1818
|
+
children: "全部已读"
|
|
1819
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1820
|
+
type: "button",
|
|
1821
|
+
className: NotificationBell_module_css_default.button,
|
|
1822
|
+
onClick: () => {
|
|
1823
|
+
setOpen(false);
|
|
1824
|
+
},
|
|
1825
|
+
children: "关闭"
|
|
1826
|
+
})]
|
|
1827
|
+
}),
|
|
1828
|
+
children: [
|
|
1829
|
+
reviewDemoMode() ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1830
|
+
className: NotificationBell_module_css_default.demo,
|
|
1831
|
+
children: "演示数据 · 后端契约到达后自动切换"
|
|
1832
|
+
}) : null,
|
|
1833
|
+
state.status === "unavailable" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1834
|
+
className: NotificationBell_module_css_default.empty,
|
|
1835
|
+
children: "通知服务待后端联调(task-35);上线后这里会显示审核、上架与 AI 报告事件。"
|
|
1836
|
+
}) : null,
|
|
1837
|
+
state.status !== "unavailable" && items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1838
|
+
className: NotificationBell_module_css_default.empty,
|
|
1839
|
+
children: "暂无通知。"
|
|
1840
|
+
}) : null,
|
|
1841
|
+
items.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
1842
|
+
className: NotificationBell_module_css_default.list,
|
|
1843
|
+
children: visible.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1844
|
+
type: "button",
|
|
1845
|
+
className: NotificationBell_module_css_default.row,
|
|
1846
|
+
onClick: () => {
|
|
1847
|
+
openOne(item);
|
|
1848
|
+
},
|
|
1849
|
+
children: [
|
|
1850
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1851
|
+
className: NotificationBell_module_css_default.rowIcon,
|
|
1852
|
+
"data-tone": notificationTone(item.type),
|
|
1853
|
+
"aria-hidden": "true",
|
|
1854
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationIcon, { tone: notificationTone(item.type) })
|
|
1855
|
+
}),
|
|
1856
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1857
|
+
className: NotificationBell_module_css_default.rowBody,
|
|
1858
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1859
|
+
className: NotificationBell_module_css_default.rowTitle,
|
|
1860
|
+
children: item.title
|
|
1861
|
+
}), item.body === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1862
|
+
className: NotificationBell_module_css_default.rowText,
|
|
1863
|
+
children: item.body
|
|
1864
|
+
})]
|
|
1865
|
+
}),
|
|
1866
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1867
|
+
className: NotificationBell_module_css_default.rowMeta,
|
|
1868
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1869
|
+
className: NotificationBell_module_css_default.rowTime,
|
|
1870
|
+
children: item.createdAt.slice(0, 16).replace("T", " ")
|
|
1871
|
+
}), item.read ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1872
|
+
className: NotificationBell_module_css_default.dot,
|
|
1873
|
+
"aria-hidden": "true"
|
|
1874
|
+
})]
|
|
1875
|
+
})
|
|
1876
|
+
]
|
|
1877
|
+
}) }, item.id))
|
|
1878
|
+
}),
|
|
1879
|
+
items.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1880
|
+
className: NotificationBell_module_css_default.pager,
|
|
1881
|
+
children: [
|
|
1882
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1883
|
+
type: "button",
|
|
1884
|
+
disabled: page === 0,
|
|
1885
|
+
onClick: () => {
|
|
1886
|
+
setPage((current) => Math.max(0, current - 1));
|
|
1887
|
+
},
|
|
1888
|
+
children: "上一页"
|
|
1889
|
+
}),
|
|
1890
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1891
|
+
"第 ",
|
|
1892
|
+
page + 1,
|
|
1893
|
+
" / ",
|
|
1894
|
+
pages,
|
|
1895
|
+
" 页 · 共 ",
|
|
1896
|
+
items.length,
|
|
1897
|
+
" 条"
|
|
1898
|
+
] }),
|
|
1899
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1900
|
+
type: "button",
|
|
1901
|
+
disabled: page >= pages - 1,
|
|
1902
|
+
onClick: () => {
|
|
1903
|
+
setPage((current) => Math.min(pages - 1, current + 1));
|
|
1904
|
+
},
|
|
1905
|
+
children: "下一页"
|
|
1906
|
+
})
|
|
1907
|
+
]
|
|
1908
|
+
})
|
|
1909
|
+
]
|
|
1910
|
+
})] });
|
|
1911
|
+
}
|
|
1912
|
+
/** The automation page's seat: the same inbox under the workflow plaza's heading. */
|
|
1913
|
+
function WorkflowPlazaBell() {
|
|
1914
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationBell, { label: "自动化任务通知" });
|
|
1915
|
+
}
|
|
1916
|
+
//#endregion
|
|
1917
|
+
//#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/SkillPlazaSection.module.css.mjs
|
|
1918
|
+
const css$3 = "._9FdZvq_section{box-sizing:border-box;flex-direction:column;width:100%;max-width:960px;margin:0 auto;display:flex}._9FdZvq_head{flex-direction:column;margin-bottom:8px;display:flex}._9FdZvq_title{color:var(--dsw-alias-label-primary);align-items:baseline;gap:8px;margin:0;font-size:14px;font-weight:500;line-height:20px;display:flex}._9FdZvq_titleCount{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400}._9FdZvq_status{text-align:center;color:var(--dsw-alias-label-tertiary);margin:0;padding:20px 0;font-size:13px;line-height:20px}._9FdZvq_tagBar{flex-wrap:wrap;gap:6px;margin-bottom:8px;display:flex}._9FdZvq_chip{background:var(--dsw-alias-bg-module-platform);height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;border:0;border-radius:12px;align-items:center;padding:0 10px;font-size:12px;line-height:18px;transition:background .12s;display:inline-flex}._9FdZvq_chip:hover{background:var(--dsw-alias-interactive-bg-hover)}._9FdZvq_chip:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}._9FdZvq_chipOn{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-state-business-primary);font-weight:500}._9FdZvq_notice{color:var(--dsw-alias-state-business-primary);margin:4px 0 0;font-size:12px;line-height:18px}._9FdZvq_cards{grid-template-columns:repeat(auto-fill,minmax(272px,1fr));grid-auto-rows:max-content;align-content:start;align-items:stretch;gap:12px;margin:0;padding:0;list-style:none;display:grid}._9FdZvq_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:10px;flex-direction:column;align-self:stretch;gap:10px;min-width:0;height:auto;padding:12px;display:flex}._9FdZvq_cardTop{justify-content:space-between;align-items:center;gap:10px;min-height:28px;display:flex}._9FdZvq_cardIcon{background:var(--dsw-alias-bg-layer-2);width:28px;height:28px;color:var(--dsw-alias-label-secondary);border-radius:50%;flex:none;place-items:center;display:grid}._9FdZvq_cardTitle{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:14px;font-weight:600;line-height:20px;overflow:hidden}._9FdZvq_mine{flex:none}._9FdZvq_cardDesc{min-height:36px;color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:18px;display:-webkit-box;overflow:hidden}._9FdZvq_cardMarks{flex-wrap:wrap;align-content:flex-start;gap:6px;min-height:20px;display:flex}._9FdZvq_mark{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-state-business-primary);white-space:nowrap;border:0;border-radius:4px;padding:2px 7px;font-size:11px;font-weight:500;line-height:16px}._9FdZvq_cardEnd{flex:none;align-items:center;gap:4px;margin-top:auto;padding-top:4px;display:flex}._9FdZvq_detailButton{margin-left:auto}._9FdZvq_detail{flex-direction:column;gap:12px;margin:0;display:flex}._9FdZvq_dialog{width:min(560px,100%)}._9FdZvq_detailRow{flex-direction:column;gap:2px;display:flex}._9FdZvq_detailLabel{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px}._9FdZvq_detailValue{max-height:160px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:12px;line-height:18px;overflow:auto}._9FdZvq_empty{text-align:center;flex-direction:column;align-items:center;gap:10px;padding:28px 16px 32px;display:flex}._9FdZvq_emptyIcon{border:.5px solid var(--dsw-alias-border-l3);width:56px;height:56px;color:var(--dsw-alias-label-tertiary);border-radius:14px;justify-content:center;align-items:center;display:inline-flex}._9FdZvq_emptyText{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:20px}._9FdZvq_headRow{align-items:center;gap:6px;display:flex}._9FdZvq_headRow ._9FdZvq_title{flex:none}._9FdZvq_demoTag{background:var(--dsw-alias-state-warn-secondary);color:var(--dsw-alias-state-warn-primary);border-radius:4px;padding:1px 6px;font-size:10px;line-height:16px}._9FdZvq_tabs{border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:2px;margin-bottom:8px;display:flex}._9FdZvq_tab{height:30px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-bottom:2px solid #0000;align-items:center;gap:5px;padding:0 9px;font-size:12px;display:inline-flex}._9FdZvq_tab:hover{color:var(--dsw-alias-label-primary)}._9FdZvq_tabActive{color:var(--dsw-alias-state-business-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:500}._9FdZvq_tabCount{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);border-radius:7px;padding:0 4px;font-size:10px;line-height:14px}._9FdZvq_tagTop{text-overflow:ellipsis;white-space:nowrap;border:1px solid var(--dsw-alias-border-l2);max-width:40%;color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:1px 6px;font-size:10px;line-height:16px;overflow:hidden}._9FdZvq_sharedTag{color:#fff;background:#0e6b3a;border-radius:4px;padding:2px 7px;font-size:11px;font-weight:600;line-height:16px}._9FdZvq_pendingTag{color:#fff;background:#7a4a00;border-radius:4px;padding:2px 7px;font-size:11px;font-weight:600;line-height:16px}._9FdZvq_rejectedTag{color:#fff;background:#a8231b;border-radius:4px;padding:2px 7px;font-size:11px;font-weight:600;line-height:16px}._9FdZvq_reviewButton{margin-left:auto}._9FdZvq_reviewNote{border-left:3px solid var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-primary);margin:0;padding:6px 8px;font-size:11px;line-height:16px}._9FdZvq_auditDialog{width:min(640px,100vw - 32px)}._9FdZvq_audit{flex-direction:column;gap:14px;display:flex}._9FdZvq_auditFooter{justify-content:flex-end;gap:6px;display:flex}._9FdZvq_risk{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:6px;flex-direction:column;gap:8px;padding:10px;display:flex}._9FdZvq_riskHead{justify-content:space-between;align-items:center;gap:8px;font-size:12px;display:flex}._9FdZvq_riskEmpty,._9FdZvq_riskFailed{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:16px}._9FdZvq_riskFailed{color:var(--dsw-alias-state-error-primary)}._9FdZvq_riskSummary{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;align-items:center;gap:6px;font-size:12px;line-height:18px;display:flex}._9FdZvq_riskScore{color:var(--dsw-alias-label-primary);font-weight:600}._9FdZvq_severity{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);text-transform:uppercase;border-radius:3px;padding:0 5px;font-size:10px;line-height:16px}._9FdZvq_severity[data-severity=high],._9FdZvq_severity[data-severity=critical]{background:var(--dsw-alias-state-error-secondary);color:var(--dsw-alias-state-error-primary)}._9FdZvq_severity[data-severity=medium]{background:var(--dsw-alias-state-warn-secondary);color:var(--dsw-alias-state-warn-primary)}._9FdZvq_findings{flex-direction:column;gap:6px;margin:0;padding:0;list-style:none;display:flex}._9FdZvq_finding{flex-wrap:wrap;align-items:baseline;gap:5px;font-size:11px;line-height:16px;display:flex}._9FdZvq_findingText{color:var(--dsw-alias-label-secondary);flex-basis:100%}._9FdZvq_findingHint{color:var(--dsw-alias-label-tertiary);flex-basis:100%}._9FdZvq_noteField{color:var(--dsw-alias-label-secondary);flex-direction:column;gap:5px;font-size:12px;display:flex}._9FdZvq_noteField textarea{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);min-height:64px;color:var(--dsw-alias-label-primary);font:inherit;resize:vertical;border-radius:5px;padding:7px 9px}._9FdZvq_reviewError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:11px}";
|
|
1919
|
+
const tagId$3 = "@sparkelf/dsh-dataops-managed/SkillPlazaSection.module.css";
|
|
1920
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
1921
|
+
const tag = document.createElement("style");
|
|
1922
|
+
tag.dataset.plugin = "@sparkelf/dsh-dataops-managed";
|
|
1923
|
+
tag.dataset.pluginCss = tagId$3;
|
|
1924
|
+
tag.textContent = css$3;
|
|
1925
|
+
document.head.appendChild(tag);
|
|
1926
|
+
}
|
|
1927
|
+
var SkillPlazaSection_module_css_default = {
|
|
1928
|
+
"reviewError": "_9FdZvq_reviewError",
|
|
1929
|
+
"chipOn": "_9FdZvq_chipOn",
|
|
1930
|
+
"tabs": "_9FdZvq_tabs",
|
|
1931
|
+
"sharedTag": "_9FdZvq_sharedTag",
|
|
1932
|
+
"noteField": "_9FdZvq_noteField",
|
|
1933
|
+
"mark": "_9FdZvq_mark",
|
|
1934
|
+
"severity": "_9FdZvq_severity",
|
|
1935
|
+
"findingText": "_9FdZvq_findingText",
|
|
1936
|
+
"section": "_9FdZvq_section",
|
|
1937
|
+
"head": "_9FdZvq_head",
|
|
1938
|
+
"cardMarks": "_9FdZvq_cardMarks",
|
|
1939
|
+
"cardTop": "_9FdZvq_cardTop",
|
|
1940
|
+
"cardTitle": "_9FdZvq_cardTitle",
|
|
1941
|
+
"detailLabel": "_9FdZvq_detailLabel",
|
|
1942
|
+
"riskHead": "_9FdZvq_riskHead",
|
|
1943
|
+
"riskSummary": "_9FdZvq_riskSummary",
|
|
1944
|
+
"tab": "_9FdZvq_tab",
|
|
1945
|
+
"reviewButton": "_9FdZvq_reviewButton",
|
|
1946
|
+
"headRow": "_9FdZvq_headRow",
|
|
1947
|
+
"risk": "_9FdZvq_risk",
|
|
1948
|
+
"tagBar": "_9FdZvq_tagBar",
|
|
1949
|
+
"riskEmpty": "_9FdZvq_riskEmpty",
|
|
1950
|
+
"emptyText": "_9FdZvq_emptyText",
|
|
1951
|
+
"cards": "_9FdZvq_cards",
|
|
1952
|
+
"cardEnd": "_9FdZvq_cardEnd",
|
|
1953
|
+
"chip": "_9FdZvq_chip",
|
|
1954
|
+
"notice": "_9FdZvq_notice",
|
|
1955
|
+
"cardIcon": "_9FdZvq_cardIcon",
|
|
1956
|
+
"detailButton": "_9FdZvq_detailButton",
|
|
1957
|
+
"status": "_9FdZvq_status",
|
|
1958
|
+
"tagTop": "_9FdZvq_tagTop",
|
|
1959
|
+
"title": "_9FdZvq_title",
|
|
1960
|
+
"detailRow": "_9FdZvq_detailRow",
|
|
1961
|
+
"detailValue": "_9FdZvq_detailValue",
|
|
1962
|
+
"card": "_9FdZvq_card",
|
|
1963
|
+
"detail": "_9FdZvq_detail",
|
|
1964
|
+
"tabActive": "_9FdZvq_tabActive",
|
|
1965
|
+
"tabCount": "_9FdZvq_tabCount",
|
|
1966
|
+
"pendingTag": "_9FdZvq_pendingTag",
|
|
1967
|
+
"rejectedTag": "_9FdZvq_rejectedTag",
|
|
1968
|
+
"demoTag": "_9FdZvq_demoTag",
|
|
1969
|
+
"empty": "_9FdZvq_empty",
|
|
1970
|
+
"auditDialog": "_9FdZvq_auditDialog",
|
|
1971
|
+
"audit": "_9FdZvq_audit",
|
|
1972
|
+
"auditFooter": "_9FdZvq_auditFooter",
|
|
1973
|
+
"riskScore": "_9FdZvq_riskScore",
|
|
1974
|
+
"finding": "_9FdZvq_finding",
|
|
1975
|
+
"findingHint": "_9FdZvq_findingHint",
|
|
1976
|
+
"riskFailed": "_9FdZvq_riskFailed",
|
|
1977
|
+
"emptyIcon": "_9FdZvq_emptyIcon",
|
|
1978
|
+
"reviewNote": "_9FdZvq_reviewNote",
|
|
1979
|
+
"cardDesc": "_9FdZvq_cardDesc",
|
|
1980
|
+
"dialog": "_9FdZvq_dialog",
|
|
1981
|
+
"findings": "_9FdZvq_findings",
|
|
1982
|
+
"titleCount": "_9FdZvq_titleCount",
|
|
1983
|
+
"mine": "_9FdZvq_mine"
|
|
1984
|
+
};
|
|
1985
|
+
//#endregion
|
|
1986
|
+
//#region src/client/SkillPlazaSection.tsx
|
|
1987
|
+
/** Tab counts from the same visible rows the cards render. */
|
|
1988
|
+
function countsOf(skills) {
|
|
1989
|
+
const counted = reviewCounts(skills);
|
|
1990
|
+
return {
|
|
1991
|
+
approved: counted.approved,
|
|
1992
|
+
pending: counted.pending,
|
|
1993
|
+
rejected: skills.filter((row) => row.reviewStatus === "rejected").length,
|
|
1994
|
+
total: counted.all
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
/** One skill as a card in the plaza grid. */
|
|
1998
|
+
function PlazaCard({ skill, busy, canAudit, onInstall, onUnshare, onReview, t }) {
|
|
1999
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2000
|
+
const status = normalizeReviewStatus(skill.reviewStatus);
|
|
2001
|
+
const tag = visibilityTag(skill);
|
|
2002
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
2003
|
+
className: SkillPlazaSection_module_css_default.card,
|
|
2004
|
+
"data-review-status": status,
|
|
2005
|
+
children: [
|
|
2006
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2007
|
+
className: SkillPlazaSection_module_css_default.cardTop,
|
|
2008
|
+
children: [
|
|
2009
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2010
|
+
className: SkillPlazaSection_module_css_default.cardIcon,
|
|
2011
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutlineRegular, { size: 16 })
|
|
2012
|
+
}),
|
|
2013
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2014
|
+
className: SkillPlazaSection_module_css_default.cardTitle,
|
|
2015
|
+
children: skill.name
|
|
2016
|
+
}),
|
|
2017
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2018
|
+
className: SkillPlazaSection_module_css_default.tagTop,
|
|
2019
|
+
title: tag.detail,
|
|
2020
|
+
children: tag.label
|
|
2021
|
+
})
|
|
2022
|
+
]
|
|
2023
|
+
}),
|
|
2024
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2025
|
+
className: SkillPlazaSection_module_css_default.cardMarks,
|
|
2026
|
+
children: [
|
|
2027
|
+
skill.sharedByMe ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2028
|
+
className: SkillPlazaSection_module_css_default.sharedTag,
|
|
2029
|
+
children: t("plazaSharedTag")
|
|
2030
|
+
}) : null,
|
|
2031
|
+
status === "pending" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2032
|
+
className: SkillPlazaSection_module_css_default.pendingTag,
|
|
2033
|
+
children: t("plazaTabPending")
|
|
2034
|
+
}) : null,
|
|
2035
|
+
status === "rejected" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2036
|
+
className: SkillPlazaSection_module_css_default.rejectedTag,
|
|
2037
|
+
children: t("plazaReject")
|
|
2038
|
+
}) : null,
|
|
2039
|
+
skill.tags.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
802
2040
|
className: SkillPlazaSection_module_css_default.mark,
|
|
803
|
-
children:
|
|
804
|
-
})
|
|
2041
|
+
children: item
|
|
2042
|
+
}, item))
|
|
805
2043
|
]
|
|
806
2044
|
}),
|
|
807
|
-
|
|
2045
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
808
2046
|
className: SkillPlazaSection_module_css_default.cardDesc,
|
|
809
|
-
children: skill.description
|
|
810
|
-
}),
|
|
811
|
-
skill.tags.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
812
|
-
className: SkillPlazaSection_module_css_default.cardMarks,
|
|
813
|
-
children: skill.tags.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
814
|
-
className: SkillPlazaSection_module_css_default.mark,
|
|
815
|
-
children: item
|
|
816
|
-
}, item))
|
|
2047
|
+
children: skill.description ?? ""
|
|
817
2048
|
}),
|
|
2049
|
+
status === "rejected" && skill.reviewNote !== null && skill.reviewNote !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2050
|
+
className: SkillPlazaSection_module_css_default.reviewNote,
|
|
2051
|
+
children: skill.reviewNote
|
|
2052
|
+
}) : null,
|
|
818
2053
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
819
2054
|
className: SkillPlazaSection_module_css_default.cardEnd,
|
|
820
2055
|
children: [
|
|
2056
|
+
canAudit && status === "pending" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2057
|
+
variant: "outline",
|
|
2058
|
+
size: "sm",
|
|
2059
|
+
className: SkillPlazaSection_module_css_default.reviewButton,
|
|
2060
|
+
onClick: onReview,
|
|
2061
|
+
children: t("plazaReview")
|
|
2062
|
+
}) : null,
|
|
821
2063
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
822
2064
|
variant: "outline",
|
|
823
2065
|
size: "sm",
|
|
@@ -886,43 +2128,171 @@ window.__ModuleLoader__.load({
|
|
|
886
2128
|
]
|
|
887
2129
|
});
|
|
888
2130
|
}
|
|
2131
|
+
/** The AI report block inside the audit dialog. */
|
|
2132
|
+
function RiskReportView({ report, busy, failed, onScan, t }) {
|
|
2133
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2134
|
+
className: SkillPlazaSection_module_css_default.risk,
|
|
2135
|
+
children: [
|
|
2136
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2137
|
+
className: SkillPlazaSection_module_css_default.riskHead,
|
|
2138
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("plazaRiskTitle") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2139
|
+
variant: "outline",
|
|
2140
|
+
size: "sm",
|
|
2141
|
+
disabled: busy,
|
|
2142
|
+
onClick: onScan,
|
|
2143
|
+
children: busy ? t("plazaRiskScanning") : t("plazaRiskScan")
|
|
2144
|
+
})]
|
|
2145
|
+
}),
|
|
2146
|
+
failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2147
|
+
className: SkillPlazaSection_module_css_default.riskFailed,
|
|
2148
|
+
children: t("plazaRiskFailed")
|
|
2149
|
+
}) : null,
|
|
2150
|
+
report === null ? busy ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2151
|
+
className: SkillPlazaSection_module_css_default.riskEmpty,
|
|
2152
|
+
children: t("plazaRiskNone")
|
|
2153
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2154
|
+
className: SkillPlazaSection_module_css_default.riskSummary,
|
|
2155
|
+
children: [
|
|
2156
|
+
report.score === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2157
|
+
className: SkillPlazaSection_module_css_default.riskScore,
|
|
2158
|
+
children: [
|
|
2159
|
+
t("plazaRiskScore"),
|
|
2160
|
+
" ",
|
|
2161
|
+
report.score
|
|
2162
|
+
]
|
|
2163
|
+
}),
|
|
2164
|
+
report.severity === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2165
|
+
className: SkillPlazaSection_module_css_default.severity,
|
|
2166
|
+
"data-severity": report.severity,
|
|
2167
|
+
children: report.severity
|
|
2168
|
+
}),
|
|
2169
|
+
report.summary === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: report.summary })
|
|
2170
|
+
]
|
|
2171
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2172
|
+
className: SkillPlazaSection_module_css_default.findings,
|
|
2173
|
+
children: report.findings.map((finding, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
2174
|
+
className: SkillPlazaSection_module_css_default.finding,
|
|
2175
|
+
children: [
|
|
2176
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2177
|
+
className: SkillPlazaSection_module_css_default.severity,
|
|
2178
|
+
"data-severity": finding.severity,
|
|
2179
|
+
children: finding.severity
|
|
2180
|
+
}),
|
|
2181
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: finding.category }),
|
|
2182
|
+
finding.evidence === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2183
|
+
className: SkillPlazaSection_module_css_default.findingText,
|
|
2184
|
+
children: finding.evidence
|
|
2185
|
+
}),
|
|
2186
|
+
finding.recommendation === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2187
|
+
className: SkillPlazaSection_module_css_default.findingHint,
|
|
2188
|
+
children: finding.recommendation
|
|
2189
|
+
})
|
|
2190
|
+
]
|
|
2191
|
+
}, String(index)))
|
|
2192
|
+
})] })
|
|
2193
|
+
]
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
889
2196
|
/**
|
|
890
2197
|
* Browse, share, and install skills other users published.
|
|
891
2198
|
*
|
|
892
|
-
*
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
* @param props - Skill center injection values.
|
|
896
|
-
* @returns The plaza section, or nothing before injection.
|
|
2199
|
+
* Review is a permissioned overlay on the same plaza: the tab strip and the audit dialog only
|
|
2200
|
+
* appear when the Host says the caller may review, and the Host additionally narrows the pending
|
|
2201
|
+
* list to the caller's department subtree.
|
|
897
2202
|
*/
|
|
898
2203
|
function SkillPlazaSection(props) {
|
|
899
2204
|
const { t } = props;
|
|
900
2205
|
const [state, setState] = (0, react.useState)({ kind: "loading" });
|
|
2206
|
+
const [context, setContext] = (0, react.useState)(void 0);
|
|
2207
|
+
const [tab, setTab] = (0, react.useState)("approved");
|
|
901
2208
|
const [tag, setTag] = (0, react.useState)("");
|
|
902
2209
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
903
|
-
const query = props.query ?? "";
|
|
904
2210
|
const [notice, setNotice] = (0, react.useState)(null);
|
|
2211
|
+
const [audit, setAudit] = (0, react.useState)(null);
|
|
2212
|
+
const [risk, setRisk] = (0, react.useState)(null);
|
|
2213
|
+
const [riskBusy, setRiskBusy] = (0, react.useState)(false);
|
|
2214
|
+
const [riskFailed, setRiskFailed] = (0, react.useState)(false);
|
|
2215
|
+
const [reviewNote, setReviewNote] = (0, react.useState)("");
|
|
2216
|
+
const [reviewBusy, setReviewBusy] = (0, react.useState)(false);
|
|
2217
|
+
const [reviewError, setReviewError] = (0, react.useState)(null);
|
|
2218
|
+
const query = props.query ?? "";
|
|
2219
|
+
const allowed = canReview(context);
|
|
2220
|
+
(0, react.useEffect)(() => {
|
|
2221
|
+
let live = true;
|
|
2222
|
+
fetchReviewContext().then((next) => {
|
|
2223
|
+
if (live) setContext(next);
|
|
2224
|
+
}).catch(() => {
|
|
2225
|
+
if (live) setContext(null);
|
|
2226
|
+
});
|
|
2227
|
+
return () => {
|
|
2228
|
+
live = false;
|
|
2229
|
+
};
|
|
2230
|
+
}, []);
|
|
905
2231
|
const load = (0, react.useCallback)(async (filter) => {
|
|
906
2232
|
try {
|
|
907
|
-
|
|
908
|
-
|
|
2233
|
+
if (reviewDemoMode()) {
|
|
2234
|
+
const demo = demoPlazaSkills();
|
|
2235
|
+
setState({
|
|
2236
|
+
kind: "ready",
|
|
2237
|
+
listing: {
|
|
2238
|
+
skills: [...filterByReview(demo, filter.tab)],
|
|
2239
|
+
tags: [
|
|
2240
|
+
"性能",
|
|
2241
|
+
"数据",
|
|
2242
|
+
"数据库",
|
|
2243
|
+
"质量",
|
|
2244
|
+
"经营",
|
|
2245
|
+
"日报",
|
|
2246
|
+
"客户"
|
|
2247
|
+
],
|
|
2248
|
+
counts: countsOf(demo)
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
const listing = await fetchPlaza({
|
|
2254
|
+
tag: filter.tag,
|
|
2255
|
+
query: filter.query,
|
|
2256
|
+
status: allowed ? "all" : "approved"
|
|
2257
|
+
});
|
|
2258
|
+
if (listing === null) {
|
|
2259
|
+
setState({ kind: "unavailable" });
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
const visible = allowed ? listing.skills : listing.skills.filter((row) => normalizeReviewStatus(row.reviewStatus) === "approved");
|
|
2263
|
+
setState({
|
|
909
2264
|
kind: "ready",
|
|
910
|
-
listing
|
|
2265
|
+
listing: {
|
|
2266
|
+
...listing,
|
|
2267
|
+
skills: [...filterByReview(visible, filter.tab)],
|
|
2268
|
+
counts: countsOf(visible)
|
|
2269
|
+
}
|
|
911
2270
|
});
|
|
912
2271
|
} catch {
|
|
913
2272
|
setState({ kind: "failed" });
|
|
914
2273
|
}
|
|
915
|
-
}, []);
|
|
2274
|
+
}, [allowed]);
|
|
916
2275
|
(0, react.useEffect)(() => {
|
|
917
|
-
load({
|
|
918
|
-
|
|
919
|
-
|
|
2276
|
+
load({
|
|
2277
|
+
tab,
|
|
2278
|
+
tag,
|
|
2279
|
+
query
|
|
2280
|
+
});
|
|
2281
|
+
}, [
|
|
2282
|
+
load,
|
|
2283
|
+
tab,
|
|
2284
|
+
tag,
|
|
2285
|
+
query
|
|
2286
|
+
]);
|
|
2287
|
+
const refresh = (0, react.useCallback)(async (nextTab = tab, nextTag = tag) => {
|
|
920
2288
|
await load({
|
|
2289
|
+
tab: nextTab,
|
|
921
2290
|
tag: nextTag,
|
|
922
|
-
query
|
|
2291
|
+
query
|
|
923
2292
|
});
|
|
924
2293
|
}, [
|
|
925
2294
|
load,
|
|
2295
|
+
tab,
|
|
926
2296
|
tag,
|
|
927
2297
|
query
|
|
928
2298
|
]);
|
|
@@ -939,16 +2309,75 @@ window.__ModuleLoader__.load({
|
|
|
939
2309
|
setBusy(false);
|
|
940
2310
|
}
|
|
941
2311
|
}, [refresh, t]);
|
|
2312
|
+
const runScan = async (skill) => {
|
|
2313
|
+
setRiskBusy(true);
|
|
2314
|
+
setRiskFailed(false);
|
|
2315
|
+
try {
|
|
2316
|
+
const report = await scanReview("skill", [skill.id]);
|
|
2317
|
+
if (report === null) setRiskFailed(true);
|
|
2318
|
+
else setRisk(report);
|
|
2319
|
+
} catch {
|
|
2320
|
+
setRiskFailed(true);
|
|
2321
|
+
} finally {
|
|
2322
|
+
setRiskBusy(false);
|
|
2323
|
+
}
|
|
2324
|
+
};
|
|
2325
|
+
const openAudit = (skill) => {
|
|
2326
|
+
setAudit(skill);
|
|
2327
|
+
setRisk(skill.riskReport ?? null);
|
|
2328
|
+
setRiskFailed(false);
|
|
2329
|
+
setReviewNote("");
|
|
2330
|
+
setReviewError(null);
|
|
2331
|
+
if (skill.riskReport === null || skill.riskReport === void 0) runScan(skill);
|
|
2332
|
+
};
|
|
2333
|
+
const submitReview = async (decision) => {
|
|
2334
|
+
if (audit === null || reviewBusy) return;
|
|
2335
|
+
if (decision === "reject" && reviewNote.trim() === "") {
|
|
2336
|
+
setReviewError(t === void 0 ? null : t("plazaReviewNote"));
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
setReviewBusy(true);
|
|
2340
|
+
setReviewError(null);
|
|
2341
|
+
try {
|
|
2342
|
+
if (!await decideReview("skill", [audit.id], decision, reviewNote.trim())) {
|
|
2343
|
+
setReviewError(t === void 0 ? null : t("plazaReviewFailed"));
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
setAudit(null);
|
|
2347
|
+
setNotice(t === void 0 ? null : t("plazaReviewDone"));
|
|
2348
|
+
await refresh("pending");
|
|
2349
|
+
} catch {
|
|
2350
|
+
setReviewError(t === void 0 ? null : t("plazaReviewFailed"));
|
|
2351
|
+
} finally {
|
|
2352
|
+
setReviewBusy(false);
|
|
2353
|
+
}
|
|
2354
|
+
};
|
|
942
2355
|
if (t === void 0) return null;
|
|
943
2356
|
if (state.kind === "loading") return null;
|
|
944
2357
|
const head = (count) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
945
2358
|
className: SkillPlazaSection_module_css_default.head,
|
|
946
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
947
|
-
className: SkillPlazaSection_module_css_default.
|
|
948
|
-
children: [
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
2359
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2360
|
+
className: SkillPlazaSection_module_css_default.headRow,
|
|
2361
|
+
children: [
|
|
2362
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
|
|
2363
|
+
className: SkillPlazaSection_module_css_default.title,
|
|
2364
|
+
children: [t("plazaTitle"), count === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2365
|
+
className: SkillPlazaSection_module_css_default.titleCount,
|
|
2366
|
+
children: count
|
|
2367
|
+
})]
|
|
2368
|
+
}),
|
|
2369
|
+
reviewDemoMode() ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2370
|
+
className: SkillPlazaSection_module_css_default.demoTag,
|
|
2371
|
+
children: t("plazaDemo")
|
|
2372
|
+
}) : null,
|
|
2373
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationBell, {
|
|
2374
|
+
label: t("plazaTitle") + "通知",
|
|
2375
|
+
onOpen: (item) => {
|
|
2376
|
+
const next = notificationTab(item);
|
|
2377
|
+
if (next !== void 0) setTab(next);
|
|
2378
|
+
}
|
|
2379
|
+
})
|
|
2380
|
+
]
|
|
952
2381
|
})
|
|
953
2382
|
});
|
|
954
2383
|
if (state.kind === "unavailable") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
@@ -985,10 +2414,40 @@ window.__ModuleLoader__.load({
|
|
|
985
2414
|
})]
|
|
986
2415
|
});
|
|
987
2416
|
const { listing } = state;
|
|
2417
|
+
const counts = listing.counts === void 0 ? reviewCounts(listing.skills) : {
|
|
2418
|
+
approved: listing.counts.approved,
|
|
2419
|
+
pending: listing.counts.pending,
|
|
2420
|
+
all: listing.counts.total
|
|
2421
|
+
};
|
|
2422
|
+
const tabs = reviewTabsFor(context);
|
|
2423
|
+
const tabLabel = (item) => item === "approved" ? t("plazaTabApproved") : item === "pending" ? t("plazaTabPending") : t("plazaTabAll");
|
|
988
2424
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
989
2425
|
className: SkillPlazaSection_module_css_default.section,
|
|
990
2426
|
children: [
|
|
991
|
-
head(
|
|
2427
|
+
head(counts[tab]),
|
|
2428
|
+
tabs.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2429
|
+
className: SkillPlazaSection_module_css_default.tabs,
|
|
2430
|
+
role: "tablist",
|
|
2431
|
+
"aria-label": t("plazaTitle"),
|
|
2432
|
+
children: tabs.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2433
|
+
type: "button",
|
|
2434
|
+
role: "tab",
|
|
2435
|
+
"aria-selected": tab === item,
|
|
2436
|
+
className: tab === item ? SkillPlazaSection_module_css_default.tab + " " + SkillPlazaSection_module_css_default.tabActive : SkillPlazaSection_module_css_default.tab,
|
|
2437
|
+
onClick: () => {
|
|
2438
|
+
setTab(item);
|
|
2439
|
+
load({
|
|
2440
|
+
tab: item,
|
|
2441
|
+
tag,
|
|
2442
|
+
query
|
|
2443
|
+
});
|
|
2444
|
+
},
|
|
2445
|
+
children: [tabLabel(item), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2446
|
+
className: SkillPlazaSection_module_css_default.tabCount,
|
|
2447
|
+
children: counts[item]
|
|
2448
|
+
})]
|
|
2449
|
+
}, item))
|
|
2450
|
+
}) : null,
|
|
992
2451
|
listing.tags.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
993
2452
|
className: SkillPlazaSection_module_css_default.tagBar,
|
|
994
2453
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -996,7 +2455,7 @@ window.__ModuleLoader__.load({
|
|
|
996
2455
|
className: tag === "" ? SkillPlazaSection_module_css_default.chip + " " + SkillPlazaSection_module_css_default.chipOn : SkillPlazaSection_module_css_default.chip,
|
|
997
2456
|
onClick: () => {
|
|
998
2457
|
setTag("");
|
|
999
|
-
refresh(""
|
|
2458
|
+
refresh(tab, "");
|
|
1000
2459
|
},
|
|
1001
2460
|
children: t("plazaAllTags")
|
|
1002
2461
|
}), listing.tags.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -1004,7 +2463,7 @@ window.__ModuleLoader__.load({
|
|
|
1004
2463
|
className: tag === item ? SkillPlazaSection_module_css_default.chip + " " + SkillPlazaSection_module_css_default.chipOn : SkillPlazaSection_module_css_default.chip,
|
|
1005
2464
|
onClick: () => {
|
|
1006
2465
|
setTag(item);
|
|
1007
|
-
refresh(
|
|
2466
|
+
refresh(tab, item);
|
|
1008
2467
|
},
|
|
1009
2468
|
children: item
|
|
1010
2469
|
}, item))]
|
|
@@ -1020,7 +2479,7 @@ window.__ModuleLoader__.load({
|
|
|
1020
2479
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconShareOutlineRegular, { size: 28 })
|
|
1021
2480
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1022
2481
|
className: SkillPlazaSection_module_css_default.emptyText,
|
|
1023
|
-
children: t("plazaEmpty")
|
|
2482
|
+
children: tab === "pending" ? t("plazaPendingEmpty") : t("plazaEmpty")
|
|
1024
2483
|
})]
|
|
1025
2484
|
}) : null,
|
|
1026
2485
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
@@ -1028,6 +2487,7 @@ window.__ModuleLoader__.load({
|
|
|
1028
2487
|
children: listing.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlazaCard, {
|
|
1029
2488
|
skill,
|
|
1030
2489
|
busy,
|
|
2490
|
+
canAudit: allowed,
|
|
1031
2491
|
t,
|
|
1032
2492
|
onInstall: () => {
|
|
1033
2493
|
act({
|
|
@@ -1040,67 +2500,151 @@ window.__ModuleLoader__.load({
|
|
|
1040
2500
|
action: "unshare",
|
|
1041
2501
|
id: skill.id
|
|
1042
2502
|
}, t("plazaUnshared"));
|
|
2503
|
+
},
|
|
2504
|
+
onReview: () => {
|
|
2505
|
+
openAudit(skill);
|
|
1043
2506
|
}
|
|
1044
2507
|
}, skill.id))
|
|
2508
|
+
}),
|
|
2509
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
2510
|
+
open: audit !== null,
|
|
2511
|
+
onClose: () => {
|
|
2512
|
+
if (!reviewBusy) setAudit(null);
|
|
2513
|
+
},
|
|
2514
|
+
title: t("plazaAuditTitle"),
|
|
2515
|
+
closeLabel: t("plazaClose"),
|
|
2516
|
+
className: SkillPlazaSection_module_css_default.auditDialog,
|
|
2517
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2518
|
+
className: SkillPlazaSection_module_css_default.auditFooter,
|
|
2519
|
+
children: [
|
|
2520
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2521
|
+
variant: "outline",
|
|
2522
|
+
disabled: reviewBusy,
|
|
2523
|
+
onClick: () => {
|
|
2524
|
+
setAudit(null);
|
|
2525
|
+
},
|
|
2526
|
+
children: t("plazaClose")
|
|
2527
|
+
}),
|
|
2528
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2529
|
+
variant: "outline",
|
|
2530
|
+
disabled: reviewBusy,
|
|
2531
|
+
onClick: () => {
|
|
2532
|
+
submitReview("reject");
|
|
2533
|
+
},
|
|
2534
|
+
children: t("plazaReject")
|
|
2535
|
+
}),
|
|
2536
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2537
|
+
variant: "primary",
|
|
2538
|
+
disabled: reviewBusy,
|
|
2539
|
+
onClick: () => {
|
|
2540
|
+
submitReview("approve");
|
|
2541
|
+
},
|
|
2542
|
+
children: t("plazaApprove")
|
|
2543
|
+
})
|
|
2544
|
+
]
|
|
2545
|
+
}),
|
|
2546
|
+
children: audit === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2547
|
+
className: SkillPlazaSection_module_css_default.audit,
|
|
2548
|
+
children: [
|
|
2549
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
|
|
2550
|
+
className: SkillPlazaSection_module_css_default.detail,
|
|
2551
|
+
children: [
|
|
2552
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2553
|
+
className: SkillPlazaSection_module_css_default.detailRow,
|
|
2554
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
2555
|
+
className: SkillPlazaSection_module_css_default.detailLabel,
|
|
2556
|
+
children: t("plazaAuditSource")
|
|
2557
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", {
|
|
2558
|
+
className: SkillPlazaSection_module_css_default.detailValue,
|
|
2559
|
+
children: [
|
|
2560
|
+
audit.ownerName ?? "—",
|
|
2561
|
+
" · ",
|
|
2562
|
+
visibilityTag(audit).detail
|
|
2563
|
+
]
|
|
2564
|
+
})]
|
|
2565
|
+
}),
|
|
2566
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2567
|
+
className: SkillPlazaSection_module_css_default.detailRow,
|
|
2568
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
2569
|
+
className: SkillPlazaSection_module_css_default.detailLabel,
|
|
2570
|
+
children: t("plazaAuditVisibility")
|
|
2571
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
2572
|
+
className: SkillPlazaSection_module_css_default.detailValue,
|
|
2573
|
+
children: visibilityTag(audit).label
|
|
2574
|
+
})]
|
|
2575
|
+
}),
|
|
2576
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2577
|
+
className: SkillPlazaSection_module_css_default.detailRow,
|
|
2578
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
2579
|
+
className: SkillPlazaSection_module_css_default.detailLabel,
|
|
2580
|
+
children: t("plazaShowDetail")
|
|
2581
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
2582
|
+
className: SkillPlazaSection_module_css_default.detailValue,
|
|
2583
|
+
children: audit.description ?? "—"
|
|
2584
|
+
})]
|
|
2585
|
+
})
|
|
2586
|
+
]
|
|
2587
|
+
}),
|
|
2588
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RiskReportView, {
|
|
2589
|
+
report: risk,
|
|
2590
|
+
busy: riskBusy,
|
|
2591
|
+
failed: riskFailed,
|
|
2592
|
+
onScan: () => {
|
|
2593
|
+
runScan(audit);
|
|
2594
|
+
},
|
|
2595
|
+
t
|
|
2596
|
+
}),
|
|
2597
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2598
|
+
className: SkillPlazaSection_module_css_default.noteField,
|
|
2599
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("plazaReviewNote") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
2600
|
+
value: reviewNote,
|
|
2601
|
+
placeholder: t("plazaReviewNotePlaceholder"),
|
|
2602
|
+
onChange: (event) => {
|
|
2603
|
+
setReviewNote(event.target.value);
|
|
2604
|
+
}
|
|
2605
|
+
})]
|
|
2606
|
+
}),
|
|
2607
|
+
reviewError === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2608
|
+
className: SkillPlazaSection_module_css_default.reviewError,
|
|
2609
|
+
role: "alert",
|
|
2610
|
+
children: reviewError
|
|
2611
|
+
})
|
|
2612
|
+
]
|
|
2613
|
+
})
|
|
1045
2614
|
})
|
|
1046
2615
|
]
|
|
1047
2616
|
});
|
|
1048
2617
|
}
|
|
1049
2618
|
//#endregion
|
|
1050
|
-
//#region \0dsh-css:/
|
|
1051
|
-
const css = ".
|
|
1052
|
-
const tagId = "@sparkelf/dsh-dataops-managed/WanxiangBrand.module.css";
|
|
1053
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
2619
|
+
//#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/WanxiangBrand.module.css.mjs
|
|
2620
|
+
const css$2 = ".s0HPxG_mark{color:inherit;flex:none;display:block;overflow:visible}.s0HPxG_animatedMark{cursor:pointer}.s0HPxG_mark g{transform-box:fill-box;transform-origin:50%}.s0HPxG_sidebarName{min-width:0;height:24px;color:inherit;align-items:center;gap:6px;display:inline-flex}.s0HPxG_sidebarName>:first-child{flex:0 auto;min-width:0;max-width:102px}.s0HPxG_wordmark{letter-spacing:.01em;white-space:nowrap;text-overflow:ellipsis;font-family:PingFang SC,HarmonyOS Sans SC,Microsoft YaHei,Noto Sans CJK SC,Source Han Sans SC,sans-serif;font-weight:600;line-height:1;display:block;overflow:hidden}.s0HPxG_harness{flex:none;display:block;overflow:visible}@keyframes s0HPxG_unit-top{0%,to{transform:translate(0)}50%{transform:translateY(-2px)}}@keyframes s0HPxG_unit-left{0%,to{transform:translate(0)}50%{transform:translate(-2px,1px)}}@keyframes s0HPxG_unit-right{0%,to{transform:translate(0)}50%{transform:translate(2px,1px)}}@keyframes s0HPxG_unit-core{0%,to{transform:translate(0)}50%{transform:translateY(2px)}}@media (hover:hover) and (prefers-reduced-motion:no-preference){.s0HPxG_animatedMark:hover .s0HPxG_unitTop{animation:1.6s ease-in-out infinite s0HPxG_unit-top}.s0HPxG_animatedMark:hover .s0HPxG_unitLeft{animation:1.6s ease-in-out infinite s0HPxG_unit-left}.s0HPxG_animatedMark:hover .s0HPxG_unitRight{animation:1.6s ease-in-out infinite s0HPxG_unit-right}.s0HPxG_animatedMark:hover .s0HPxG_unitCore{animation:1.6s ease-in-out infinite s0HPxG_unit-core}}@media (prefers-reduced-motion:reduce){.s0HPxG_animatedMark,.s0HPxG_animatedMark g{animation:none!important;transform:none!important}}";
|
|
2621
|
+
const tagId$2 = "@sparkelf/dsh-dataops-managed/WanxiangBrand.module.css";
|
|
2622
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
1054
2623
|
const tag = document.createElement("style");
|
|
1055
2624
|
tag.dataset.plugin = "@sparkelf/dsh-dataops-managed";
|
|
1056
|
-
tag.dataset.pluginCss = tagId;
|
|
1057
|
-
tag.textContent = css;
|
|
2625
|
+
tag.dataset.pluginCss = tagId$2;
|
|
2626
|
+
tag.textContent = css$2;
|
|
1058
2627
|
document.head.appendChild(tag);
|
|
1059
2628
|
}
|
|
1060
2629
|
var WanxiangBrand_module_css_default = {
|
|
1061
|
-
"
|
|
1062
|
-
"
|
|
1063
|
-
"
|
|
1064
|
-
"
|
|
1065
|
-
"unit-
|
|
1066
|
-
"
|
|
1067
|
-
"
|
|
1068
|
-
"unit-
|
|
1069
|
-
"
|
|
1070
|
-
"
|
|
1071
|
-
"
|
|
1072
|
-
"
|
|
2630
|
+
"unitRight": "s0HPxG_unitRight",
|
|
2631
|
+
"unitCore": "s0HPxG_unitCore",
|
|
2632
|
+
"animatedMark": "s0HPxG_animatedMark",
|
|
2633
|
+
"wordmark": "s0HPxG_wordmark",
|
|
2634
|
+
"unit-left": "s0HPxG_unit-left",
|
|
2635
|
+
"unitLeft": "s0HPxG_unitLeft",
|
|
2636
|
+
"unit-top": "s0HPxG_unit-top",
|
|
2637
|
+
"unit-core": "s0HPxG_unit-core",
|
|
2638
|
+
"sidebarName": "s0HPxG_sidebarName",
|
|
2639
|
+
"mark": "s0HPxG_mark",
|
|
2640
|
+
"unitTop": "s0HPxG_unitTop",
|
|
2641
|
+
"harness": "s0HPxG_harness",
|
|
2642
|
+
"unit-right": "s0HPxG_unit-right"
|
|
1073
2643
|
};
|
|
1074
2644
|
//#endregion
|
|
1075
2645
|
//#region src/client/WanxiangBrand.tsx
|
|
1076
|
-
|
|
1077
|
-
const
|
|
1078
|
-
const WORDMARK_PATHS = [
|
|
1079
|
-
{
|
|
1080
|
-
"d": "M62 765V691H333C326 434 312 123 34 -24C53 -38 77 -62 89 -82C287 28 361 217 390 414H767C752 147 735 37 705 9C693 -2 681 -4 657 -3C631 -3 558 -3 483 4C498 -17 508 -48 509 -70C578 -74 648 -75 686 -72C724 -70 749 -62 772 -36C811 5 829 126 846 450C847 460 847 487 847 487H399C406 556 409 625 411 691H939V765Z",
|
|
1081
|
-
"transform": "translate(0.00 940.00) scale(0.96000 -0.94000)"
|
|
1082
|
-
},
|
|
1083
|
-
{
|
|
1084
|
-
"d": "M546 474H850V300H546ZM546 542V710H850V542ZM546 231H850V57H546ZM473 781V-73H546V-12H850V-70H926V781ZM214 840V626H52V554H205C170 416 99 258 29 175C41 157 60 127 68 107C122 176 175 287 214 402V-79H287V378C325 329 370 267 389 234L435 295C413 322 322 429 287 464V554H430V626H287V840Z",
|
|
1085
|
-
"transform": "translate(1002.00 940.00) scale(0.96000 -0.94000)"
|
|
1086
|
-
},
|
|
1087
|
-
{
|
|
1088
|
-
"d": "M443 821C425 782 393 723 368 688L417 664C443 697 477 747 506 793ZM88 793C114 751 141 696 150 661L207 686C198 722 171 776 143 815ZM410 260C387 208 355 164 317 126C279 145 240 164 203 180C217 204 233 231 247 260ZM110 153C159 134 214 109 264 83C200 37 123 5 41 -14C54 -28 70 -54 77 -72C169 -47 254 -8 326 50C359 30 389 11 412 -6L460 43C437 59 408 77 375 95C428 152 470 222 495 309L454 326L442 323H278L300 375L233 387C226 367 216 345 206 323H70V260H175C154 220 131 183 110 153ZM257 841V654H50V592H234C186 527 109 465 39 435C54 421 71 395 80 378C141 411 207 467 257 526V404H327V540C375 505 436 458 461 435L503 489C479 506 391 562 342 592H531V654H327V841ZM629 832C604 656 559 488 481 383C497 373 526 349 538 337C564 374 586 418 606 467C628 369 657 278 694 199C638 104 560 31 451 -22C465 -37 486 -67 493 -83C595 -28 672 41 731 129C781 44 843 -24 921 -71C933 -52 955 -26 972 -12C888 33 822 106 771 198C824 301 858 426 880 576H948V646H663C677 702 689 761 698 821ZM809 576C793 461 769 361 733 276C695 366 667 468 648 576Z",
|
|
1089
|
-
"transform": "translate(2004.00 940.00) scale(0.96000 -0.94000)"
|
|
1090
|
-
},
|
|
1091
|
-
{
|
|
1092
|
-
"d": "M484 238V-81H550V-40H858V-77H927V238H734V362H958V427H734V537H923V796H395V494C395 335 386 117 282 -37C299 -45 330 -67 344 -79C427 43 455 213 464 362H663V238ZM468 731H851V603H468ZM468 537H663V427H467L468 494ZM550 22V174H858V22ZM167 839V638H42V568H167V349C115 333 67 319 29 309L49 235L167 273V14C167 0 162 -4 150 -4C138 -5 99 -5 56 -4C65 -24 75 -55 77 -73C140 -74 179 -71 203 -59C228 -48 237 -27 237 14V296L352 334L341 403L237 370V568H350V638H237V839Z",
|
|
1093
|
-
"transform": "translate(3006.00 940.00) scale(0.96000 -0.94000)"
|
|
1094
|
-
},
|
|
1095
|
-
{
|
|
1096
|
-
"d": "M174 630C213 556 252 459 266 399L337 424C323 482 282 578 242 650ZM755 655C730 582 684 480 646 417L711 396C750 456 797 552 834 633ZM52 348V273H459V-79H537V273H949V348H537V698H893V773H105V698H459V348Z",
|
|
1097
|
-
"transform": "translate(4008.00 940.00) scale(0.96000 -0.94000)"
|
|
1098
|
-
},
|
|
1099
|
-
{
|
|
1100
|
-
"d": "M179 342V-79H255V-25H741V-77H821V342ZM255 48V270H741V48ZM126 426C165 441 224 443 800 474C825 443 846 414 861 388L925 434C873 518 756 641 658 727L599 687C647 644 699 591 745 540L231 516C320 598 410 701 490 811L415 844C336 720 219 593 183 559C149 526 124 505 101 500C110 480 122 442 126 426Z",
|
|
1101
|
-
"transform": "translate(5010.00 940.00) scale(0.96000 -0.94000)"
|
|
1102
|
-
}
|
|
1103
|
-
];
|
|
2646
|
+
/** User-visible platform name shared by the header wordmark and its accessible name. */
|
|
2647
|
+
const BRAND_NAME = "万相AI中台";
|
|
1104
2648
|
const HARNESS_PATHS = [
|
|
1105
2649
|
{
|
|
1106
2650
|
"d": "M132.848 8.93205H134.08V16.137H132.848V8.93205ZM136.5 8.93205H137.732V16.137H136.5V8.93205ZM133.365 13.024V11.99H137.193V13.024H133.365Z",
|
|
@@ -1189,30 +2733,22 @@ window.__ModuleLoader__.load({
|
|
|
1189
2733
|
]
|
|
1190
2734
|
});
|
|
1191
2735
|
}
|
|
1192
|
-
/**
|
|
2736
|
+
/**
|
|
2737
|
+
* Platform wordmark rendered as text.
|
|
2738
|
+
*
|
|
2739
|
+
* It used to be glyph artwork for the previous platform name, which cannot express the new
|
|
2740
|
+
* name without redrawing every path. Text keeps the visible header, the accessible name, and
|
|
2741
|
+
* the browser title on one string, and inherits the host's CJK font stack through `font-family`.
|
|
2742
|
+
*/
|
|
1193
2743
|
function WanxiangWordmark({ height, accessible = false, className }) {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
viewBox: WORDMARK_VIEWBOX,
|
|
1203
|
-
...accessibility,
|
|
1204
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
|
|
1205
|
-
fill: "currentColor",
|
|
1206
|
-
stroke: "currentColor",
|
|
1207
|
-
strokeWidth: "42",
|
|
1208
|
-
strokeLinejoin: "round",
|
|
1209
|
-
strokeLinecap: "round",
|
|
1210
|
-
paintOrder: "stroke fill",
|
|
1211
|
-
children: WORDMARK_PATHS.map((path) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1212
|
-
d: path.d,
|
|
1213
|
-
transform: path.transform
|
|
1214
|
-
}, path.transform))
|
|
1215
|
-
})
|
|
2744
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2745
|
+
className: classes(WanxiangBrand_module_css_default.wordmark, className),
|
|
2746
|
+
style: { fontSize: height },
|
|
2747
|
+
...accessible ? {
|
|
2748
|
+
role: "img",
|
|
2749
|
+
"aria-label": BRAND_NAME
|
|
2750
|
+
} : {},
|
|
2751
|
+
children: BRAND_NAME
|
|
1216
2752
|
});
|
|
1217
2753
|
}
|
|
1218
2754
|
/** Exact HARNESS badge artwork extracted from the current DSH BrandWordmark. */
|
|
@@ -1254,7 +2790,7 @@ window.__ModuleLoader__.load({
|
|
|
1254
2790
|
}
|
|
1255
2791
|
//#endregion
|
|
1256
2792
|
//#region src/client/install-branding.tsx
|
|
1257
|
-
const BRAND_TITLE = "
|
|
2793
|
+
const BRAND_TITLE = "万相AI中台 Harness";
|
|
1258
2794
|
const SLOT_PRIORITY = -100;
|
|
1259
2795
|
const FAVICON_ID = "dataops-wanxiang-favicon";
|
|
1260
2796
|
const FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 58"><style>:root{color:#1b1b1d}@media(prefers-color-scheme:dark){:root{color:#f7f7f8}}</style><g transform="translate(15 0)" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M12 0 24 7 12 14 0 7Z" fill="none"/><path d="M0 7 12 14v16L0 23Z" fill="currentColor"/><path d="M12 14 24 7v16L12 30Z" fill="currentColor" fill-opacity=".18"/></g><g transform="translate(0 16)" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M12 0 24 7 12 14 0 7Z" fill="none"/><path d="M0 7 12 14v16L0 23Z" fill="currentColor"/><path d="M12 14 24 7v16L12 30Z" fill="currentColor" fill-opacity=".18"/></g><g transform="translate(30 16)" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M12 0 24 7 12 14 0 7Z" fill="none"/><path d="M0 7 12 14v16L0 23Z" fill="currentColor"/><path d="M12 14 24 7v16L12 30Z" fill="currentColor" fill-opacity=".18"/></g><g transform="translate(15 27)" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M12 0 24 7 12 14 0 7Z" fill="none"/><path d="M0 7 12 14v16L0 23Z" fill="currentColor"/><path d="M12 14 24 7v16L12 30Z" fill="currentColor" fill-opacity=".18"/></g></svg>`;
|
|
@@ -1340,50 +2876,2004 @@ window.__ModuleLoader__.load({
|
|
|
1340
2876
|
}, "dataops-managed: Wanxiang shell branding");
|
|
1341
2877
|
}
|
|
1342
2878
|
//#endregion
|
|
1343
|
-
//#region src/client/
|
|
1344
|
-
/**
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
plazaActionFailed: "The action could not be completed. Try again.",
|
|
1383
|
-
plazaRetry: "Try again"
|
|
2879
|
+
//#region src/client/ReloginPrompt.tsx
|
|
2880
|
+
/**
|
|
2881
|
+
* The "sign in again" prompt shown when this workspace's DataOps session has ended.
|
|
2882
|
+
*
|
|
2883
|
+
* The failure it reports used to be invisible. Every DataOps call handles a 401 by degrading to
|
|
2884
|
+
* an empty result, which is right for one panel but wrong for the operator: settings, shared
|
|
2885
|
+
* models and the skill plaza all render as "nothing here" while the real cause is that nobody is
|
|
2886
|
+
* signed in any more. This prompt names that state once, with the one action that clears it.
|
|
2887
|
+
*
|
|
2888
|
+
* It reports and nothing more. It does not retry, expire, or re-authenticate — the portal owns
|
|
2889
|
+
* the login form and is the only thing that can issue a session. It also cannot navigate the
|
|
2890
|
+
* operator there: the portal is a different origin, so this frame cannot read or drive its
|
|
2891
|
+
* window, and the gateway does not forward the browser's origin to the workspace. Guessing a
|
|
2892
|
+
* URL from inside here would open the wrong page on some deployments, so the dialog tells the
|
|
2893
|
+
* operator which window to switch to instead of pretending it can take them.
|
|
2894
|
+
*
|
|
2895
|
+
* @module ReloginPrompt
|
|
2896
|
+
*/
|
|
2897
|
+
/** How often to re-read the observed auth state. */
|
|
2898
|
+
const POLL_MS$1 = 15e3;
|
|
2899
|
+
/**
|
|
2900
|
+
* Read the observed credential state from the Host.
|
|
2901
|
+
*
|
|
2902
|
+
* A failed read says nothing about the credential: this route is served by the same process that
|
|
2903
|
+
* serves the page, so an unreachable route means the Web server is busy or restarting, not that
|
|
2904
|
+
* anyone was signed out. Returning `undefined` keeps a transient error from raising a sign-in
|
|
2905
|
+
* prompt, which would be a lie the operator cannot act on.
|
|
2906
|
+
*
|
|
2907
|
+
* @returns the reported state, or `undefined` when it could not be read.
|
|
2908
|
+
*/
|
|
2909
|
+
const readAuthState = async () => {
|
|
2910
|
+
try {
|
|
2911
|
+
const response = await fetch(new URL("/integrations/dataops/auth-state", window.location.origin), { credentials: "same-origin" });
|
|
2912
|
+
if (!response.ok) return void 0;
|
|
2913
|
+
const state = (await response.json()).state;
|
|
2914
|
+
return state === "expired" || state === "valid" || state === "unknown" ? state : void 0;
|
|
2915
|
+
} catch {
|
|
2916
|
+
return;
|
|
2917
|
+
}
|
|
1384
2918
|
};
|
|
1385
|
-
/**
|
|
1386
|
-
|
|
2919
|
+
/**
|
|
2920
|
+
* Watch for an ended DataOps session and say so.
|
|
2921
|
+
*
|
|
2922
|
+
* @param props.t - localized copy resolver.
|
|
2923
|
+
* @returns the dialog while the session is known to have ended, otherwise `null`.
|
|
2924
|
+
*/
|
|
2925
|
+
function ReloginPrompt(props) {
|
|
2926
|
+
const { t } = props;
|
|
2927
|
+
const [expired, setExpired] = (0, react.useState)(false);
|
|
2928
|
+
const [dismissed, setDismissed] = (0, react.useState)(false);
|
|
2929
|
+
(0, react.useEffect)(() => {
|
|
2930
|
+
let cancelled = false;
|
|
2931
|
+
const tick = async () => {
|
|
2932
|
+
const state = await readAuthState();
|
|
2933
|
+
if (cancelled || state === void 0) return;
|
|
2934
|
+
setExpired(state === "expired");
|
|
2935
|
+
if (state !== "expired") setDismissed(false);
|
|
2936
|
+
};
|
|
2937
|
+
tick();
|
|
2938
|
+
const timer = setInterval(() => {
|
|
2939
|
+
tick();
|
|
2940
|
+
}, POLL_MS$1);
|
|
2941
|
+
return () => {
|
|
2942
|
+
cancelled = true;
|
|
2943
|
+
clearInterval(timer);
|
|
2944
|
+
};
|
|
2945
|
+
}, []);
|
|
2946
|
+
const dismiss = (0, react.useCallback)(() => {
|
|
2947
|
+
setDismissed(true);
|
|
2948
|
+
}, []);
|
|
2949
|
+
if (!expired || dismissed) return null;
|
|
2950
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
2951
|
+
open: true,
|
|
2952
|
+
onClose: dismiss,
|
|
2953
|
+
title: t("reloginTitle"),
|
|
2954
|
+
closeLabel: t("reloginDismiss"),
|
|
2955
|
+
description: t("reloginBody"),
|
|
2956
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2957
|
+
onClick: dismiss,
|
|
2958
|
+
children: t("reloginDismiss")
|
|
2959
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2960
|
+
variant: "primary",
|
|
2961
|
+
onClick: () => {
|
|
2962
|
+
window.location.reload();
|
|
2963
|
+
},
|
|
2964
|
+
children: t("reloginAction")
|
|
2965
|
+
})] })
|
|
2966
|
+
});
|
|
2967
|
+
}
|
|
2968
|
+
//#endregion
|
|
2969
|
+
//#region src/client/settings-gate.ts
|
|
2970
|
+
/**
|
|
2971
|
+
* Whether the managed DataOps settings section may be registered at all.
|
|
2972
|
+
*
|
|
2973
|
+
* Registration is the gate, not a CSS class: an ordinary user's profile should not contain a
|
|
2974
|
+
* section whose controls they may not use. The identity record is presentation-only and arrives
|
|
2975
|
+
* from the portal after the iframe loads, so the registration follows it instead of reading it
|
|
2976
|
+
* once at startup. Authorization still lives in DataOps; this only keeps a forbidden panel out of
|
|
2977
|
+
* the interface.
|
|
2978
|
+
*/
|
|
2979
|
+
/** The two DataOps role labels allowed to see the managed settings panel. */
|
|
2980
|
+
const MANAGED_SETTINGS_ROLES = /* @__PURE__ */ new Set(["超级管理员", "管理员"]);
|
|
2981
|
+
/** The account launcher's visible label, which is painted before the window record settles. */
|
|
2982
|
+
function domAccountName() {
|
|
2983
|
+
if (typeof document === "undefined") return null;
|
|
2984
|
+
return [...document.querySelectorAll("[data-dataops-account-name]")].map((element) => element.textContent?.trim() ?? "").filter((text) => text !== "" && text.length <= 30).sort((left, right) => left.length - right.length)[0] ?? null;
|
|
2985
|
+
}
|
|
2986
|
+
/** Read the current role label without trusting any other field. */
|
|
2987
|
+
function currentManagedSettingsRole() {
|
|
2988
|
+
if (typeof window === "undefined") return null;
|
|
2989
|
+
const domName = domAccountName();
|
|
2990
|
+
if (domName !== null) return domName;
|
|
2991
|
+
const name = window.__DATAOPS_USER__?.name;
|
|
2992
|
+
return typeof name === "string" && name.trim() !== "" ? name.trim() : null;
|
|
2993
|
+
}
|
|
2994
|
+
/** Whether the current identity may see the managed DataOps settings section. */
|
|
2995
|
+
function canSeeManagedSettings() {
|
|
2996
|
+
const role = currentManagedSettingsRole();
|
|
2997
|
+
return role !== null && MANAGED_SETTINGS_ROLES.has(role);
|
|
2998
|
+
}
|
|
2999
|
+
/**
|
|
3000
|
+
* Observe identity changes.
|
|
3001
|
+
*
|
|
3002
|
+
* The account plugin keeps one store on `window`; subscribing to its listener set catches the
|
|
3003
|
+
* in-page update even before the portal's DOM event. The event is kept as a second signal because
|
|
3004
|
+
* a future portal may publish the event without touching the store this plugin reads.
|
|
3005
|
+
*/
|
|
3006
|
+
function subscribeManagedSettingsVisibility(listener) {
|
|
3007
|
+
if (typeof window === "undefined") return () => {};
|
|
3008
|
+
const store = window.__DATAOPS_ACCOUNT_STORE__;
|
|
3009
|
+
store?.listeners?.add(listener);
|
|
3010
|
+
window.addEventListener("dataops-account:changed", listener);
|
|
3011
|
+
/**
|
|
3012
|
+
* The first identity can arrive before this module subscribes (or replace the account store
|
|
3013
|
+
* object), which is why the sidebar used to show only the sections whose gate happened to run
|
|
3014
|
+
* later. The launcher's label is a DOM fact either way, so watch it as the fallback signal.
|
|
3015
|
+
*/
|
|
3016
|
+
let observed = domAccountName();
|
|
3017
|
+
const observer = typeof MutationObserver === "undefined" || typeof document === "undefined" ? void 0 : new MutationObserver(() => {
|
|
3018
|
+
const next = domAccountName();
|
|
3019
|
+
if (next === observed) return;
|
|
3020
|
+
observed = next;
|
|
3021
|
+
listener();
|
|
3022
|
+
});
|
|
3023
|
+
observer?.observe(document.documentElement, {
|
|
3024
|
+
subtree: true,
|
|
3025
|
+
childList: true,
|
|
3026
|
+
characterData: true,
|
|
3027
|
+
attributes: true,
|
|
3028
|
+
attributeFilter: ["data-dataops-account-name"]
|
|
3029
|
+
});
|
|
3030
|
+
if (observed !== null) queueMicrotask(listener);
|
|
3031
|
+
return () => {
|
|
3032
|
+
store?.listeners?.delete(listener);
|
|
3033
|
+
window.removeEventListener("dataops-account:changed", listener);
|
|
3034
|
+
observer?.disconnect();
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
//#endregion
|
|
3038
|
+
//#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/PermissionCenterSection.module.css.mjs
|
|
3039
|
+
const css$1 = ".PNbOqW_page{box-sizing:border-box;flex-direction:column;gap:14px;width:100%;max-width:clamp(1000px,60vw,1200px);min-height:0;margin:0 auto;padding:24px;display:flex}.PNbOqW_head{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.PNbOqW_title{color:var(--dsw-alias-label-primary);margin:0;font-size:18px;font-weight:600;line-height:26px}.PNbOqW_subtitle{color:var(--dsw-alias-label-tertiary);align-items:center;gap:6px;margin:3px 0 0;font-size:12px;display:flex}.PNbOqW_demo{color:#9a5b00;background:#fff3e0;border-radius:4px;padding:1px 6px;font-size:10px;line-height:16px}.PNbOqW_tabs{border-bottom:1px solid var(--dsw-alias-border-l1);gap:2px;display:flex}.PNbOqW_tab{height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-bottom:2px solid #0000;padding:0 12px;font-size:13px}.PNbOqW_tabActive{color:var(--dsw-alias-state-business-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:500}.PNbOqW_notice,.PNbOqW_waiting{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);border-radius:5px;margin:0;padding:8px 10px;font-size:12px}.PNbOqW_waiting{border:1px dashed var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}.PNbOqW_panel{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:8px;flex-direction:column;gap:10px;min-height:0;padding:14px;display:flex}.PNbOqW_sectionTitle{margin:0;font-size:13px;font-weight:600}.PNbOqW_matrixScroll,.PNbOqW_tableScroll{overscroll-behavior:contain;scrollbar-gutter:stable;max-height:clamp(280px,100dvh - 340px,520px);overflow:auto}.PNbOqW_matrix,.PNbOqW_table{border-collapse:collapse;width:100%;font-size:12px}.PNbOqW_matrix th,.PNbOqW_matrix td,.PNbOqW_table th,.PNbOqW_table td{border-bottom:1px solid var(--dsw-alias-border-l1);text-align:left;vertical-align:top;padding:7px 9px}.PNbOqW_matrix th,.PNbOqW_table th{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);white-space:nowrap;font-weight:600}.PNbOqW_permission{white-space:nowrap;color:var(--dsw-alias-label-secondary);align-items:center;gap:5px;font-size:11px;line-height:20px;display:flex}.PNbOqW_panelFoot{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:6px;display:flex}.PNbOqW_readOnly{color:var(--dsw-alias-label-tertiary);margin-right:auto;font-size:11px}.PNbOqW_muted{color:var(--dsw-alias-label-tertiary);font-size:11px}.PNbOqW_departments{flex-direction:column;gap:4px;margin:0;padding:0;list-style:none;display:flex}.PNbOqW_department{border-bottom:1px solid var(--dsw-alias-border-l1);justify-content:space-between;align-items:center;gap:12px;padding:9px 8px;font-size:12px;display:flex}.PNbOqW_deptAdmins{align-items:center;gap:5px;display:flex}.PNbOqW_adminTag{color:var(--dsw-alias-state-business-primary);background:#ecf1fc;border-radius:4px;padding:1px 6px;font-size:11px}.PNbOqW_empty{text-align:center;color:var(--dsw-alias-label-tertiary);margin:0;padding:40px 0}.PNbOqW_matrixScroll{overscroll-behavior:contain;scrollbar-gutter:stable;max-height:clamp(280px,100dvh - 340px,520px);overflow:auto}.PNbOqW_matrix thead th{z-index:2;position:sticky;top:0}.PNbOqW_matrix thead th:first-child,.PNbOqW_matrix tbody th{background:var(--dsw-alias-bg-layer-2);min-width:132px;position:sticky;left:0}.PNbOqW_matrix thead th:first-child{z-index:3}.PNbOqW_matrix tbody th{z-index:1;background:var(--dsw-alias-bg-layer-1)}.PNbOqW_protected{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);white-space:nowrap;border-radius:3px;margin-left:5px;padding:0 5px;font-size:10px;font-weight:500;line-height:16px;display:inline-block}tr[data-locked=true] td{opacity:.62}.PNbOqW_pager{justify-content:flex-end;align-items:center;gap:8px;padding-top:8px;display:flex}.PNbOqW_dialog{width:min(560px,100vw - 32px)}.PNbOqW_dialogBody{flex-direction:column;gap:10px;display:flex}.PNbOqW_dialogHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}.PNbOqW_search{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);width:100%;height:30px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-xs-13);border-radius:5px;padding:0 9px}.PNbOqW_candidates{flex-direction:column;gap:2px;max-height:260px;margin:0;padding:0;list-style:none;display:flex;overflow:auto}.PNbOqW_candidate{cursor:pointer;border-radius:5px;align-items:center;gap:7px;padding:6px 7px;font-size:12px;display:flex}.PNbOqW_candidate:hover{background:var(--dsw-alias-interactive-bg-hover)}.PNbOqW_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:11px;line-height:16px}.PNbOqW_dialogFooter{justify-content:flex-end;gap:6px;display:flex}.PNbOqW_matrixNote{color:var(--dsw-alias-label-tertiary);margin:6px 0 0;font-size:11px;line-height:16px}.PNbOqW_panelHead{justify-content:space-between;align-items:center;gap:10px;display:flex}.PNbOqW_treeRoot,.PNbOqW_treeChildren{margin:0;padding:0;list-style:none}.PNbOqW_treeChildren{border-left:1px solid var(--dsw-alias-border-l1);margin-left:18px}.PNbOqW_treeRow{border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:7px;min-height:34px;font-size:12px;display:flex}.PNbOqW_treeToggle,.PNbOqW_treeSpacer{flex:none;justify-content:center;align-items:center;width:18px;height:18px;display:inline-flex}.PNbOqW_treeToggle{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:3px;font-size:11px}.PNbOqW_treeToggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.PNbOqW_treeName{color:var(--dsw-alias-label-primary);white-space:nowrap;font-weight:600}.PNbOqW_treeActions{align-items:center;gap:2px;margin-left:auto;display:flex}.PNbOqW_field{color:var(--dsw-alias-label-secondary);flex-direction:column;gap:5px;font-size:12px;display:flex}.PNbOqW_field input,.PNbOqW_field select{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);width:100%;height:30px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-xs-13);border-radius:5px;padding:0 9px}.PNbOqW_field input:disabled{opacity:.65}.PNbOqW_matrixModule{vertical-align:top;background:var(--dsw-alias-bg-layer-2);min-width:96px;color:var(--dsw-alias-label-secondary);text-align:left;border-right:1px solid var(--dsw-alias-border-l1);padding:10px 9px;font-size:12px;font-weight:600}.PNbOqW_matrixGroup th{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);text-align:left;padding:7px 9px;font-size:12px;font-weight:600}.PNbOqW_matrixPermission{white-space:nowrap;min-width:230px}.PNbOqW_scopeBadge{border-radius:3px;margin-left:6px;padding:0 5px;font-size:10px;font-weight:500;line-height:16px;display:inline-block}.PNbOqW_scopeBadge[data-scope=department]{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-state-business-primary)}.PNbOqW_scopeBadge[data-scope=global]{background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 16%, var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-state-warn-primary)}.PNbOqW_onlyAdmin{color:var(--dsw-alias-label-tertiary);white-space:nowrap;margin-left:4px;font-size:10px}.PNbOqW_permCopy{max-width:100%;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;gap:2px;padding:0;display:inline-flex}.PNbOqW_permCopy:hover span:first-child{color:var(--dsw-alias-state-business-primary);text-decoration:underline dotted}.PNbOqW_permDescription{color:var(--dsw-alias-label-tertiary);white-space:normal;margin-top:2px;font-size:10px;font-weight:400;line-height:15px;display:block}.PNbOqW_copied{color:var(--dsw-alias-state-success-primary);margin-left:4px;font-size:10px}.PNbOqW_combobox{min-width:132px;position:relative}.PNbOqW_comboboxTrigger{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);width:100%;height:28px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-xs-13);text-align:left;cursor:pointer;border-radius:5px;justify-content:space-between;align-items:center;gap:8px;padding:0 8px;display:flex}.PNbOqW_comboboxTrigger:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}.PNbOqW_comboboxTrigger:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.PNbOqW_comboboxTrigger:disabled{opacity:.55;cursor:default}.PNbOqW_comboboxPlaceholder{color:var(--dsw-alias-label-tertiary)}.PNbOqW_comboboxMenu{z-index:30;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:7px;width:max(100%,220px);padding:5px;position:absolute;top:calc(100% + 4px);left:0;box-shadow:0 8px 24px #00000038}.PNbOqW_comboboxSearch{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:100%;height:28px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-xs-13);border-radius:4px;padding:0 7px}.PNbOqW_comboboxList{max-height:210px;margin:4px 0 0;padding:0;list-style:none;overflow:auto}.PNbOqW_comboboxOption{width:100%;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:4px;align-items:center;gap:5px;padding:6px;font-size:12px;line-height:18px;display:flex}.PNbOqW_comboboxOptionActive,.PNbOqW_comboboxOption:hover{background:var(--dsw-alias-interactive-bg-hover)}.PNbOqW_comboboxCheck{width:13px;color:var(--dsw-alias-state-business-primary);flex:none;font-size:11px}.PNbOqW_comboboxHint{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:10px}.PNbOqW_comboboxEmpty{color:var(--dsw-alias-label-tertiary);padding:9px 7px;font-size:11px}.PNbOqW_rowActions{white-space:nowrap;align-items:center;gap:4px;display:flex}.PNbOqW_detailGrid{grid-template-columns:92px minmax(0,1fr);gap:8px 12px;margin:0;font-size:12px;line-height:18px;display:grid}.PNbOqW_detailGrid dt{color:var(--dsw-alias-label-tertiary)}.PNbOqW_detailGrid dd{color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;margin:0}";
|
|
3040
|
+
const tagId$1 = "@sparkelf/dsh-dataops-managed/PermissionCenterSection.module.css";
|
|
3041
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
3042
|
+
const tag = document.createElement("style");
|
|
3043
|
+
tag.dataset.plugin = "@sparkelf/dsh-dataops-managed";
|
|
3044
|
+
tag.dataset.pluginCss = tagId$1;
|
|
3045
|
+
tag.textContent = css$1;
|
|
3046
|
+
document.head.appendChild(tag);
|
|
3047
|
+
}
|
|
3048
|
+
var PermissionCenterSection_module_css_default = {
|
|
3049
|
+
"panelFoot": "PNbOqW_panelFoot",
|
|
3050
|
+
"comboboxHint": "PNbOqW_comboboxHint",
|
|
3051
|
+
"matrixGroup": "PNbOqW_matrixGroup",
|
|
3052
|
+
"permDescription": "PNbOqW_permDescription",
|
|
3053
|
+
"protected": "PNbOqW_protected",
|
|
3054
|
+
"matrixNote": "PNbOqW_matrixNote",
|
|
3055
|
+
"dialogHint": "PNbOqW_dialogHint",
|
|
3056
|
+
"pager": "PNbOqW_pager",
|
|
3057
|
+
"tabs": "PNbOqW_tabs",
|
|
3058
|
+
"permCopy": "PNbOqW_permCopy",
|
|
3059
|
+
"comboboxSearch": "PNbOqW_comboboxSearch",
|
|
3060
|
+
"error": "PNbOqW_error",
|
|
3061
|
+
"dialog": "PNbOqW_dialog",
|
|
3062
|
+
"treeSpacer": "PNbOqW_treeSpacer",
|
|
3063
|
+
"matrixPermission": "PNbOqW_matrixPermission",
|
|
3064
|
+
"treeActions": "PNbOqW_treeActions",
|
|
3065
|
+
"panel": "PNbOqW_panel",
|
|
3066
|
+
"tab": "PNbOqW_tab",
|
|
3067
|
+
"adminTag": "PNbOqW_adminTag",
|
|
3068
|
+
"candidates": "PNbOqW_candidates",
|
|
3069
|
+
"department": "PNbOqW_department",
|
|
3070
|
+
"copied": "PNbOqW_copied",
|
|
3071
|
+
"tabActive": "PNbOqW_tabActive",
|
|
3072
|
+
"treeRoot": "PNbOqW_treeRoot",
|
|
3073
|
+
"comboboxOption": "PNbOqW_comboboxOption",
|
|
3074
|
+
"table": "PNbOqW_table",
|
|
3075
|
+
"onlyAdmin": "PNbOqW_onlyAdmin",
|
|
3076
|
+
"permission": "PNbOqW_permission",
|
|
3077
|
+
"combobox": "PNbOqW_combobox",
|
|
3078
|
+
"treeToggle": "PNbOqW_treeToggle",
|
|
3079
|
+
"dialogFooter": "PNbOqW_dialogFooter",
|
|
3080
|
+
"candidate": "PNbOqW_candidate",
|
|
3081
|
+
"empty": "PNbOqW_empty",
|
|
3082
|
+
"comboboxList": "PNbOqW_comboboxList",
|
|
3083
|
+
"sectionTitle": "PNbOqW_sectionTitle",
|
|
3084
|
+
"readOnly": "PNbOqW_readOnly",
|
|
3085
|
+
"treeRow": "PNbOqW_treeRow",
|
|
3086
|
+
"detailGrid": "PNbOqW_detailGrid",
|
|
3087
|
+
"matrix": "PNbOqW_matrix",
|
|
3088
|
+
"field": "PNbOqW_field",
|
|
3089
|
+
"subtitle": "PNbOqW_subtitle",
|
|
3090
|
+
"demo": "PNbOqW_demo",
|
|
3091
|
+
"title": "PNbOqW_title",
|
|
3092
|
+
"treeChildren": "PNbOqW_treeChildren",
|
|
3093
|
+
"matrixModule": "PNbOqW_matrixModule",
|
|
3094
|
+
"comboboxPlaceholder": "PNbOqW_comboboxPlaceholder",
|
|
3095
|
+
"comboboxMenu": "PNbOqW_comboboxMenu",
|
|
3096
|
+
"comboboxCheck": "PNbOqW_comboboxCheck",
|
|
3097
|
+
"scopeBadge": "PNbOqW_scopeBadge",
|
|
3098
|
+
"comboboxEmpty": "PNbOqW_comboboxEmpty",
|
|
3099
|
+
"treeName": "PNbOqW_treeName",
|
|
3100
|
+
"panelHead": "PNbOqW_panelHead",
|
|
3101
|
+
"rowActions": "PNbOqW_rowActions",
|
|
3102
|
+
"deptAdmins": "PNbOqW_deptAdmins",
|
|
3103
|
+
"comboboxTrigger": "PNbOqW_comboboxTrigger",
|
|
3104
|
+
"waiting": "PNbOqW_waiting",
|
|
3105
|
+
"tableScroll": "PNbOqW_tableScroll",
|
|
3106
|
+
"muted": "PNbOqW_muted",
|
|
3107
|
+
"search": "PNbOqW_search",
|
|
3108
|
+
"page": "PNbOqW_page",
|
|
3109
|
+
"departments": "PNbOqW_departments",
|
|
3110
|
+
"notice": "PNbOqW_notice",
|
|
3111
|
+
"dialogBody": "PNbOqW_dialogBody",
|
|
3112
|
+
"head": "PNbOqW_head",
|
|
3113
|
+
"matrixScroll": "PNbOqW_matrixScroll",
|
|
3114
|
+
"comboboxOptionActive": "PNbOqW_comboboxOptionActive"
|
|
3115
|
+
};
|
|
3116
|
+
//#endregion
|
|
3117
|
+
//#region src/client/combobox.ts
|
|
3118
|
+
/**
|
|
3119
|
+
* Filter options by display label or code hint, case-insensitively.
|
|
3120
|
+
* @param options - all options.
|
|
3121
|
+
* @param query - typed search text.
|
|
3122
|
+
*/
|
|
3123
|
+
function filterComboboxOptions(options, query) {
|
|
3124
|
+
const needle = query.trim().toLowerCase();
|
|
3125
|
+
if (needle === "") return [...options];
|
|
3126
|
+
return options.filter((option) => option.label.toLowerCase().includes(needle) || (option.hint ?? "").toLowerCase().includes(needle));
|
|
3127
|
+
}
|
|
3128
|
+
/** Keep an active index inside a possibly empty result list. */
|
|
3129
|
+
function clampActiveIndex(index, length) {
|
|
3130
|
+
if (length <= 0) return 0;
|
|
3131
|
+
return Math.max(0, Math.min(length - 1, index));
|
|
3132
|
+
}
|
|
3133
|
+
/** Owner cell face: a real display name, or a dash; never the raw id. */
|
|
3134
|
+
function ownerDisplay(ownerName) {
|
|
3135
|
+
const name = ownerName?.trim();
|
|
3136
|
+
return name === void 0 || name === "" ? "—" : name;
|
|
3137
|
+
}
|
|
3138
|
+
//#endregion
|
|
3139
|
+
//#region src/client/PermissionCenterSection.tsx
|
|
3140
|
+
init_review_demo();
|
|
3141
|
+
/** How many rows one page of the members/resources tables shows. */
|
|
3142
|
+
const PAGE_SIZE = 10;
|
|
3143
|
+
/**
|
|
3144
|
+
* The permission centre.
|
|
3145
|
+
*
|
|
3146
|
+
* Every write goes through `saveRbacSection`; the backend enforces the same role/scope rules
|
|
3147
|
+
* the disabled controls represent. The page keeps target-aware state explicitly (actor role,
|
|
3148
|
+
* actor department) so an ADMIN cannot edit a SUPER_ADMIN row and a DEPT_ADMIN cannot reach
|
|
3149
|
+
* another department, even if a list payload is wider than expected.
|
|
3150
|
+
*/
|
|
3151
|
+
function PermissionCenterSection({ t }) {
|
|
3152
|
+
const [context, setContext] = (0, react.useState)(void 0);
|
|
3153
|
+
const [overview, setOverview] = (0, react.useState)(void 0);
|
|
3154
|
+
const [tab, setTab] = (0, react.useState)("roles");
|
|
3155
|
+
const [notice, setNotice] = (0, react.useState)(null);
|
|
3156
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
3157
|
+
const [deptDialog, setDeptDialog] = (0, react.useState)(null);
|
|
3158
|
+
const [deptSearch, setDeptSearch] = (0, react.useState)("");
|
|
3159
|
+
const [deptSelected, setDeptSelected] = (0, react.useState)([]);
|
|
3160
|
+
const [deptSaving, setDeptSaving] = (0, react.useState)(false);
|
|
3161
|
+
const [deptError, setDeptError] = (0, react.useState)(null);
|
|
3162
|
+
const [deptForm, setDeptForm] = (0, react.useState)(null);
|
|
3163
|
+
const demo = reviewDemoMode();
|
|
3164
|
+
const load = (0, react.useCallback)(async () => {
|
|
3165
|
+
const nextContext = await fetchReviewContext();
|
|
3166
|
+
setContext(nextContext);
|
|
3167
|
+
if (nextContext === null && !demo) {
|
|
3168
|
+
setOverview(null);
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
try {
|
|
3172
|
+
setOverview(demo ? demoRbacOverview() : await fetchRbacOverview());
|
|
3173
|
+
} catch {
|
|
3174
|
+
setOverview(null);
|
|
3175
|
+
}
|
|
3176
|
+
}, [demo]);
|
|
3177
|
+
(0, react.useEffect)(() => {
|
|
3178
|
+
load();
|
|
3179
|
+
}, [load]);
|
|
3180
|
+
if (t === void 0) return null;
|
|
3181
|
+
if (context === void 0 || overview === void 0) return null;
|
|
3182
|
+
const knownAdmin = canSeeManagedSettings();
|
|
3183
|
+
if (context !== null ? context.role !== "SUPER_ADMIN" && context.role !== "ADMIN" && context.role !== "DEPT_ADMIN" && !knownAdmin : !knownAdmin) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
3184
|
+
className: PermissionCenterSection_module_css_default.page,
|
|
3185
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3186
|
+
className: PermissionCenterSection_module_css_default.empty,
|
|
3187
|
+
children: t("permNoAccess")
|
|
3188
|
+
})
|
|
3189
|
+
});
|
|
3190
|
+
const roleName = context === null ? currentManagedSettingsRole() ?? "—" : roleLabel(context.role);
|
|
3191
|
+
const departmentName = context?.departmentPath?.join(" / ") ?? "—";
|
|
3192
|
+
const actorRole = context?.role ?? (knownAdmin ? "ADMIN" : "APP_USER");
|
|
3193
|
+
const writable = context === null ? knownAdmin : canManagePermissions(context) || demo;
|
|
3194
|
+
const superAdmin = context === null ? knownAdmin : context.role === "SUPER_ADMIN" || demo;
|
|
3195
|
+
const canAssignDepartment = writable && (superAdmin || actorRole === "ADMIN");
|
|
3196
|
+
const labels = [
|
|
3197
|
+
{
|
|
3198
|
+
key: "roles",
|
|
3199
|
+
label: t("permTabRoles")
|
|
3200
|
+
},
|
|
3201
|
+
{
|
|
3202
|
+
key: "members",
|
|
3203
|
+
label: t("permTabMembers")
|
|
3204
|
+
},
|
|
3205
|
+
{
|
|
3206
|
+
key: "departments",
|
|
3207
|
+
label: t("permTabDepartments")
|
|
3208
|
+
},
|
|
3209
|
+
{
|
|
3210
|
+
key: "resources",
|
|
3211
|
+
label: t("permTabResources")
|
|
3212
|
+
}
|
|
3213
|
+
];
|
|
3214
|
+
const save = async (section, value) => {
|
|
3215
|
+
setBusy(true);
|
|
3216
|
+
setNotice(null);
|
|
3217
|
+
try {
|
|
3218
|
+
const result = await saveRbacSection(section, value);
|
|
3219
|
+
setNotice(result.ok ? t("permSaved") : result.reason ?? t("permWaiting"));
|
|
3220
|
+
if (result.ok) await load();
|
|
3221
|
+
return result;
|
|
3222
|
+
} catch {
|
|
3223
|
+
setNotice(t("permSaveFailed"));
|
|
3224
|
+
return {
|
|
3225
|
+
ok: false,
|
|
3226
|
+
reason: t("permSaveFailed")
|
|
3227
|
+
};
|
|
3228
|
+
} finally {
|
|
3229
|
+
setBusy(false);
|
|
3230
|
+
}
|
|
3231
|
+
};
|
|
3232
|
+
const saveRoleChanges = async (changes) => {
|
|
3233
|
+
setBusy(true);
|
|
3234
|
+
setNotice(null);
|
|
3235
|
+
try {
|
|
3236
|
+
for (const change of changes) {
|
|
3237
|
+
const result = await saveRbacSection("roles", {
|
|
3238
|
+
roleCode: change.roleCode,
|
|
3239
|
+
permissionKeys: change.permissionKeys
|
|
3240
|
+
});
|
|
3241
|
+
if (!result.ok) {
|
|
3242
|
+
setNotice(result.reason ?? t("permSaveFailed"));
|
|
3243
|
+
await load();
|
|
3244
|
+
return result;
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
setNotice(t("permSaved"));
|
|
3248
|
+
await load();
|
|
3249
|
+
return { ok: true };
|
|
3250
|
+
} catch {
|
|
3251
|
+
setNotice(t("permSaveFailed"));
|
|
3252
|
+
return {
|
|
3253
|
+
ok: false,
|
|
3254
|
+
reason: t("permSaveFailed")
|
|
3255
|
+
};
|
|
3256
|
+
} finally {
|
|
3257
|
+
setBusy(false);
|
|
3258
|
+
}
|
|
3259
|
+
};
|
|
3260
|
+
const openDepartment = (department) => {
|
|
3261
|
+
setDeptDialog(department);
|
|
3262
|
+
setDeptSelected(department.deptAdmins?.map((admin) => admin.userId) ?? []);
|
|
3263
|
+
setDeptSearch("");
|
|
3264
|
+
setDeptError(null);
|
|
3265
|
+
};
|
|
3266
|
+
const assignDepartment = async (userIds) => {
|
|
3267
|
+
if (deptDialog === null || deptSaving) return;
|
|
3268
|
+
setDeptSaving(true);
|
|
3269
|
+
setDeptError(null);
|
|
3270
|
+
try {
|
|
3271
|
+
const result = await saveRbacSection("departments", {
|
|
3272
|
+
code: deptDialog.code,
|
|
3273
|
+
userIds
|
|
3274
|
+
});
|
|
3275
|
+
if (!result.ok) {
|
|
3276
|
+
setDeptError(result.reason ?? "保存失败:后端拒绝或无权限(403)。");
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3279
|
+
setDeptDialog(null);
|
|
3280
|
+
setNotice("部门管理员已更新。");
|
|
3281
|
+
await load();
|
|
3282
|
+
} catch {
|
|
3283
|
+
setDeptError("保存失败:后端拒绝或无权限(403)。");
|
|
3284
|
+
} finally {
|
|
3285
|
+
setDeptSaving(false);
|
|
3286
|
+
}
|
|
3287
|
+
};
|
|
3288
|
+
const removeDepartment = async (department) => {
|
|
3289
|
+
const children = overview?.departments.filter((row) => row.parentCode === department.code).length ?? 0;
|
|
3290
|
+
const members = department.memberCount ?? 0;
|
|
3291
|
+
if (!window.confirm("确认删除部门「" + department.name + "」?\n成员 " + String(members) + " 人,子部门 " + String(children) + " 个;删除后不可恢复。")) return;
|
|
3292
|
+
const result = await deleteDepartment(department.code);
|
|
3293
|
+
if (!result.ok) {
|
|
3294
|
+
setNotice(result.reason ?? "删除失败:后端拒绝或无权限(403)。");
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
setNotice("部门已删除。");
|
|
3298
|
+
await load();
|
|
3299
|
+
};
|
|
3300
|
+
const candidates = deptDialog === null || overview === null ? [] : overview.members.filter((member) => {
|
|
3301
|
+
const pathCodes = member.departmentPath?.map((entry) => entry.code) ?? [];
|
|
3302
|
+
return member.departmentCode === deptDialog.code || pathCodes.includes(deptDialog.code);
|
|
3303
|
+
}).filter((member) => {
|
|
3304
|
+
const needle = deptSearch.trim().toLowerCase();
|
|
3305
|
+
return needle === "" || member.displayName.toLowerCase().includes(needle) || member.username.toLowerCase().includes(needle);
|
|
3306
|
+
});
|
|
3307
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3308
|
+
className: PermissionCenterSection_module_css_default.page,
|
|
3309
|
+
"data-permission-center": "",
|
|
3310
|
+
children: [
|
|
3311
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
3312
|
+
className: PermissionCenterSection_module_css_default.head,
|
|
3313
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
3314
|
+
className: PermissionCenterSection_module_css_default.title,
|
|
3315
|
+
children: t("permTitle")
|
|
3316
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
3317
|
+
className: PermissionCenterSection_module_css_default.subtitle,
|
|
3318
|
+
children: [
|
|
3319
|
+
roleName,
|
|
3320
|
+
" · ",
|
|
3321
|
+
departmentName,
|
|
3322
|
+
demo ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3323
|
+
className: PermissionCenterSection_module_css_default.demo,
|
|
3324
|
+
children: t("permDemo")
|
|
3325
|
+
}) : null
|
|
3326
|
+
]
|
|
3327
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3328
|
+
variant: "outline",
|
|
3329
|
+
size: "sm",
|
|
3330
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, { size: 16 }),
|
|
3331
|
+
onClick: () => {
|
|
3332
|
+
load();
|
|
3333
|
+
},
|
|
3334
|
+
children: "刷新"
|
|
3335
|
+
})]
|
|
3336
|
+
}),
|
|
3337
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3338
|
+
className: PermissionCenterSection_module_css_default.tabs,
|
|
3339
|
+
role: "tablist",
|
|
3340
|
+
"aria-label": t("permTitle"),
|
|
3341
|
+
children: labels.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3342
|
+
type: "button",
|
|
3343
|
+
role: "tab",
|
|
3344
|
+
"aria-selected": tab === item.key,
|
|
3345
|
+
className: tab === item.key ? PermissionCenterSection_module_css_default.tab + " " + PermissionCenterSection_module_css_default.tabActive : PermissionCenterSection_module_css_default.tab,
|
|
3346
|
+
onClick: () => {
|
|
3347
|
+
setTab(item.key);
|
|
3348
|
+
},
|
|
3349
|
+
children: item.label
|
|
3350
|
+
}, item.key))
|
|
3351
|
+
}),
|
|
3352
|
+
notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3353
|
+
className: PermissionCenterSection_module_css_default.notice,
|
|
3354
|
+
children: notice
|
|
3355
|
+
}),
|
|
3356
|
+
overview === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3357
|
+
className: PermissionCenterSection_module_css_default.waiting,
|
|
3358
|
+
children: t("permWaiting")
|
|
3359
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3360
|
+
tab === "roles" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleMatrix, {
|
|
3361
|
+
roles: overview.matrix.roles,
|
|
3362
|
+
permissions: overview.matrix.permissions,
|
|
3363
|
+
writable,
|
|
3364
|
+
actorRole,
|
|
3365
|
+
busy,
|
|
3366
|
+
t,
|
|
3367
|
+
onSaveAll: saveRoleChanges
|
|
3368
|
+
}) : null,
|
|
3369
|
+
tab === "members" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Members, {
|
|
3370
|
+
members: overview.members,
|
|
3371
|
+
writable,
|
|
3372
|
+
actorRole,
|
|
3373
|
+
actorUserId: context?.userId,
|
|
3374
|
+
actorDepartmentCode: context?.departmentCode,
|
|
3375
|
+
busy,
|
|
3376
|
+
t,
|
|
3377
|
+
onSave: (userId, roleCodes) => {
|
|
3378
|
+
save("members", {
|
|
3379
|
+
userId,
|
|
3380
|
+
roleCodes
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
}) : null,
|
|
3384
|
+
tab === "departments" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Departments, {
|
|
3385
|
+
departments: overview.departments,
|
|
3386
|
+
writable,
|
|
3387
|
+
canAssign: canAssignDepartment,
|
|
3388
|
+
canWrite: writable && (superAdmin || actorRole === "ADMIN"),
|
|
3389
|
+
t,
|
|
3390
|
+
onAssign: openDepartment,
|
|
3391
|
+
onAdd: (parentCode) => {
|
|
3392
|
+
setDeptForm({
|
|
3393
|
+
mode: "create",
|
|
3394
|
+
parentCode
|
|
3395
|
+
});
|
|
3396
|
+
},
|
|
3397
|
+
onEdit: (department) => {
|
|
3398
|
+
setDeptForm({
|
|
3399
|
+
mode: "edit",
|
|
3400
|
+
department
|
|
3401
|
+
});
|
|
3402
|
+
},
|
|
3403
|
+
onDelete: (department) => {
|
|
3404
|
+
removeDepartment(department);
|
|
3405
|
+
}
|
|
3406
|
+
}) : null,
|
|
3407
|
+
tab === "resources" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Resources, {
|
|
3408
|
+
resources: overview.resources,
|
|
3409
|
+
departments: overview.departments,
|
|
3410
|
+
writable,
|
|
3411
|
+
actorRole,
|
|
3412
|
+
actorDepartmentCode: context?.departmentCode,
|
|
3413
|
+
busy,
|
|
3414
|
+
t,
|
|
3415
|
+
onSave: (row, visibility, departmentCode) => save("resources", {
|
|
3416
|
+
kind: row.kind,
|
|
3417
|
+
id: row.id,
|
|
3418
|
+
visibility,
|
|
3419
|
+
departmentCode
|
|
3420
|
+
})
|
|
3421
|
+
}) : null
|
|
3422
|
+
] }),
|
|
3423
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
3424
|
+
open: deptDialog !== null,
|
|
3425
|
+
onClose: () => {
|
|
3426
|
+
if (!deptSaving) setDeptDialog(null);
|
|
3427
|
+
},
|
|
3428
|
+
title: deptDialog === null ? "分配部门管理员" : "分配管理员 · " + deptDialog.name,
|
|
3429
|
+
closeLabel: "取消",
|
|
3430
|
+
className: PermissionCenterSection_module_css_default.dialog,
|
|
3431
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3432
|
+
className: PermissionCenterSection_module_css_default.dialogFooter,
|
|
3433
|
+
children: [
|
|
3434
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3435
|
+
variant: "outline",
|
|
3436
|
+
disabled: deptSaving,
|
|
3437
|
+
onClick: () => {
|
|
3438
|
+
if (deptDialog !== null) assignDepartment([]);
|
|
3439
|
+
},
|
|
3440
|
+
children: "清空"
|
|
3441
|
+
}),
|
|
3442
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3443
|
+
variant: "outline",
|
|
3444
|
+
disabled: deptSaving,
|
|
3445
|
+
onClick: () => {
|
|
3446
|
+
setDeptDialog(null);
|
|
3447
|
+
},
|
|
3448
|
+
children: "取消"
|
|
3449
|
+
}),
|
|
3450
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3451
|
+
variant: "primary",
|
|
3452
|
+
disabled: deptSaving,
|
|
3453
|
+
onClick: () => {
|
|
3454
|
+
assignDepartment(deptSelected);
|
|
3455
|
+
},
|
|
3456
|
+
children: "保存"
|
|
3457
|
+
})
|
|
3458
|
+
]
|
|
3459
|
+
}),
|
|
3460
|
+
children: deptDialog === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3461
|
+
className: PermissionCenterSection_module_css_default.dialogBody,
|
|
3462
|
+
children: [
|
|
3463
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
3464
|
+
className: PermissionCenterSection_module_css_default.dialogHint,
|
|
3465
|
+
children: ["当前管理员:", deptDialog.deptAdmins?.length === 0 || deptDialog.deptAdmins === void 0 ? "未设置" : deptDialog.deptAdmins.map((admin) => admin.displayName).join("、")]
|
|
3466
|
+
}),
|
|
3467
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3468
|
+
className: PermissionCenterSection_module_css_default.search,
|
|
3469
|
+
value: deptSearch,
|
|
3470
|
+
placeholder: "搜索本部门成员…",
|
|
3471
|
+
onChange: (event) => {
|
|
3472
|
+
setDeptSearch(event.target.value);
|
|
3473
|
+
}
|
|
3474
|
+
}),
|
|
3475
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
3476
|
+
className: PermissionCenterSection_module_css_default.candidates,
|
|
3477
|
+
children: candidates.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
3478
|
+
className: PermissionCenterSection_module_css_default.muted,
|
|
3479
|
+
children: "没有可选成员(后端可能只允许本部门/未分配用户)。"
|
|
3480
|
+
}) : candidates.map((member) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3481
|
+
className: PermissionCenterSection_module_css_default.candidate,
|
|
3482
|
+
children: [
|
|
3483
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3484
|
+
type: "checkbox",
|
|
3485
|
+
checked: deptSelected.includes(member.userId),
|
|
3486
|
+
onChange: (event) => {
|
|
3487
|
+
setDeptSelected((current) => event.target.checked ? [...current, member.userId] : current.filter((id) => id !== member.userId));
|
|
3488
|
+
}
|
|
3489
|
+
}),
|
|
3490
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: member.displayName }),
|
|
3491
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3492
|
+
className: PermissionCenterSection_module_css_default.muted,
|
|
3493
|
+
children: [
|
|
3494
|
+
"@",
|
|
3495
|
+
member.username,
|
|
3496
|
+
" · ",
|
|
3497
|
+
member.departmentPath?.map((entry) => entry.name).join(" / ") ?? "—"
|
|
3498
|
+
]
|
|
3499
|
+
})
|
|
3500
|
+
]
|
|
3501
|
+
}) }, member.userId))
|
|
3502
|
+
}),
|
|
3503
|
+
deptError === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3504
|
+
className: PermissionCenterSection_module_css_default.error,
|
|
3505
|
+
role: "alert",
|
|
3506
|
+
children: deptError
|
|
3507
|
+
})
|
|
3508
|
+
]
|
|
3509
|
+
})
|
|
3510
|
+
}),
|
|
3511
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DepartmentFormDialog, {
|
|
3512
|
+
state: deptForm,
|
|
3513
|
+
departments: overview?.departments ?? [],
|
|
3514
|
+
t,
|
|
3515
|
+
onClose: () => {
|
|
3516
|
+
setDeptForm(null);
|
|
3517
|
+
},
|
|
3518
|
+
onSaved: async () => {
|
|
3519
|
+
setDeptForm(null);
|
|
3520
|
+
setNotice("部门已保存。");
|
|
3521
|
+
await load();
|
|
3522
|
+
}
|
|
3523
|
+
})
|
|
3524
|
+
]
|
|
3525
|
+
});
|
|
3526
|
+
}
|
|
3527
|
+
function RoleMatrix({ roles, permissions, writable, actorRole, busy, t, onSaveAll }) {
|
|
3528
|
+
const visibleRoles = (0, react.useMemo)(() => roles.filter((role) => role.systemKey === "SUPER_ADMIN" || role.systemKey === "ADMIN" || role.systemKey === "DEPT_ADMIN" || role.systemKey === "APP_USER" || role.code === "超级管理员" || role.code === "管理员" || role.code === "部门管理员" || role.code === "应用人员" || role.code === "普通用户").map((role) => role.systemKey === "APP_USER" || role.code === "应用人员" || role.code === "普通用户" ? {
|
|
3529
|
+
...role,
|
|
3530
|
+
name: "普通用户"
|
|
3531
|
+
} : role), [roles]);
|
|
3532
|
+
const groups = (0, react.useMemo)(() => {
|
|
3533
|
+
const hasSkill = permissions.some((point) => point.group === "skill" || point.group === "技能中心");
|
|
3534
|
+
const keyOf = (group) => {
|
|
3535
|
+
const raw = group.trim().toLowerCase();
|
|
3536
|
+
if (raw === "kb" || raw === "knowledge" || group === "知识库") return "kb";
|
|
3537
|
+
if (raw === "skill" || group === "技能中心") return "skill";
|
|
3538
|
+
if (raw === "workflow" || group === "工作流广场") return "workflow";
|
|
3539
|
+
if (raw === "automation" || group === "自动化任务") return "automation";
|
|
3540
|
+
if (raw === "ai" && !hasSkill) return "skill";
|
|
3541
|
+
return null;
|
|
3542
|
+
};
|
|
3543
|
+
return [
|
|
3544
|
+
{
|
|
3545
|
+
key: "kb",
|
|
3546
|
+
label: "知识库"
|
|
3547
|
+
},
|
|
3548
|
+
{
|
|
3549
|
+
key: "skill",
|
|
3550
|
+
label: "技能中心"
|
|
3551
|
+
},
|
|
3552
|
+
{
|
|
3553
|
+
key: "workflow",
|
|
3554
|
+
label: "工作流广场"
|
|
3555
|
+
},
|
|
3556
|
+
{
|
|
3557
|
+
key: "automation",
|
|
3558
|
+
label: "自动化任务"
|
|
3559
|
+
}
|
|
3560
|
+
].map((def) => ({
|
|
3561
|
+
...def,
|
|
3562
|
+
points: permissions.filter((point) => keyOf(point.group) === def.key)
|
|
3563
|
+
})).filter((group) => group.points.length > 0);
|
|
3564
|
+
}, [permissions]);
|
|
3565
|
+
(0, react.useMemo)(() => groups.flatMap((group) => group.points), [groups]);
|
|
3566
|
+
const [draft, setDraft] = (0, react.useState)(() => Object.fromEntries(visibleRoles.map((role) => [role.code, new Set(role.permissions)])));
|
|
3567
|
+
const [dirty, setDirty] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
3568
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
3569
|
+
const [error, setError] = (0, react.useState)(null);
|
|
3570
|
+
const [copiedKey, setCopiedKey] = (0, react.useState)(null);
|
|
3571
|
+
const copyKey = async (key) => {
|
|
3572
|
+
try {
|
|
3573
|
+
await navigator.clipboard.writeText(key);
|
|
3574
|
+
} catch {}
|
|
3575
|
+
setCopiedKey(key);
|
|
3576
|
+
window.setTimeout(() => {
|
|
3577
|
+
setCopiedKey((current) => current === key ? null : current);
|
|
3578
|
+
}, 1600);
|
|
3579
|
+
};
|
|
3580
|
+
(0, react.useEffect)(() => {
|
|
3581
|
+
setDraft(Object.fromEntries(visibleRoles.map((role) => [role.code, new Set(role.permissions)])));
|
|
3582
|
+
setDirty(/* @__PURE__ */ new Set());
|
|
3583
|
+
}, [visibleRoles]);
|
|
3584
|
+
const isDepartmentRole = (code) => code === "DEPT_ADMIN" || code === "部门管理员";
|
|
3585
|
+
const isAppRole = (code) => code === "APP_USER" || code === "应用人员" || code === "普通用户";
|
|
3586
|
+
const globalScope = (point) => point.scope === "global" || point.scope === "GLOBAL" || point.key === "skill:review:global" || point.key === "workflow:review:global";
|
|
3587
|
+
const locked = (roleCode, point) => {
|
|
3588
|
+
if (!writable || actorRole !== "SUPER_ADMIN") return true;
|
|
3589
|
+
return globalScope(point) && (isDepartmentRole(roleCode) || isAppRole(roleCode));
|
|
3590
|
+
};
|
|
3591
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3592
|
+
className: PermissionCenterSection_module_css_default.panel,
|
|
3593
|
+
children: [
|
|
3594
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
3595
|
+
className: PermissionCenterSection_module_css_default.sectionTitle,
|
|
3596
|
+
children: t("permRoleMatrix")
|
|
3597
|
+
}),
|
|
3598
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3599
|
+
className: PermissionCenterSection_module_css_default.matrixScroll,
|
|
3600
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
3601
|
+
className: PermissionCenterSection_module_css_default.matrix,
|
|
3602
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
3603
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permPermissionModule") }),
|
|
3604
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permPermissionPoint") }),
|
|
3605
|
+
visibleRoles.map((role) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: role.name }, role.code))
|
|
3606
|
+
] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: group.points.map((point, pointIndex) => {
|
|
3607
|
+
const global = globalScope(point);
|
|
3608
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
3609
|
+
pointIndex === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
3610
|
+
scope: "rowgroup",
|
|
3611
|
+
rowSpan: group.points.length,
|
|
3612
|
+
className: PermissionCenterSection_module_css_default.matrixModule,
|
|
3613
|
+
children: group.label
|
|
3614
|
+
}) : null,
|
|
3615
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("th", {
|
|
3616
|
+
scope: "row",
|
|
3617
|
+
className: PermissionCenterSection_module_css_default.matrixPermission,
|
|
3618
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3619
|
+
type: "button",
|
|
3620
|
+
className: PermissionCenterSection_module_css_default.permCopy,
|
|
3621
|
+
title: point.key,
|
|
3622
|
+
onClick: () => {
|
|
3623
|
+
copyKey(point.key);
|
|
3624
|
+
},
|
|
3625
|
+
children: [
|
|
3626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: point.name }),
|
|
3627
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3628
|
+
className: PermissionCenterSection_module_css_default.scopeBadge,
|
|
3629
|
+
"data-scope": global ? "global" : "department",
|
|
3630
|
+
children: global ? "全局" : "部门级"
|
|
3631
|
+
}),
|
|
3632
|
+
copiedKey === point.key ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3633
|
+
className: PermissionCenterSection_module_css_default.copied,
|
|
3634
|
+
children: "已复制"
|
|
3635
|
+
}) : null
|
|
3636
|
+
]
|
|
3637
|
+
}), point.description === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3638
|
+
className: PermissionCenterSection_module_css_default.permDescription,
|
|
3639
|
+
children: point.description
|
|
3640
|
+
})]
|
|
3641
|
+
}),
|
|
3642
|
+
visibleRoles.map((role) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3643
|
+
className: PermissionCenterSection_module_css_default.permission,
|
|
3644
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3645
|
+
type: "checkbox",
|
|
3646
|
+
checked: draft[role.code]?.has(point.key) === true,
|
|
3647
|
+
disabled: locked(role.code, point) || busy,
|
|
3648
|
+
onChange: (event) => {
|
|
3649
|
+
setDraft((current) => {
|
|
3650
|
+
const next = new Set(current[role.code] ?? []);
|
|
3651
|
+
if (event.target.checked) next.add(point.key);
|
|
3652
|
+
else next.delete(point.key);
|
|
3653
|
+
return {
|
|
3654
|
+
...current,
|
|
3655
|
+
[role.code]: next
|
|
3656
|
+
};
|
|
3657
|
+
});
|
|
3658
|
+
setDirty((current) => new Set(current).add(role.code));
|
|
3659
|
+
}
|
|
3660
|
+
}), global && (isDepartmentRole(role.code) || isAppRole(role.code)) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3661
|
+
className: PermissionCenterSection_module_css_default.onlyAdmin,
|
|
3662
|
+
children: "仅管理员"
|
|
3663
|
+
}) : null]
|
|
3664
|
+
}) }, role.code))
|
|
3665
|
+
] }, point.key);
|
|
3666
|
+
}) }, group.key)) })]
|
|
3667
|
+
})
|
|
3668
|
+
}),
|
|
3669
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3670
|
+
className: PermissionCenterSection_module_css_default.panelFoot,
|
|
3671
|
+
children: [
|
|
3672
|
+
error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3673
|
+
className: PermissionCenterSection_module_css_default.error,
|
|
3674
|
+
children: error
|
|
3675
|
+
}),
|
|
3676
|
+
actorRole !== "SUPER_ADMIN" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3677
|
+
className: PermissionCenterSection_module_css_default.readOnly,
|
|
3678
|
+
children: t("permSuperAdminOnly")
|
|
3679
|
+
}) : null,
|
|
3680
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3681
|
+
variant: "primary",
|
|
3682
|
+
size: "sm",
|
|
3683
|
+
disabled: dirty.size === 0 || busy || saving,
|
|
3684
|
+
onClick: () => {
|
|
3685
|
+
setSaving(true);
|
|
3686
|
+
setError(null);
|
|
3687
|
+
onSaveAll(visibleRoles.filter((role) => dirty.has(role.code)).map((role) => ({
|
|
3688
|
+
roleCode: role.code,
|
|
3689
|
+
permissionKeys: [...draft[role.code] ?? []]
|
|
3690
|
+
}))).then((result) => {
|
|
3691
|
+
if (result.ok) {
|
|
3692
|
+
setDirty(/* @__PURE__ */ new Set());
|
|
3693
|
+
setError(null);
|
|
3694
|
+
} else setError(result.reason ?? t("permSaveFailed"));
|
|
3695
|
+
}).finally(() => {
|
|
3696
|
+
setSaving(false);
|
|
3697
|
+
});
|
|
3698
|
+
},
|
|
3699
|
+
children: [t("permSave"), dirty.size === 0 ? "" : " (" + String(dirty.size) + ")"]
|
|
3700
|
+
})
|
|
3701
|
+
]
|
|
3702
|
+
})
|
|
3703
|
+
]
|
|
3704
|
+
});
|
|
3705
|
+
}
|
|
3706
|
+
function Members({ members, writable, actorRole, actorUserId, actorDepartmentCode, busy, t, onSave }) {
|
|
3707
|
+
const [page, setPage] = (0, react.useState)(0);
|
|
3708
|
+
const protectedMember = (member) => member.roleCodes.some((code) => {
|
|
3709
|
+
if (code === "SUPER_ADMIN" || code === "超级管理员" || code === "ADMIN" || code === "管理员") return true;
|
|
3710
|
+
return actorRole === "DEPT_ADMIN" && (code === "DEPT_ADMIN" || code === "部门管理员");
|
|
3711
|
+
});
|
|
3712
|
+
const superCount = members.filter((member) => member.roleCodes.some((code) => code === "SUPER_ADMIN" || code === "超级管理员")).length;
|
|
3713
|
+
const inScope = (member) => actorRole === "SUPER_ADMIN" || actorRole === "ADMIN" || member.departmentCode === actorDepartmentCode || member.departmentPath?.some((entry) => entry.code === actorDepartmentCode) === true;
|
|
3714
|
+
const pages = Math.max(1, Math.ceil(members.length / PAGE_SIZE));
|
|
3715
|
+
const visible = members.slice(page * PAGE_SIZE, page * PAGE_SIZE + PAGE_SIZE);
|
|
3716
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3717
|
+
className: PermissionCenterSection_module_css_default.panel,
|
|
3718
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3719
|
+
className: PermissionCenterSection_module_css_default.tableScroll,
|
|
3720
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
3721
|
+
className: PermissionCenterSection_module_css_default.table,
|
|
3722
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
3723
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permMember") }),
|
|
3724
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permDepartment") }),
|
|
3725
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permRole") })
|
|
3726
|
+
] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: visible.map((member) => {
|
|
3727
|
+
const isProtected = protectedMember(member);
|
|
3728
|
+
const scoped = inScope(member);
|
|
3729
|
+
const isSelf = actorUserId !== void 0 && member.userId === actorUserId;
|
|
3730
|
+
const lastSuper = superCount <= 1 && member.roleCodes.some((code) => code === "SUPER_ADMIN" || code === "超级管理员");
|
|
3731
|
+
const locked = !writable || !scoped || isSelf || lastSuper || actorRole !== "SUPER_ADMIN" && isProtected;
|
|
3732
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
|
|
3733
|
+
"data-locked": locked,
|
|
3734
|
+
children: [
|
|
3735
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [
|
|
3736
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: member.displayName }),
|
|
3737
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3738
|
+
className: PermissionCenterSection_module_css_default.muted,
|
|
3739
|
+
children: [" @", member.username]
|
|
3740
|
+
}),
|
|
3741
|
+
isProtected && actorRole !== "SUPER_ADMIN" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3742
|
+
className: PermissionCenterSection_module_css_default.protected,
|
|
3743
|
+
children: "受保护"
|
|
3744
|
+
}) : null,
|
|
3745
|
+
isSelf ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3746
|
+
className: PermissionCenterSection_module_css_default.protected,
|
|
3747
|
+
children: "本人"
|
|
3748
|
+
}) : null,
|
|
3749
|
+
lastSuper ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3750
|
+
className: PermissionCenterSection_module_css_default.protected,
|
|
3751
|
+
children: "最后一个超管"
|
|
3752
|
+
}) : null,
|
|
3753
|
+
!scoped ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3754
|
+
className: PermissionCenterSection_module_css_default.protected,
|
|
3755
|
+
children: "越界"
|
|
3756
|
+
}) : null
|
|
3757
|
+
] }),
|
|
3758
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: member.departmentPath?.map((entry) => entry.name).join(" / ") ?? "—" }),
|
|
3759
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
3760
|
+
value: member.roleCodes[0] ?? "APP_USER",
|
|
3761
|
+
disabled: locked || busy,
|
|
3762
|
+
onChange: (event) => {
|
|
3763
|
+
if (window.confirm("确认修改该成员的角色?")) onSave(member.userId, [event.target.value]);
|
|
3764
|
+
},
|
|
3765
|
+
children: (actorRole === "SUPER_ADMIN" ? [
|
|
3766
|
+
"超级管理员",
|
|
3767
|
+
"管理员",
|
|
3768
|
+
"部门管理员",
|
|
3769
|
+
"运营人员",
|
|
3770
|
+
"应用人员"
|
|
3771
|
+
] : [
|
|
3772
|
+
"部门管理员",
|
|
3773
|
+
"运营人员",
|
|
3774
|
+
"应用人员"
|
|
3775
|
+
]).map((role) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3776
|
+
value: role,
|
|
3777
|
+
children: role
|
|
3778
|
+
}, role))
|
|
3779
|
+
}) })
|
|
3780
|
+
]
|
|
3781
|
+
}, member.userId);
|
|
3782
|
+
}) })]
|
|
3783
|
+
})
|
|
3784
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pager, {
|
|
3785
|
+
page,
|
|
3786
|
+
pages,
|
|
3787
|
+
onChange: setPage
|
|
3788
|
+
})]
|
|
3789
|
+
});
|
|
3790
|
+
}
|
|
3791
|
+
function Departments({ departments, writable, canAssign, canWrite, t, onAssign, onAdd, onEdit, onDelete }) {
|
|
3792
|
+
const [expanded, setExpanded] = (0, react.useState)(() => new Set(departments.map((department) => department.code)));
|
|
3793
|
+
const byParent = (0, react.useMemo)(() => {
|
|
3794
|
+
const map = /* @__PURE__ */ new Map();
|
|
3795
|
+
const codes = new Set(departments.map((department) => department.code));
|
|
3796
|
+
for (const department of departments) {
|
|
3797
|
+
const parent = department.parentCode !== void 0 && department.parentCode !== null && codes.has(department.parentCode) ? department.parentCode : null;
|
|
3798
|
+
map.set(parent, [...map.get(parent) ?? [], department]);
|
|
3799
|
+
}
|
|
3800
|
+
for (const list of map.values()) list.sort((left, right) => left.name.localeCompare(right.name, "zh-CN") || left.code.localeCompare(right.code));
|
|
3801
|
+
return map;
|
|
3802
|
+
}, [departments]);
|
|
3803
|
+
const node = (department, depth) => {
|
|
3804
|
+
const children = byParent.get(department.code) ?? [];
|
|
3805
|
+
const open = expanded.has(department.code);
|
|
3806
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
3807
|
+
className: PermissionCenterSection_module_css_default.treeNode,
|
|
3808
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3809
|
+
className: PermissionCenterSection_module_css_default.treeRow,
|
|
3810
|
+
style: { paddingLeft: String(8 + depth * 18) + "px" },
|
|
3811
|
+
children: [
|
|
3812
|
+
children.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: PermissionCenterSection_module_css_default.treeSpacer }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3813
|
+
type: "button",
|
|
3814
|
+
className: PermissionCenterSection_module_css_default.treeToggle,
|
|
3815
|
+
"aria-label": open ? "折叠" : "展开",
|
|
3816
|
+
onClick: () => {
|
|
3817
|
+
setExpanded((current) => {
|
|
3818
|
+
const next = new Set(current);
|
|
3819
|
+
if (next.has(department.code)) next.delete(department.code);
|
|
3820
|
+
else next.add(department.code);
|
|
3821
|
+
return next;
|
|
3822
|
+
});
|
|
3823
|
+
},
|
|
3824
|
+
children: open ? "▾" : "▸"
|
|
3825
|
+
}),
|
|
3826
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
3827
|
+
className: PermissionCenterSection_module_css_default.treeName,
|
|
3828
|
+
children: department.name
|
|
3829
|
+
}),
|
|
3830
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3831
|
+
className: PermissionCenterSection_module_css_default.muted,
|
|
3832
|
+
children: [
|
|
3833
|
+
department.code,
|
|
3834
|
+
" · ",
|
|
3835
|
+
department.memberCount ?? 0,
|
|
3836
|
+
" 人"
|
|
3837
|
+
]
|
|
3838
|
+
}),
|
|
3839
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3840
|
+
className: PermissionCenterSection_module_css_default.deptAdmins,
|
|
3841
|
+
children: department.deptAdmins?.length === 0 || department.deptAdmins === void 0 ? null : department.deptAdmins.map((admin) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3842
|
+
className: PermissionCenterSection_module_css_default.adminTag,
|
|
3843
|
+
children: admin.displayName
|
|
3844
|
+
}, admin.userId))
|
|
3845
|
+
}),
|
|
3846
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3847
|
+
className: PermissionCenterSection_module_css_default.treeActions,
|
|
3848
|
+
children: [
|
|
3849
|
+
canAssign ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3850
|
+
variant: "ghost",
|
|
3851
|
+
size: "sm",
|
|
3852
|
+
disabled: !writable,
|
|
3853
|
+
onClick: () => {
|
|
3854
|
+
onAssign(department);
|
|
3855
|
+
},
|
|
3856
|
+
children: "分配管理员"
|
|
3857
|
+
}) : null,
|
|
3858
|
+
canWrite ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3859
|
+
variant: "ghost",
|
|
3860
|
+
size: "sm",
|
|
3861
|
+
onClick: () => {
|
|
3862
|
+
onAdd(department.code);
|
|
3863
|
+
},
|
|
3864
|
+
children: "+ 子部门"
|
|
3865
|
+
}) : null,
|
|
3866
|
+
canWrite ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3867
|
+
variant: "ghost",
|
|
3868
|
+
size: "sm",
|
|
3869
|
+
onClick: () => {
|
|
3870
|
+
onEdit(department);
|
|
3871
|
+
},
|
|
3872
|
+
children: "编辑"
|
|
3873
|
+
}) : null,
|
|
3874
|
+
canWrite ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3875
|
+
variant: "ghost",
|
|
3876
|
+
size: "sm",
|
|
3877
|
+
onClick: () => {
|
|
3878
|
+
onDelete(department);
|
|
3879
|
+
},
|
|
3880
|
+
children: "删除"
|
|
3881
|
+
}) : null
|
|
3882
|
+
]
|
|
3883
|
+
})
|
|
3884
|
+
]
|
|
3885
|
+
}), children.length > 0 && open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
3886
|
+
className: PermissionCenterSection_module_css_default.treeChildren,
|
|
3887
|
+
children: children.map((child) => node(child, depth + 1))
|
|
3888
|
+
}) : null]
|
|
3889
|
+
}, department.code);
|
|
3890
|
+
};
|
|
3891
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3892
|
+
className: PermissionCenterSection_module_css_default.panel,
|
|
3893
|
+
children: [
|
|
3894
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3895
|
+
className: PermissionCenterSection_module_css_default.panelHead,
|
|
3896
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
3897
|
+
className: PermissionCenterSection_module_css_default.sectionTitle,
|
|
3898
|
+
children: t("permTabDepartments")
|
|
3899
|
+
}), canWrite ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3900
|
+
variant: "outline",
|
|
3901
|
+
size: "sm",
|
|
3902
|
+
onClick: () => {
|
|
3903
|
+
onAdd(null);
|
|
3904
|
+
},
|
|
3905
|
+
children: "新增部门"
|
|
3906
|
+
}) : null]
|
|
3907
|
+
}),
|
|
3908
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
3909
|
+
className: PermissionCenterSection_module_css_default.treeRoot,
|
|
3910
|
+
children: (byParent.get(null) ?? []).map((department) => node(department, 0))
|
|
3911
|
+
}),
|
|
3912
|
+
departments.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3913
|
+
className: PermissionCenterSection_module_css_default.muted,
|
|
3914
|
+
children: "暂无部门。"
|
|
3915
|
+
}) : null
|
|
3916
|
+
]
|
|
3917
|
+
});
|
|
3918
|
+
}
|
|
3919
|
+
/** Create/edit dialog for one department. */
|
|
3920
|
+
function DepartmentFormDialog({ state, departments, t, onClose, onSaved }) {
|
|
3921
|
+
const [code, setCode] = (0, react.useState)("");
|
|
3922
|
+
const [name, setName] = (0, react.useState)("");
|
|
3923
|
+
const [parentCode, setParentCode] = (0, react.useState)("");
|
|
3924
|
+
const [description, setDescription] = (0, react.useState)("");
|
|
3925
|
+
const [owner, setOwner] = (0, react.useState)("");
|
|
3926
|
+
const [enabled, setEnabled] = (0, react.useState)(true);
|
|
3927
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
3928
|
+
const [error, setError] = (0, react.useState)(null);
|
|
3929
|
+
(0, react.useEffect)(() => {
|
|
3930
|
+
if (state === null) return;
|
|
3931
|
+
setCode(state.department?.code ?? "");
|
|
3932
|
+
setName(state.department?.name ?? "");
|
|
3933
|
+
setParentCode(state.parentCode ?? state.department?.parentCode ?? "");
|
|
3934
|
+
setDescription(state.department?.description ?? "");
|
|
3935
|
+
setOwner(state.department?.owner ?? "");
|
|
3936
|
+
setEnabled(state.department?.enabled !== false);
|
|
3937
|
+
setError(null);
|
|
3938
|
+
}, [state]);
|
|
3939
|
+
if (state === null) return null;
|
|
3940
|
+
const editing = state.mode === "edit";
|
|
3941
|
+
const blocked = /* @__PURE__ */ new Set();
|
|
3942
|
+
if (editing && state.department !== void 0) {
|
|
3943
|
+
blocked.add(state.department.code);
|
|
3944
|
+
let changed = true;
|
|
3945
|
+
while (changed) {
|
|
3946
|
+
changed = false;
|
|
3947
|
+
for (const department of departments) if (department.parentCode !== void 0 && department.parentCode !== null && blocked.has(department.parentCode) && !blocked.has(department.code)) {
|
|
3948
|
+
blocked.add(department.code);
|
|
3949
|
+
changed = true;
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
const submit = async () => {
|
|
3954
|
+
if (saving) return;
|
|
3955
|
+
setSaving(true);
|
|
3956
|
+
setError(null);
|
|
3957
|
+
try {
|
|
3958
|
+
const result = editing && state.department !== void 0 ? await updateDepartment({
|
|
3959
|
+
code: state.department.code,
|
|
3960
|
+
name,
|
|
3961
|
+
parentCode: parentCode === "" ? null : parentCode,
|
|
3962
|
+
description,
|
|
3963
|
+
owner,
|
|
3964
|
+
enabled,
|
|
3965
|
+
updatedAt: state.department.updatedAt
|
|
3966
|
+
}) : await createDepartment({
|
|
3967
|
+
code: code.trim(),
|
|
3968
|
+
name: name.trim(),
|
|
3969
|
+
parentCode: parentCode === "" ? null : parentCode,
|
|
3970
|
+
description,
|
|
3971
|
+
owner,
|
|
3972
|
+
enabled
|
|
3973
|
+
});
|
|
3974
|
+
if (!result.ok) {
|
|
3975
|
+
setError(result.reason ?? "保存失败:后端拒绝或无权限(403)。");
|
|
3976
|
+
return;
|
|
3977
|
+
}
|
|
3978
|
+
await onSaved();
|
|
3979
|
+
} catch {
|
|
3980
|
+
setError("保存失败:网络或后端异常。");
|
|
3981
|
+
} finally {
|
|
3982
|
+
setSaving(false);
|
|
3983
|
+
}
|
|
3984
|
+
};
|
|
3985
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
3986
|
+
open: true,
|
|
3987
|
+
onClose: () => {
|
|
3988
|
+
if (!saving) onClose();
|
|
3989
|
+
},
|
|
3990
|
+
title: editing ? "编辑部门" : "新增部门",
|
|
3991
|
+
closeLabel: "取消",
|
|
3992
|
+
className: PermissionCenterSection_module_css_default.dialog,
|
|
3993
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3994
|
+
className: PermissionCenterSection_module_css_default.dialogFooter,
|
|
3995
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3996
|
+
variant: "outline",
|
|
3997
|
+
disabled: saving,
|
|
3998
|
+
onClick: onClose,
|
|
3999
|
+
children: "取消"
|
|
4000
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4001
|
+
variant: "primary",
|
|
4002
|
+
disabled: saving,
|
|
4003
|
+
onClick: () => {
|
|
4004
|
+
submit();
|
|
4005
|
+
},
|
|
4006
|
+
children: "保存"
|
|
4007
|
+
})]
|
|
4008
|
+
}),
|
|
4009
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4010
|
+
className: PermissionCenterSection_module_css_default.dialogBody,
|
|
4011
|
+
children: [
|
|
4012
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4013
|
+
className: PermissionCenterSection_module_css_default.field,
|
|
4014
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "部门编码" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4015
|
+
value: code,
|
|
4016
|
+
disabled: editing,
|
|
4017
|
+
placeholder: "例如 marketing",
|
|
4018
|
+
onChange: (event) => {
|
|
4019
|
+
setCode(event.target.value);
|
|
4020
|
+
}
|
|
4021
|
+
})]
|
|
4022
|
+
}),
|
|
4023
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4024
|
+
className: PermissionCenterSection_module_css_default.field,
|
|
4025
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "部门名称" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4026
|
+
value: name,
|
|
4027
|
+
placeholder: "例如 市场部",
|
|
4028
|
+
onChange: (event) => {
|
|
4029
|
+
setName(event.target.value);
|
|
4030
|
+
}
|
|
4031
|
+
})]
|
|
4032
|
+
}),
|
|
4033
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4034
|
+
className: PermissionCenterSection_module_css_default.field,
|
|
4035
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "上级部门" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchCombobox, {
|
|
4036
|
+
t,
|
|
4037
|
+
value: parentCode,
|
|
4038
|
+
ariaLabel: "上级部门",
|
|
4039
|
+
placeholder: t("permNone"),
|
|
4040
|
+
options: [{
|
|
4041
|
+
value: "",
|
|
4042
|
+
label: t("permNone")
|
|
4043
|
+
}, ...departments.filter((department) => !blocked.has(department.code)).map((department) => ({
|
|
4044
|
+
value: department.code,
|
|
4045
|
+
label: department.name,
|
|
4046
|
+
hint: department.code
|
|
4047
|
+
}))],
|
|
4048
|
+
onChange: setParentCode
|
|
4049
|
+
})]
|
|
4050
|
+
}),
|
|
4051
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4052
|
+
className: PermissionCenterSection_module_css_default.field,
|
|
4053
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "描述" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4054
|
+
value: description,
|
|
4055
|
+
onChange: (event) => {
|
|
4056
|
+
setDescription(event.target.value);
|
|
4057
|
+
}
|
|
4058
|
+
})]
|
|
4059
|
+
}),
|
|
4060
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4061
|
+
className: PermissionCenterSection_module_css_default.field,
|
|
4062
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "负责人" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4063
|
+
value: owner,
|
|
4064
|
+
onChange: (event) => {
|
|
4065
|
+
setOwner(event.target.value);
|
|
4066
|
+
}
|
|
4067
|
+
})]
|
|
4068
|
+
}),
|
|
4069
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4070
|
+
className: PermissionCenterSection_module_css_default.candidate,
|
|
4071
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4072
|
+
type: "checkbox",
|
|
4073
|
+
checked: enabled,
|
|
4074
|
+
onChange: (event) => {
|
|
4075
|
+
setEnabled(event.target.checked);
|
|
4076
|
+
}
|
|
4077
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "启用" })]
|
|
4078
|
+
}),
|
|
4079
|
+
error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4080
|
+
className: PermissionCenterSection_module_css_default.error,
|
|
4081
|
+
role: "alert",
|
|
4082
|
+
children: error
|
|
4083
|
+
})
|
|
4084
|
+
]
|
|
4085
|
+
})
|
|
4086
|
+
});
|
|
4087
|
+
}
|
|
4088
|
+
function Resources({ resources, departments, writable, actorRole, actorDepartmentCode, busy, t, onSave }) {
|
|
4089
|
+
const [page, setPage] = (0, react.useState)(0);
|
|
4090
|
+
const [draft, setDraft] = (0, react.useState)({});
|
|
4091
|
+
const [error, setError] = (0, react.useState)(null);
|
|
4092
|
+
const [detail, setDetail] = (0, react.useState)(null);
|
|
4093
|
+
const pages = Math.max(1, Math.ceil(resources.length / PAGE_SIZE));
|
|
4094
|
+
const visible = resources.slice(page * PAGE_SIZE, page * PAGE_SIZE + PAGE_SIZE);
|
|
4095
|
+
const departmentOptions = actorRole === "DEPT_ADMIN" ? departments.filter((department) => department.code === actorDepartmentCode || department.path?.some((entry) => entry.code === actorDepartmentCode) === true) : departments;
|
|
4096
|
+
const keyOf = (row) => row.kind + ":" + String(row.id);
|
|
4097
|
+
const valueOf = (row) => draft[keyOf(row)] ?? {
|
|
4098
|
+
visibility: row.visibility,
|
|
4099
|
+
departmentCode: row.departmentCode ?? null
|
|
4100
|
+
};
|
|
4101
|
+
const visibilityLabel = (visibility) => visibility === "global" ? t("permGlobal") : visibility === "department" ? t("permDepartmentScope") : t("permPrivate");
|
|
4102
|
+
const statusLabel = (status) => status === "pending" ? t("permStatusPending") : status === "approved" ? t("permStatusApproved") : status === "rejected" ? t("permStatusRejected") : "—";
|
|
4103
|
+
const departmentName = (code) => code === null || code === void 0 ? "—" : departments.find((department) => department.code === code)?.name ?? code;
|
|
4104
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4105
|
+
className: PermissionCenterSection_module_css_default.panel,
|
|
4106
|
+
children: [
|
|
4107
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4108
|
+
className: PermissionCenterSection_module_css_default.tableScroll,
|
|
4109
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
4110
|
+
className: PermissionCenterSection_module_css_default.table,
|
|
4111
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
4112
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permType") }),
|
|
4113
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permName") }),
|
|
4114
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permOwner") }),
|
|
4115
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permScope") }),
|
|
4116
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permDepartment") }),
|
|
4117
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("permOperations") })
|
|
4118
|
+
] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: visible.map((row) => {
|
|
4119
|
+
const key = keyOf(row);
|
|
4120
|
+
const value = valueOf(row);
|
|
4121
|
+
const visibilityOptions = actorRole === "DEPT_ADMIN" ? ["department", "private"] : [
|
|
4122
|
+
"global",
|
|
4123
|
+
"department",
|
|
4124
|
+
"private"
|
|
4125
|
+
];
|
|
4126
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
4127
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.kind === "skill" ? t("permResourceSkill") : t("permResourceWorkflow") }),
|
|
4128
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: row.name }) }),
|
|
4129
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: ownerDisplay(row.ownerName) }),
|
|
4130
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchCombobox, {
|
|
4131
|
+
t,
|
|
4132
|
+
value: value.visibility,
|
|
4133
|
+
ariaLabel: t("permScope"),
|
|
4134
|
+
placeholder: t("permSelect"),
|
|
4135
|
+
disabled: !writable || busy,
|
|
4136
|
+
options: visibilityOptions.map((option) => ({
|
|
4137
|
+
value: option,
|
|
4138
|
+
label: visibilityLabel(option)
|
|
4139
|
+
})),
|
|
4140
|
+
onChange: (next) => {
|
|
4141
|
+
const visibility = next;
|
|
4142
|
+
setDraft((current) => ({
|
|
4143
|
+
...current,
|
|
4144
|
+
[key]: {
|
|
4145
|
+
visibility,
|
|
4146
|
+
departmentCode: visibility === "department" ? current[key]?.departmentCode ?? row.departmentCode ?? actorDepartmentCode ?? null : null
|
|
4147
|
+
}
|
|
4148
|
+
}));
|
|
4149
|
+
}
|
|
4150
|
+
}) }),
|
|
4151
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchCombobox, {
|
|
4152
|
+
t,
|
|
4153
|
+
value: value.departmentCode ?? "",
|
|
4154
|
+
ariaLabel: t("permDepartment"),
|
|
4155
|
+
placeholder: t("permNone"),
|
|
4156
|
+
disabled: !writable || busy || value.visibility !== "department" || actorRole === "DEPT_ADMIN",
|
|
4157
|
+
options: [{
|
|
4158
|
+
value: "",
|
|
4159
|
+
label: t("permNone")
|
|
4160
|
+
}, ...departmentOptions.map((department) => ({
|
|
4161
|
+
value: department.code,
|
|
4162
|
+
label: department.name,
|
|
4163
|
+
hint: department.code
|
|
4164
|
+
}))],
|
|
4165
|
+
onChange: (next) => {
|
|
4166
|
+
setDraft((current) => ({
|
|
4167
|
+
...current,
|
|
4168
|
+
[key]: {
|
|
4169
|
+
...valueOf(row),
|
|
4170
|
+
departmentCode: next === "" ? null : next
|
|
4171
|
+
}
|
|
4172
|
+
}));
|
|
4173
|
+
}
|
|
4174
|
+
}) }),
|
|
4175
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4176
|
+
className: PermissionCenterSection_module_css_default.rowActions,
|
|
4177
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4178
|
+
variant: "ghost",
|
|
4179
|
+
size: "sm",
|
|
4180
|
+
onClick: () => {
|
|
4181
|
+
setDetail(row);
|
|
4182
|
+
},
|
|
4183
|
+
children: t("permViewDetails")
|
|
4184
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4185
|
+
variant: "outline",
|
|
4186
|
+
size: "sm",
|
|
4187
|
+
disabled: !writable || busy,
|
|
4188
|
+
onClick: () => {
|
|
4189
|
+
setError(null);
|
|
4190
|
+
if (window.confirm(t("permSave") + "?")) onSave(row, value.visibility, value.departmentCode).then((result) => {
|
|
4191
|
+
if (!result.ok) setError(result.reason ?? t("permSaveFailed"));
|
|
4192
|
+
});
|
|
4193
|
+
},
|
|
4194
|
+
children: t("permSave")
|
|
4195
|
+
})]
|
|
4196
|
+
}) })
|
|
4197
|
+
] }, key);
|
|
4198
|
+
}) })]
|
|
4199
|
+
})
|
|
4200
|
+
}),
|
|
4201
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pager, {
|
|
4202
|
+
page,
|
|
4203
|
+
pages,
|
|
4204
|
+
onChange: setPage
|
|
4205
|
+
}),
|
|
4206
|
+
error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4207
|
+
className: PermissionCenterSection_module_css_default.error,
|
|
4208
|
+
role: "alert",
|
|
4209
|
+
children: error
|
|
4210
|
+
}),
|
|
4211
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
4212
|
+
open: detail !== null,
|
|
4213
|
+
onClose: () => {
|
|
4214
|
+
setDetail(null);
|
|
4215
|
+
},
|
|
4216
|
+
title: t("permDetailTitle"),
|
|
4217
|
+
closeLabel: t("permClose"),
|
|
4218
|
+
className: PermissionCenterSection_module_css_default.dialog,
|
|
4219
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4220
|
+
className: PermissionCenterSection_module_css_default.dialogFooter,
|
|
4221
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4222
|
+
variant: "outline",
|
|
4223
|
+
onClick: () => {
|
|
4224
|
+
setDetail(null);
|
|
4225
|
+
},
|
|
4226
|
+
children: t("permClose")
|
|
4227
|
+
})
|
|
4228
|
+
}),
|
|
4229
|
+
children: detail === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
|
|
4230
|
+
className: PermissionCenterSection_module_css_default.detailGrid,
|
|
4231
|
+
children: [
|
|
4232
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailType") }),
|
|
4233
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: detail.kind === "skill" ? t("permResourceSkill") : t("permResourceWorkflow") }),
|
|
4234
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailName") }),
|
|
4235
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: detail.name }),
|
|
4236
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailOwner") }),
|
|
4237
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: ownerDisplay(detail.ownerName) }),
|
|
4238
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailScope") }),
|
|
4239
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: visibilityLabel(detail.visibility) }),
|
|
4240
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailDepartment") }),
|
|
4241
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: departmentName(detail.departmentCode) }),
|
|
4242
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailStatus") }),
|
|
4243
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: statusLabel(detail.reviewStatus) }),
|
|
4244
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("permDetailUpdated") }),
|
|
4245
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: detail.updatedAt === void 0 ? "—" : new Date(detail.updatedAt).toLocaleString() })
|
|
4246
|
+
]
|
|
4247
|
+
})
|
|
4248
|
+
})
|
|
4249
|
+
]
|
|
4250
|
+
});
|
|
4251
|
+
}
|
|
4252
|
+
/** Accessible searchable combobox used for visibility and department pickers. */
|
|
4253
|
+
function SearchCombobox({ t, value, options, onChange, placeholder, ariaLabel, disabled = false }) {
|
|
4254
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
4255
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
4256
|
+
const [active, setActive] = (0, react.useState)(0);
|
|
4257
|
+
const rootRef = (0, react.useRef)(null);
|
|
4258
|
+
const inputRef = (0, react.useRef)(null);
|
|
4259
|
+
const selected = options.find((option) => option.value === value);
|
|
4260
|
+
const filtered = (0, react.useMemo)(() => filterComboboxOptions(options, query), [options, query]);
|
|
4261
|
+
(0, react.useEffect)(() => {
|
|
4262
|
+
if (!open) return;
|
|
4263
|
+
const close = (event) => {
|
|
4264
|
+
if (rootRef.current !== null && !rootRef.current.contains(event.target)) setOpen(false);
|
|
4265
|
+
};
|
|
4266
|
+
document.addEventListener("mousedown", close);
|
|
4267
|
+
inputRef.current?.focus();
|
|
4268
|
+
return () => {
|
|
4269
|
+
document.removeEventListener("mousedown", close);
|
|
4270
|
+
};
|
|
4271
|
+
}, [open]);
|
|
4272
|
+
(0, react.useEffect)(() => {
|
|
4273
|
+
if (!open) return;
|
|
4274
|
+
const index = filtered.findIndex((option) => option.value === value);
|
|
4275
|
+
setActive(index >= 0 ? index : 0);
|
|
4276
|
+
}, [
|
|
4277
|
+
open,
|
|
4278
|
+
filtered,
|
|
4279
|
+
value
|
|
4280
|
+
]);
|
|
4281
|
+
const choose = (next) => {
|
|
4282
|
+
onChange(next);
|
|
4283
|
+
setOpen(false);
|
|
4284
|
+
setQuery("");
|
|
4285
|
+
};
|
|
4286
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4287
|
+
className: PermissionCenterSection_module_css_default.combobox,
|
|
4288
|
+
ref: rootRef,
|
|
4289
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4290
|
+
type: "button",
|
|
4291
|
+
className: PermissionCenterSection_module_css_default.comboboxTrigger,
|
|
4292
|
+
"aria-label": ariaLabel,
|
|
4293
|
+
"aria-haspopup": "listbox",
|
|
4294
|
+
"aria-expanded": open,
|
|
4295
|
+
disabled,
|
|
4296
|
+
onClick: () => {
|
|
4297
|
+
setOpen((current) => !current);
|
|
4298
|
+
},
|
|
4299
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4300
|
+
className: selected === void 0 ? PermissionCenterSection_module_css_default.comboboxPlaceholder : void 0,
|
|
4301
|
+
children: selected?.label ?? placeholder
|
|
4302
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4303
|
+
"aria-hidden": "true",
|
|
4304
|
+
children: "⌄"
|
|
4305
|
+
})]
|
|
4306
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4307
|
+
className: PermissionCenterSection_module_css_default.comboboxMenu,
|
|
4308
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4309
|
+
ref: inputRef,
|
|
4310
|
+
className: PermissionCenterSection_module_css_default.comboboxSearch,
|
|
4311
|
+
value: query,
|
|
4312
|
+
placeholder: t("permSearch"),
|
|
4313
|
+
"aria-label": t("permSearch"),
|
|
4314
|
+
onChange: (event) => {
|
|
4315
|
+
setQuery(event.target.value);
|
|
4316
|
+
setActive(0);
|
|
4317
|
+
},
|
|
4318
|
+
onKeyDown: (event) => {
|
|
4319
|
+
if (event.key === "Escape") {
|
|
4320
|
+
setOpen(false);
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4323
|
+
if (event.key === "ArrowDown") {
|
|
4324
|
+
event.preventDefault();
|
|
4325
|
+
setActive((current) => clampActiveIndex(current + 1, filtered.length));
|
|
4326
|
+
}
|
|
4327
|
+
if (event.key === "ArrowUp") {
|
|
4328
|
+
event.preventDefault();
|
|
4329
|
+
setActive((current) => clampActiveIndex(current - 1, filtered.length));
|
|
4330
|
+
}
|
|
4331
|
+
if (event.key === "Enter") {
|
|
4332
|
+
event.preventDefault();
|
|
4333
|
+
const option = filtered[active];
|
|
4334
|
+
if (option !== void 0) choose(option.value);
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4337
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4338
|
+
className: PermissionCenterSection_module_css_default.comboboxList,
|
|
4339
|
+
role: "listbox",
|
|
4340
|
+
"aria-label": ariaLabel,
|
|
4341
|
+
children: filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
4342
|
+
className: PermissionCenterSection_module_css_default.comboboxEmpty,
|
|
4343
|
+
children: t("permNoMatches")
|
|
4344
|
+
}) : filtered.map((option, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
4345
|
+
role: "option",
|
|
4346
|
+
"aria-selected": option.value === value,
|
|
4347
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4348
|
+
type: "button",
|
|
4349
|
+
className: index === active ? PermissionCenterSection_module_css_default.comboboxOption + " " + PermissionCenterSection_module_css_default.comboboxOptionActive : PermissionCenterSection_module_css_default.comboboxOption,
|
|
4350
|
+
onMouseEnter: () => {
|
|
4351
|
+
setActive(index);
|
|
4352
|
+
},
|
|
4353
|
+
onClick: () => {
|
|
4354
|
+
choose(option.value);
|
|
4355
|
+
},
|
|
4356
|
+
children: [
|
|
4357
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4358
|
+
className: PermissionCenterSection_module_css_default.comboboxCheck,
|
|
4359
|
+
children: option.value === value ? "✓" : ""
|
|
4360
|
+
}),
|
|
4361
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: option.label }),
|
|
4362
|
+
option.hint === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4363
|
+
className: PermissionCenterSection_module_css_default.comboboxHint,
|
|
4364
|
+
children: option.hint
|
|
4365
|
+
})
|
|
4366
|
+
]
|
|
4367
|
+
})
|
|
4368
|
+
}, option.value))
|
|
4369
|
+
})]
|
|
4370
|
+
}) : null]
|
|
4371
|
+
});
|
|
4372
|
+
}
|
|
4373
|
+
function Pager({ page, pages, onChange }) {
|
|
4374
|
+
if (pages <= 1) return null;
|
|
4375
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4376
|
+
className: PermissionCenterSection_module_css_default.pager,
|
|
4377
|
+
children: [
|
|
4378
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4379
|
+
variant: "outline",
|
|
4380
|
+
size: "sm",
|
|
4381
|
+
disabled: page === 0,
|
|
4382
|
+
onClick: () => {
|
|
4383
|
+
onChange(page - 1);
|
|
4384
|
+
},
|
|
4385
|
+
children: "上一页"
|
|
4386
|
+
}),
|
|
4387
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4388
|
+
className: PermissionCenterSection_module_css_default.muted,
|
|
4389
|
+
children: [
|
|
4390
|
+
page + 1,
|
|
4391
|
+
" / ",
|
|
4392
|
+
pages
|
|
4393
|
+
]
|
|
4394
|
+
}),
|
|
4395
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4396
|
+
variant: "outline",
|
|
4397
|
+
size: "sm",
|
|
4398
|
+
disabled: page >= pages - 1,
|
|
4399
|
+
onClick: () => {
|
|
4400
|
+
onChange(page + 1);
|
|
4401
|
+
},
|
|
4402
|
+
children: "下一页"
|
|
4403
|
+
})
|
|
4404
|
+
]
|
|
4405
|
+
});
|
|
4406
|
+
}
|
|
4407
|
+
/** The four role labels, so the header does not show a backend code. */
|
|
4408
|
+
function roleLabel(role) {
|
|
4409
|
+
switch (role) {
|
|
4410
|
+
case "SUPER_ADMIN": return "超级管理员";
|
|
4411
|
+
case "ADMIN": return "管理员";
|
|
4412
|
+
case "DEPT_ADMIN": return "部门管理员";
|
|
4413
|
+
default: return "应用人员";
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4416
|
+
//#endregion
|
|
4417
|
+
//#region src/client/PermissionCenterIcon.tsx
|
|
4418
|
+
/**
|
|
4419
|
+
* The sidebar glyph for the permission centre.
|
|
4420
|
+
*
|
|
4421
|
+
* The entry is hidden for a caller the Host reports as an ordinary user. This is a navigation
|
|
4422
|
+
* affordance, not the authorization decision: every permission-centre endpoint re-checks the
|
|
4423
|
+
* caller, and the page itself refuses to render without a management role.
|
|
4424
|
+
*/
|
|
4425
|
+
function PermissionCenterIcon() {
|
|
4426
|
+
const [allowed, setAllowed] = (0, react.useState)(() => canSeeManagedSettings() ? true : void 0);
|
|
4427
|
+
(0, react.useEffect)(() => {
|
|
4428
|
+
let live = true;
|
|
4429
|
+
if (reviewDemoMode() || canSeeManagedSettings()) {
|
|
4430
|
+
setAllowed(true);
|
|
4431
|
+
return () => {
|
|
4432
|
+
live = false;
|
|
4433
|
+
};
|
|
4434
|
+
}
|
|
4435
|
+
fetchReviewContext().then((context) => {
|
|
4436
|
+
if (!live) return;
|
|
4437
|
+
setAllowed(context !== null && context.role !== "APP_USER");
|
|
4438
|
+
}).catch(() => {
|
|
4439
|
+
if (live) setAllowed(false);
|
|
4440
|
+
});
|
|
4441
|
+
return () => {
|
|
4442
|
+
live = false;
|
|
4443
|
+
};
|
|
4444
|
+
}, []);
|
|
4445
|
+
if (allowed !== true) return null;
|
|
4446
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4447
|
+
width: "18",
|
|
4448
|
+
height: "18",
|
|
4449
|
+
viewBox: "0 0 18 18",
|
|
4450
|
+
fill: "none",
|
|
4451
|
+
"aria-hidden": "true",
|
|
4452
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
4453
|
+
d: "M9 2.2 14.5 4v4.4c0 3.1-2.1 5.6-5.5 7.4-3.4-1.8-5.5-4.3-5.5-7.4V4L9 2.2Z",
|
|
4454
|
+
stroke: "currentColor",
|
|
4455
|
+
strokeWidth: "1.2",
|
|
4456
|
+
strokeLinejoin: "round"
|
|
4457
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
4458
|
+
d: "m6.7 8.8 1.6 1.6 3-3.1",
|
|
4459
|
+
stroke: "currentColor",
|
|
4460
|
+
strokeWidth: "1.2",
|
|
4461
|
+
strokeLinecap: "round",
|
|
4462
|
+
strokeLinejoin: "round"
|
|
4463
|
+
})]
|
|
4464
|
+
});
|
|
4465
|
+
}
|
|
4466
|
+
//#endregion
|
|
4467
|
+
//#region src/client/workspace-upgrade-client.ts
|
|
4468
|
+
/** Same-origin calls for the workspace image upgrade banner. */
|
|
4469
|
+
/** Route the plugin's Host half serves for upgrade detection and start. */
|
|
4470
|
+
const WORKSPACE_UPGRADE_PATH = "/integrations/dataops/workspace-upgrade";
|
|
4471
|
+
/** Narrow an unknown response into the status contract. */
|
|
4472
|
+
function asStatus(value) {
|
|
4473
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
4474
|
+
const record = value;
|
|
4475
|
+
const imageStatus = record.imageStatus;
|
|
4476
|
+
if (imageStatus !== "CURRENT" && imageStatus !== "UPDATE_AVAILABLE" && imageStatus !== "NOT_RUNNING" && imageStatus !== "UNKNOWN") return null;
|
|
4477
|
+
const sessions = Array.isArray(record.activeSessions) ? record.activeSessions.flatMap((entry) => {
|
|
4478
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
|
|
4479
|
+
const session = entry;
|
|
4480
|
+
if (typeof session.sessionId !== "string" || session.sessionId.trim() === "") return [];
|
|
4481
|
+
return [{
|
|
4482
|
+
sessionId: session.sessionId.trim(),
|
|
4483
|
+
...typeof session.title === "string" && session.title.trim() !== "" ? { title: session.title.trim() } : {},
|
|
4484
|
+
...typeof session.cwd === "string" && session.cwd.trim() !== "" ? { cwd: session.cwd.trim() } : {}
|
|
4485
|
+
}];
|
|
4486
|
+
}) : [];
|
|
4487
|
+
return {
|
|
4488
|
+
imageStatus,
|
|
4489
|
+
configuredImage: typeof record.configuredImage === "string" ? record.configuredImage : "",
|
|
4490
|
+
activeSessions: sessions
|
|
4491
|
+
};
|
|
4492
|
+
}
|
|
4493
|
+
/**
|
|
4494
|
+
* Read the workspace upgrade preview.
|
|
4495
|
+
* @returns the status, or null when the Host route is not ready or not connected.
|
|
4496
|
+
*/
|
|
4497
|
+
async function fetchWorkspaceUpgrade() {
|
|
4498
|
+
const response = await fetch(WORKSPACE_UPGRADE_PATH, {
|
|
4499
|
+
credentials: "same-origin",
|
|
4500
|
+
headers: { accept: "application/json" }
|
|
4501
|
+
});
|
|
4502
|
+
if (response.status === 503 || response.status === 401) return null;
|
|
4503
|
+
if (!response.ok) throw new Error(`workspace upgrade preview failed with HTTP ${String(response.status)}`);
|
|
4504
|
+
return asStatus(await response.json());
|
|
4505
|
+
}
|
|
4506
|
+
/**
|
|
4507
|
+
* Ask the Host to snapshot sessions and recreate the workspace container.
|
|
4508
|
+
* @returns the accepted hand-off summary.
|
|
4509
|
+
*/
|
|
4510
|
+
async function requestWorkspaceUpgrade() {
|
|
4511
|
+
const response = await fetch(WORKSPACE_UPGRADE_PATH, {
|
|
4512
|
+
method: "POST",
|
|
4513
|
+
credentials: "same-origin",
|
|
4514
|
+
headers: {
|
|
4515
|
+
"content-type": "application/json",
|
|
4516
|
+
accept: "application/json"
|
|
4517
|
+
},
|
|
4518
|
+
body: JSON.stringify({ force: true })
|
|
4519
|
+
});
|
|
4520
|
+
const body = await response.json().catch(() => null);
|
|
4521
|
+
if (!response.ok) {
|
|
4522
|
+
const message = typeof body?.error === "string" ? body.error : `workspace upgrade failed with HTTP ${String(response.status)}`;
|
|
4523
|
+
throw new Error(message);
|
|
4524
|
+
}
|
|
4525
|
+
return {
|
|
4526
|
+
accepted: body?.accepted === true,
|
|
4527
|
+
upgradeId: typeof body?.upgradeId === "string" ? body.upgradeId : "",
|
|
4528
|
+
sessions: typeof body?.sessions === "number" ? body.sessions : 0
|
|
4529
|
+
};
|
|
4530
|
+
}
|
|
4531
|
+
//#endregion
|
|
4532
|
+
//#region \0dsh-css:/root/projects/dsh-plugins-plus/packages/dataops-managed/src/client/WorkspaceUpgradePrompt.module.css.mjs
|
|
4533
|
+
const css = ".razUfG_banner{z-index:40;border:.5px solid var(--dsw-alias-settings-card-stroke);border-radius:var(--dsw-radius-xl);background:var(--dsw-alias-settings-card-fill);width:min(680px,100vw - 32px);color:var(--dsw-alias-label-primary);justify-content:space-between;align-items:center;gap:20px;padding:10px 12px 10px 16px;display:flex;position:fixed;top:16px;left:50%;transform:translate(-50%);box-shadow:0 8px 28px #0000001f}.razUfG_copy{flex-direction:column;gap:2px;min-width:0;display:flex}.razUfG_title{font-size:13px;font-weight:600;line-height:22px}.razUfG_body{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.razUfG_actions{flex:none;align-items:center;gap:4px;display:flex}.razUfG_dialogBody{flex-direction:column;gap:12px;min-width:min(420px,70vw);display:flex}.razUfG_dialogText{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;line-height:22px}.razUfG_sessionList{border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-layer-1);flex-direction:column;gap:4px;max-height:220px;margin:0;padding:8px 10px;list-style:none;display:flex;overflow:auto}.razUfG_sessionRow{flex-direction:column;gap:1px;min-width:0;display:flex}.razUfG_sessionTitle{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px;overflow:hidden}.razUfG_sessionPath{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;line-height:16px;overflow:hidden}.razUfG_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}";
|
|
4534
|
+
const tagId = "@sparkelf/dsh-dataops-managed/WorkspaceUpgradePrompt.module.css";
|
|
4535
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
4536
|
+
const tag = document.createElement("style");
|
|
4537
|
+
tag.dataset.plugin = "@sparkelf/dsh-dataops-managed";
|
|
4538
|
+
tag.dataset.pluginCss = tagId;
|
|
4539
|
+
tag.textContent = css;
|
|
4540
|
+
document.head.appendChild(tag);
|
|
4541
|
+
}
|
|
4542
|
+
var WorkspaceUpgradePrompt_module_css_default = {
|
|
4543
|
+
"sessionPath": "razUfG_sessionPath",
|
|
4544
|
+
"banner": "razUfG_banner",
|
|
4545
|
+
"copy": "razUfG_copy",
|
|
4546
|
+
"actions": "razUfG_actions",
|
|
4547
|
+
"sessionRow": "razUfG_sessionRow",
|
|
4548
|
+
"dialogText": "razUfG_dialogText",
|
|
4549
|
+
"sessionTitle": "razUfG_sessionTitle",
|
|
4550
|
+
"title": "razUfG_title",
|
|
4551
|
+
"sessionList": "razUfG_sessionList",
|
|
4552
|
+
"error": "razUfG_error",
|
|
4553
|
+
"dialogBody": "razUfG_dialogBody",
|
|
4554
|
+
"body": "razUfG_body"
|
|
4555
|
+
};
|
|
4556
|
+
//#endregion
|
|
4557
|
+
//#region src/client/WorkspaceUpgradePrompt.tsx
|
|
4558
|
+
/**
|
|
4559
|
+
* Workspace image upgrade banner and confirmation dialog.
|
|
4560
|
+
*
|
|
4561
|
+
* The banner is deliberately quiet: it tells the user a newer workspace image exists and
|
|
4562
|
+
* leaves the current session usable. Only the explicit upgrade action opens the dialog
|
|
4563
|
+
* that can interrupt running sessions.
|
|
4564
|
+
*
|
|
4565
|
+
* @module WorkspaceUpgradePrompt
|
|
4566
|
+
*/
|
|
4567
|
+
/** How often to re-read the image status while the page is open. */
|
|
4568
|
+
const POLL_MS = 3e4;
|
|
4569
|
+
/** Wait before a reload so the restarted workspace has a chance to come back. */
|
|
4570
|
+
const RELOAD_DELAY_MS = 12e3;
|
|
4571
|
+
/**
|
|
4572
|
+
* Render the banner and its upgrade dialog.
|
|
4573
|
+
* @param props.t - localized copy resolver.
|
|
4574
|
+
* @returns the banner while an image update is available, otherwise null.
|
|
4575
|
+
*/
|
|
4576
|
+
function WorkspaceUpgradePrompt(props) {
|
|
4577
|
+
const { t } = props;
|
|
4578
|
+
const [status, setStatus] = (0, react.useState)(null);
|
|
4579
|
+
const [dismissed, setDismissed] = (0, react.useState)(false);
|
|
4580
|
+
const [dialogOpen, setDialogOpen] = (0, react.useState)(false);
|
|
4581
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
4582
|
+
const [notice, setNotice] = (0, react.useState)("");
|
|
4583
|
+
const [error, setError] = (0, react.useState)("");
|
|
4584
|
+
const refresh = (0, react.useCallback)(async () => {
|
|
4585
|
+
try {
|
|
4586
|
+
const next = await fetchWorkspaceUpgrade();
|
|
4587
|
+
if (next !== null) setStatus(next);
|
|
4588
|
+
} catch {}
|
|
4589
|
+
}, []);
|
|
4590
|
+
(0, react.useEffect)(() => {
|
|
4591
|
+
let cancelled = false;
|
|
4592
|
+
const tick = async () => {
|
|
4593
|
+
if (cancelled) return;
|
|
4594
|
+
await refresh();
|
|
4595
|
+
};
|
|
4596
|
+
tick();
|
|
4597
|
+
const timer = setInterval(() => {
|
|
4598
|
+
tick();
|
|
4599
|
+
}, POLL_MS);
|
|
4600
|
+
return () => {
|
|
4601
|
+
cancelled = true;
|
|
4602
|
+
clearInterval(timer);
|
|
4603
|
+
};
|
|
4604
|
+
}, [refresh]);
|
|
4605
|
+
const available = status?.imageStatus === "UPDATE_AVAILABLE";
|
|
4606
|
+
const sessionCount = status?.activeSessions.length ?? 0;
|
|
4607
|
+
const warning = (0, react.useMemo)(() => t("upgradeSessionsWarning").replace("{count}", String(sessionCount)), [sessionCount, t]);
|
|
4608
|
+
const openDialog = (0, react.useCallback)(() => {
|
|
4609
|
+
setError("");
|
|
4610
|
+
setDialogOpen(true);
|
|
4611
|
+
}, []);
|
|
4612
|
+
const closeDialog = (0, react.useCallback)(() => {
|
|
4613
|
+
if (busy) return;
|
|
4614
|
+
setDialogOpen(false);
|
|
4615
|
+
}, [busy]);
|
|
4616
|
+
const upgrade = (0, react.useCallback)(async () => {
|
|
4617
|
+
setBusy(true);
|
|
4618
|
+
setError("");
|
|
4619
|
+
try {
|
|
4620
|
+
await requestWorkspaceUpgrade();
|
|
4621
|
+
setDialogOpen(false);
|
|
4622
|
+
setNotice(t("upgradeStarted"));
|
|
4623
|
+
setDismissed(true);
|
|
4624
|
+
window.setTimeout(() => {
|
|
4625
|
+
window.location.reload();
|
|
4626
|
+
}, RELOAD_DELAY_MS);
|
|
4627
|
+
} catch (cause) {
|
|
4628
|
+
if (cause instanceof TypeError) {
|
|
4629
|
+
setDialogOpen(false);
|
|
4630
|
+
setNotice(t("upgradeStarted"));
|
|
4631
|
+
setDismissed(true);
|
|
4632
|
+
window.setTimeout(() => {
|
|
4633
|
+
window.location.reload();
|
|
4634
|
+
}, RELOAD_DELAY_MS);
|
|
4635
|
+
return;
|
|
4636
|
+
}
|
|
4637
|
+
setError(cause instanceof Error ? cause.message : t("upgradeFailed"));
|
|
4638
|
+
} finally {
|
|
4639
|
+
setBusy(false);
|
|
4640
|
+
}
|
|
4641
|
+
}, [t]);
|
|
4642
|
+
if (notice !== "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4643
|
+
className: WorkspaceUpgradePrompt_module_css_default.banner,
|
|
4644
|
+
role: "status",
|
|
4645
|
+
"aria-live": "polite",
|
|
4646
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4647
|
+
className: WorkspaceUpgradePrompt_module_css_default.title,
|
|
4648
|
+
children: notice
|
|
4649
|
+
})
|
|
4650
|
+
});
|
|
4651
|
+
if (!available || dismissed) return null;
|
|
4652
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4653
|
+
className: WorkspaceUpgradePrompt_module_css_default.banner,
|
|
4654
|
+
role: "status",
|
|
4655
|
+
"aria-live": "polite",
|
|
4656
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4657
|
+
className: WorkspaceUpgradePrompt_module_css_default.copy,
|
|
4658
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
4659
|
+
className: WorkspaceUpgradePrompt_module_css_default.title,
|
|
4660
|
+
children: t("upgradeAvailableTitle")
|
|
4661
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4662
|
+
className: WorkspaceUpgradePrompt_module_css_default.body,
|
|
4663
|
+
children: t("upgradeAvailableBody")
|
|
4664
|
+
})]
|
|
4665
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4666
|
+
className: WorkspaceUpgradePrompt_module_css_default.actions,
|
|
4667
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4668
|
+
variant: "primary",
|
|
4669
|
+
size: "sm",
|
|
4670
|
+
onClick: openDialog,
|
|
4671
|
+
children: t("upgradeAction")
|
|
4672
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4673
|
+
variant: "ghost",
|
|
4674
|
+
size: "sm",
|
|
4675
|
+
onClick: () => {
|
|
4676
|
+
setDismissed(true);
|
|
4677
|
+
},
|
|
4678
|
+
children: t("upgradeLater")
|
|
4679
|
+
})]
|
|
4680
|
+
})]
|
|
4681
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
4682
|
+
open: dialogOpen,
|
|
4683
|
+
onClose: closeDialog,
|
|
4684
|
+
title: t("upgradeDialogTitle"),
|
|
4685
|
+
description: t("upgradeDialogBody"),
|
|
4686
|
+
closeLabel: t("upgradeCancel"),
|
|
4687
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4688
|
+
variant: "ghost",
|
|
4689
|
+
onClick: closeDialog,
|
|
4690
|
+
disabled: busy,
|
|
4691
|
+
children: t("upgradeCancel")
|
|
4692
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4693
|
+
variant: "primary",
|
|
4694
|
+
onClick: () => {
|
|
4695
|
+
upgrade();
|
|
4696
|
+
},
|
|
4697
|
+
disabled: busy,
|
|
4698
|
+
children: busy ? t("upgradeBusy") : sessionCount === 0 ? t("upgradeAction") : t("upgradeConfirm")
|
|
4699
|
+
})] }),
|
|
4700
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4701
|
+
className: WorkspaceUpgradePrompt_module_css_default.dialogBody,
|
|
4702
|
+
children: [
|
|
4703
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4704
|
+
className: WorkspaceUpgradePrompt_module_css_default.dialogText,
|
|
4705
|
+
children: sessionCount === 0 ? t("upgradeNoSessions") : warning
|
|
4706
|
+
}),
|
|
4707
|
+
sessionCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4708
|
+
className: WorkspaceUpgradePrompt_module_css_default.sessionList,
|
|
4709
|
+
children: status?.activeSessions.map((session) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
4710
|
+
className: WorkspaceUpgradePrompt_module_css_default.sessionRow,
|
|
4711
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4712
|
+
className: WorkspaceUpgradePrompt_module_css_default.sessionTitle,
|
|
4713
|
+
children: session.title ?? session.sessionId
|
|
4714
|
+
}), session.cwd ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4715
|
+
className: WorkspaceUpgradePrompt_module_css_default.sessionPath,
|
|
4716
|
+
children: session.cwd
|
|
4717
|
+
}) : null]
|
|
4718
|
+
}, session.sessionId))
|
|
4719
|
+
}) : null,
|
|
4720
|
+
error !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4721
|
+
className: WorkspaceUpgradePrompt_module_css_default.error,
|
|
4722
|
+
role: "alert",
|
|
4723
|
+
children: error
|
|
4724
|
+
}) : null
|
|
4725
|
+
]
|
|
4726
|
+
})
|
|
4727
|
+
})] });
|
|
4728
|
+
}
|
|
4729
|
+
//#endregion
|
|
4730
|
+
//#region src/client/locales.ts
|
|
4731
|
+
/** English managed DataOps Settings copy. */
|
|
4732
|
+
const en = {
|
|
4733
|
+
nav: "DataOps",
|
|
4734
|
+
title: "DataOps",
|
|
4735
|
+
managed: "Connected to DataOps",
|
|
4736
|
+
unavailable: "DataOps is not connected.",
|
|
4737
|
+
loadFailed: "Loading failed. Try again.",
|
|
4738
|
+
brandingTitle: "Wanxiang branding",
|
|
4739
|
+
brandingToggle: "Wanxiang branding",
|
|
4740
|
+
limitsTitle: "File size limits",
|
|
4741
|
+
limitsFileReadLabel: "Largest file to read (MiB)",
|
|
4742
|
+
limitsUploadLabel: "Largest file to upload (MiB)",
|
|
4743
|
+
limitsInvalid: "Enter a size in MiB.",
|
|
4744
|
+
limitsSave: "Save",
|
|
4745
|
+
limitsSaving: "Saving…",
|
|
4746
|
+
timeoutTitle: "DataOps query timeout",
|
|
4747
|
+
timeoutLabel: "Longest single query (seconds)",
|
|
4748
|
+
timeoutInvalid: "Enter 1 to 3600 seconds.",
|
|
4749
|
+
defaultsTitle: "Default configuration",
|
|
4750
|
+
defaultsPublish: "Publish",
|
|
4751
|
+
defaultsPublishing: "Publishing…",
|
|
4752
|
+
defaultsPublishedAt: "Last published",
|
|
4753
|
+
defaultsNeverPublished: "Not published yet",
|
|
4754
|
+
defaultsNotAdmin: "Administrators only",
|
|
4755
|
+
plazaTitle: "Skill plaza",
|
|
4756
|
+
plazaAllTags: "All tags",
|
|
4757
|
+
plazaMine: "Shared by me",
|
|
4758
|
+
plazaShowDetail: "Details",
|
|
4759
|
+
plazaClose: "Close",
|
|
4760
|
+
plazaDetailUpdated: "Updated",
|
|
4761
|
+
plazaDetailBody: "Skill body",
|
|
4762
|
+
plazaInstall: "Install",
|
|
4763
|
+
plazaInstalled: "Installed. It is now in your skill list.",
|
|
4764
|
+
plazaUnshare: "Stop sharing",
|
|
4765
|
+
plazaUnshared: "Removed from the plaza. Users who installed it keep their copy.",
|
|
4766
|
+
plazaEmpty: "No skills have been shared yet.",
|
|
4767
|
+
plazaUnavailable: "The skill plaza is available once this workspace is connected to DataOps.",
|
|
4768
|
+
plazaLoadFailed: "The skill plaza could not be loaded. Try again.",
|
|
4769
|
+
plazaActionFailed: "The action could not be completed. Try again.",
|
|
4770
|
+
plazaRetry: "Try again",
|
|
4771
|
+
plazaRefresh: "Refresh",
|
|
4772
|
+
plazaTabApproved: "Approved",
|
|
4773
|
+
plazaTabPending: "Pending",
|
|
4774
|
+
plazaTabAll: "All",
|
|
4775
|
+
plazaSharedTag: "Shared",
|
|
4776
|
+
plazaReview: "Review",
|
|
4777
|
+
plazaPendingEmpty: "No skills are waiting for review.",
|
|
4778
|
+
plazaAuditTitle: "Review skill",
|
|
4779
|
+
plazaAuditSource: "Source",
|
|
4780
|
+
plazaAuditVisibility: "Visibility",
|
|
4781
|
+
plazaRiskTitle: "AI risk report",
|
|
4782
|
+
plazaRiskScan: "Run AI scan",
|
|
4783
|
+
plazaRiskScanning: "Scanning…",
|
|
4784
|
+
plazaRiskNone: "No AI risk report yet.",
|
|
4785
|
+
plazaRiskScore: "Risk score",
|
|
4786
|
+
plazaRiskFindings: "Findings",
|
|
4787
|
+
plazaRiskFailed: "The AI scan failed; review manually.",
|
|
4788
|
+
plazaApprove: "Approve",
|
|
4789
|
+
plazaReject: "Reject",
|
|
4790
|
+
plazaReviewNote: "Review note (required to reject)",
|
|
4791
|
+
plazaReviewNotePlaceholder: "Explain the reason or what to change…",
|
|
4792
|
+
plazaReviewDone: "Review saved.",
|
|
4793
|
+
plazaReviewFailed: "The review could not be saved. Try again.",
|
|
4794
|
+
plazaDemo: "Demo data",
|
|
4795
|
+
plazaGlobal: "Global",
|
|
4796
|
+
plazaPrivate: "Private",
|
|
4797
|
+
plazaDeptSuffix: " department",
|
|
4798
|
+
reviewSettingsTitle: "Review settings",
|
|
4799
|
+
reviewSettingsSkill: "Skill plaza review",
|
|
4800
|
+
reviewSettingsWorkflow: "Workflow plaza review",
|
|
4801
|
+
reviewSettingsKb: "Knowledge base review",
|
|
4802
|
+
reviewSettingsHint: "When on, submitted content stays private until an administrator approves it.",
|
|
4803
|
+
reviewSettingsSave: "Save",
|
|
4804
|
+
reviewSettingsSaved: "Review settings saved.",
|
|
4805
|
+
reviewSettingsFailed: "Could not save. Try again.",
|
|
4806
|
+
reviewSettingsWaiting: "Review settings API is waiting for the backend (task-39).",
|
|
4807
|
+
permNav: "Permissions",
|
|
4808
|
+
permTitle: "Permission centre",
|
|
4809
|
+
permTabRoles: "Role permissions",
|
|
4810
|
+
permTabMembers: "Member roles",
|
|
4811
|
+
permTabDepartments: "Departments",
|
|
4812
|
+
permTabResources: "Resource scope",
|
|
4813
|
+
permRoleMatrix: "Role permission matrix",
|
|
4814
|
+
permPermissionModule: "Module",
|
|
4815
|
+
permPermissionPoint: "Permission",
|
|
4816
|
+
permReadOnly: "Read only",
|
|
4817
|
+
permSave: "Save",
|
|
4818
|
+
permSaved: "Saved.",
|
|
4819
|
+
permSaveFailed: "Could not save. Try again.",
|
|
4820
|
+
permSuperAdminOnly: "Only a super administrator can change this.",
|
|
4821
|
+
permMember: "Member",
|
|
4822
|
+
permDepartment: "Department",
|
|
4823
|
+
permRole: "Role",
|
|
4824
|
+
permDeptAdmin: "Department administrator",
|
|
4825
|
+
permScope: "Visibility",
|
|
4826
|
+
permGlobal: "Global",
|
|
4827
|
+
permDepartmentScope: "Department",
|
|
4828
|
+
permPrivate: "Private",
|
|
4829
|
+
permNoAccess: "You do not have access to the permission centre.",
|
|
4830
|
+
permWaiting: "The permission-centre API is waiting for the backend (task-37).",
|
|
4831
|
+
permResourceSkill: "Skill",
|
|
4832
|
+
permResourceWorkflow: "Workflow",
|
|
4833
|
+
permResourceKb: "Knowledge base",
|
|
4834
|
+
permType: "Type",
|
|
4835
|
+
permName: "Name",
|
|
4836
|
+
permOwner: "Owner",
|
|
4837
|
+
permOperations: "Actions",
|
|
4838
|
+
permViewDetails: "View details",
|
|
4839
|
+
permDetailTitle: "Resource details",
|
|
4840
|
+
permDetailType: "Type",
|
|
4841
|
+
permDetailName: "Name",
|
|
4842
|
+
permDetailOwner: "Owner",
|
|
4843
|
+
permDetailScope: "Visibility",
|
|
4844
|
+
permDetailDepartment: "Department",
|
|
4845
|
+
permDetailStatus: "Review status",
|
|
4846
|
+
permDetailUpdated: "Updated",
|
|
4847
|
+
permNone: "None",
|
|
4848
|
+
permSearch: "Search…",
|
|
4849
|
+
permNoMatches: "No matches",
|
|
4850
|
+
permClose: "Close",
|
|
4851
|
+
permSelect: "Select…",
|
|
4852
|
+
permStatusPending: "Pending review",
|
|
4853
|
+
permStatusApproved: "Approved",
|
|
4854
|
+
permStatusRejected: "Rejected",
|
|
4855
|
+
permDemo: "Demo data",
|
|
4856
|
+
reloginTitle: "Sign in to DataOps again",
|
|
4857
|
+
reloginBody: "This workspace can no longer reach DataOps: the sign-in for this session has ended, so settings, shared models and the skill plaza cannot load. Sign in again in the DataOps window, then reload this page.",
|
|
4858
|
+
reloginAction: "Reload",
|
|
4859
|
+
reloginDismiss: "Later",
|
|
4860
|
+
upgradeAvailableTitle: "A new workspace version is available",
|
|
4861
|
+
upgradeAvailableBody: "Restart the workspace to apply it.",
|
|
4862
|
+
upgradeAction: "Upgrade",
|
|
4863
|
+
upgradeLater: "Later",
|
|
4864
|
+
upgradeDialogTitle: "Upgrade workspace",
|
|
4865
|
+
upgradeDialogBody: "The workspace will restart and briefly disconnect. Your files and sessions are kept.",
|
|
4866
|
+
upgradeNoSessions: "No sessions are running. The workspace will restart now.",
|
|
4867
|
+
upgradeSessionsWarning: "{count} running session(s) will be interrupted. After the upgrade, each one is resumed and sent “继续”.",
|
|
4868
|
+
upgradeCancel: "Cancel",
|
|
4869
|
+
upgradeConfirm: "Stop tasks and upgrade",
|
|
4870
|
+
upgradeBusy: "Upgrading workspace…",
|
|
4871
|
+
upgradeStarted: "Upgrade started. Reload this page when the workspace reconnects.",
|
|
4872
|
+
upgradeFailed: "The workspace upgrade could not be started. Try again.",
|
|
4873
|
+
upgradeRetry: "Retry"
|
|
4874
|
+
};
|
|
4875
|
+
/** Chinese managed DataOps Settings copy. */
|
|
4876
|
+
const zh = {
|
|
1387
4877
|
nav: "DataOps",
|
|
1388
4878
|
title: "DataOps",
|
|
1389
4879
|
managed: "已连接 DataOps",
|
|
@@ -1421,18 +4911,142 @@ window.__ModuleLoader__.load({
|
|
|
1421
4911
|
plazaUnavailable: "工作区接入 DataOps 后即可使用技能广场。",
|
|
1422
4912
|
plazaLoadFailed: "技能广场加载失败,请重试。",
|
|
1423
4913
|
plazaActionFailed: "操作未完成,请重试。",
|
|
1424
|
-
plazaRetry: "重试"
|
|
4914
|
+
plazaRetry: "重试",
|
|
4915
|
+
plazaRefresh: "刷新",
|
|
4916
|
+
plazaTabApproved: "已审核",
|
|
4917
|
+
plazaTabPending: "未审核",
|
|
4918
|
+
plazaTabAll: "全部",
|
|
4919
|
+
plazaSharedTag: "已共享",
|
|
4920
|
+
plazaReview: "审核",
|
|
4921
|
+
plazaPendingEmpty: "没有待审核的技能。",
|
|
4922
|
+
plazaAuditTitle: "审核技能",
|
|
4923
|
+
plazaAuditSource: "来源",
|
|
4924
|
+
plazaAuditVisibility: "可见范围",
|
|
4925
|
+
plazaRiskTitle: "AI 风险报告",
|
|
4926
|
+
plazaRiskScan: "生成风险报告",
|
|
4927
|
+
plazaRiskScanning: "分析中…",
|
|
4928
|
+
plazaRiskNone: "尚未生成 AI 风险报告。",
|
|
4929
|
+
plazaRiskScore: "风险分",
|
|
4930
|
+
plazaRiskFindings: "发现",
|
|
4931
|
+
plazaRiskFailed: "风险报告生成失败,可人工审核。",
|
|
4932
|
+
plazaApprove: "通过",
|
|
4933
|
+
plazaReject: "驳回",
|
|
4934
|
+
plazaReviewNote: "审核意见(驳回必填)",
|
|
4935
|
+
plazaReviewNotePlaceholder: "说明原因或修改建议…",
|
|
4936
|
+
plazaReviewDone: "审核结果已保存。",
|
|
4937
|
+
plazaReviewFailed: "审核未完成,请重试。",
|
|
4938
|
+
plazaDemo: "演示数据",
|
|
4939
|
+
plazaGlobal: "全局",
|
|
4940
|
+
plazaPrivate: "私有",
|
|
4941
|
+
plazaDeptSuffix: "部门专属",
|
|
4942
|
+
reviewSettingsTitle: "审核设置",
|
|
4943
|
+
reviewSettingsSkill: "技能广场审核",
|
|
4944
|
+
reviewSettingsWorkflow: "工作流广场审核",
|
|
4945
|
+
reviewSettingsKb: "知识库审核",
|
|
4946
|
+
reviewSettingsHint: "开启后,提交内容需管理员审核通过才公开展示。",
|
|
4947
|
+
reviewSettingsSave: "保存",
|
|
4948
|
+
reviewSettingsSaved: "审核设置已保存。",
|
|
4949
|
+
reviewSettingsFailed: "保存失败,请重试。",
|
|
4950
|
+
reviewSettingsWaiting: "审核设置 API 待后端联调(task-39)。",
|
|
4951
|
+
permNav: "权限",
|
|
4952
|
+
permTitle: "权限中心",
|
|
4953
|
+
permTabRoles: "角色权限",
|
|
4954
|
+
permTabMembers: "成员角色",
|
|
4955
|
+
permTabDepartments: "部门",
|
|
4956
|
+
permTabResources: "资源范围",
|
|
4957
|
+
permRoleMatrix: "角色权限矩阵",
|
|
4958
|
+
permPermissionModule: "模块",
|
|
4959
|
+
permPermissionPoint: "权限点",
|
|
4960
|
+
permReadOnly: "只读",
|
|
4961
|
+
permSave: "保存",
|
|
4962
|
+
permSaved: "已保存。",
|
|
4963
|
+
permSaveFailed: "保存失败,请重试。",
|
|
4964
|
+
permSuperAdminOnly: "仅超级管理员可修改。",
|
|
4965
|
+
permMember: "成员",
|
|
4966
|
+
permDepartment: "部门",
|
|
4967
|
+
permRole: "角色",
|
|
4968
|
+
permDeptAdmin: "部门管理员",
|
|
4969
|
+
permScope: "可见范围",
|
|
4970
|
+
permGlobal: "全局",
|
|
4971
|
+
permDepartmentScope: "部门专属",
|
|
4972
|
+
permPrivate: "私有",
|
|
4973
|
+
permNoAccess: "无权限访问权限中心。",
|
|
4974
|
+
permWaiting: "权限中心 API 待后端联调(task-37)。",
|
|
4975
|
+
permResourceSkill: "技能",
|
|
4976
|
+
permResourceWorkflow: "工作流",
|
|
4977
|
+
permResourceKb: "知识库",
|
|
4978
|
+
permType: "类型",
|
|
4979
|
+
permName: "名称",
|
|
4980
|
+
permOwner: "所有者",
|
|
4981
|
+
permOperations: "操作",
|
|
4982
|
+
permViewDetails: "查看详情",
|
|
4983
|
+
permDetailTitle: "资源详情",
|
|
4984
|
+
permDetailType: "类型",
|
|
4985
|
+
permDetailName: "名称",
|
|
4986
|
+
permDetailOwner: "所有者",
|
|
4987
|
+
permDetailScope: "可见范围",
|
|
4988
|
+
permDetailDepartment: "部门",
|
|
4989
|
+
permDetailStatus: "审核状态",
|
|
4990
|
+
permDetailUpdated: "更新时间",
|
|
4991
|
+
permNone: "无",
|
|
4992
|
+
permSearch: "搜索…",
|
|
4993
|
+
permNoMatches: "无匹配项",
|
|
4994
|
+
permClose: "关闭",
|
|
4995
|
+
permSelect: "请选择…",
|
|
4996
|
+
permStatusPending: "待审核",
|
|
4997
|
+
permStatusApproved: "已审核",
|
|
4998
|
+
permStatusRejected: "已拒绝",
|
|
4999
|
+
permDemo: "演示数据",
|
|
5000
|
+
reloginTitle: "请重新登录 DataOps",
|
|
5001
|
+
reloginBody: "该工作区已无法访问 DataOps:本次登录会话已过期,因此设置、共享模型与技能广场都无法加载。请在 DataOps 页面重新登录,然后重新加载本页。",
|
|
5002
|
+
reloginAction: "重新加载",
|
|
5003
|
+
reloginDismiss: "稍后再说",
|
|
5004
|
+
upgradeAvailableTitle: "工作区有新版本可用",
|
|
5005
|
+
upgradeAvailableBody: "重启工作区后生效。",
|
|
5006
|
+
upgradeAction: "立即升级",
|
|
5007
|
+
upgradeLater: "稍后",
|
|
5008
|
+
upgradeDialogTitle: "升级工作区",
|
|
5009
|
+
upgradeDialogBody: "工作区会重启并短暂断开。文件和历史会话会保留。",
|
|
5010
|
+
upgradeNoSessions: "当前没有运行中的会话,工作区将立即重启。",
|
|
5011
|
+
upgradeSessionsWarning: "有 {count} 个正在运行的会话会被中断。升级完成后会自动恢复这些会话并发送“继续”。",
|
|
5012
|
+
upgradeCancel: "取消",
|
|
5013
|
+
upgradeConfirm: "停止任务并升级",
|
|
5014
|
+
upgradeBusy: "正在升级工作区…",
|
|
5015
|
+
upgradeStarted: "升级已开始。工作区重新连接后请刷新页面。",
|
|
5016
|
+
upgradeFailed: "工作区升级启动失败,请重试。",
|
|
5017
|
+
upgradeRetry: "重试"
|
|
1425
5018
|
};
|
|
1426
5019
|
//#endregion
|
|
1427
5020
|
//#region src/client/index.ts
|
|
1428
5021
|
const NS = "settings.dataops-managed";
|
|
1429
5022
|
const AUTH_UPDATED_MESSAGE = "dataops-auth-updated";
|
|
1430
5023
|
const MANAGED_AUTH_PATH = "/integrations/dataops/managed-auth";
|
|
5024
|
+
/** Sidebar entry and main page id for the permission centre. */
|
|
5025
|
+
const PERMISSION_PANEL_ID = "dataops-permissions";
|
|
1431
5026
|
/** Required client services for the managed DataOps Settings section. */
|
|
1432
5027
|
const inject = ["slots", "locale"];
|
|
1433
5028
|
/**
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
5029
|
+
* Bind the four strings the sign-in prompt needs.
|
|
5030
|
+
*
|
|
5031
|
+
* Narrowed to exactly those keys rather than reusing the settings-section resolver: the prompt
|
|
5032
|
+
* asks its own small copy, and a wider type would let a typo pass as a valid key.
|
|
5033
|
+
*
|
|
5034
|
+
* @param ctx - client context carrying the registered namespace.
|
|
5035
|
+
* @returns a resolver for the prompt's own keys.
|
|
5036
|
+
*/
|
|
5037
|
+
const bindReloginCopy = (ctx) => {
|
|
5038
|
+
const translate = ctx.locale.bind(NS);
|
|
5039
|
+
return (key) => translate(key);
|
|
5040
|
+
};
|
|
5041
|
+
/**
|
|
5042
|
+
* Register the managed DataOps surfaces and the sign-in prompt.
|
|
5043
|
+
*
|
|
5044
|
+
* The prompt rides a sidebar seat because the client needs *some* always-mounted surface for a
|
|
5045
|
+
* dialog that is about the whole workspace rather than one panel. The seat is a container: the
|
|
5046
|
+
* dialog itself portals to `document.body`, so this contributes no visible element to the
|
|
5047
|
+
* sidebar at rest and cannot disturb the regions that already live there.
|
|
5048
|
+
*
|
|
5049
|
+
* @param ctx - DSH client context with locale, slots and the sidebar foot.
|
|
1436
5050
|
*/
|
|
1437
5051
|
function apply(ctx) {
|
|
1438
5052
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
@@ -1441,13 +5055,113 @@ window.__ModuleLoader__.load({
|
|
|
1441
5055
|
}), "dataops-managed: settings copy");
|
|
1442
5056
|
installWanxiangBranding(ctx);
|
|
1443
5057
|
const t = ctx.locale.bind(NS);
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
5058
|
+
/**
|
|
5059
|
+
* Register the settings section only for the two administrator role labels.
|
|
5060
|
+
*
|
|
5061
|
+
* The identity record arrives after the iframe loads and can change when the portal account is
|
|
5062
|
+
* switched, so the registration is reconciled whenever the shared account store publishes. An
|
|
5063
|
+
* ordinary user's profile therefore has no section, no configuration form, and no publish
|
|
5064
|
+
* control at all — not a hidden one.
|
|
5065
|
+
*/
|
|
5066
|
+
ctx.effect(() => {
|
|
5067
|
+
let disposeSection;
|
|
5068
|
+
const reconcile = () => {
|
|
5069
|
+
const allowed = canSeeManagedSettings();
|
|
5070
|
+
if (allowed && disposeSection === void 0) disposeSection = ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
5071
|
+
name: "settings.section",
|
|
5072
|
+
id: "dataops",
|
|
5073
|
+
order: 25,
|
|
5074
|
+
label: () => t("nav"),
|
|
5075
|
+
inject: () => ({ t })
|
|
5076
|
+
}, ManagedDataOpsSection));
|
|
5077
|
+
else if (!allowed && disposeSection !== void 0) {
|
|
5078
|
+
disposeSection();
|
|
5079
|
+
disposeSection = void 0;
|
|
5080
|
+
}
|
|
5081
|
+
};
|
|
5082
|
+
reconcile();
|
|
5083
|
+
const unsubscribe = subscribeManagedSettingsVisibility(reconcile);
|
|
5084
|
+
return () => {
|
|
5085
|
+
unsubscribe();
|
|
5086
|
+
disposeSection?.();
|
|
5087
|
+
disposeSection = void 0;
|
|
5088
|
+
};
|
|
5089
|
+
}, "dataops-managed: settings section role gate");
|
|
5090
|
+
/**
|
|
5091
|
+
* The re-authentication prompt, mounted where it is always alive.
|
|
5092
|
+
*
|
|
5093
|
+
* A dialog needs a component that stays mounted whether or not the operator has the settings
|
|
5094
|
+
* panel open, and the sidebar foot is the one seat this plugin already owns. The component
|
|
5095
|
+
* renders nothing until DataOps reports the session has ended, so at rest it adds no element.
|
|
5096
|
+
*/
|
|
5097
|
+
ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
|
|
5098
|
+
name: "sidebar.footer.action",
|
|
5099
|
+
id: "dataops-relogin",
|
|
5100
|
+
order: 90,
|
|
5101
|
+
inject: () => ({ t: bindReloginCopy(ctx) })
|
|
5102
|
+
}, ReloginPrompt));
|
|
5103
|
+
/**
|
|
5104
|
+
* The workspace image upgrade notice. It lives in the shell overlay so it can sit
|
|
5105
|
+
* above the page without taking a sidebar or conversation seat; the component renders
|
|
5106
|
+
* nothing until DataOps reports a newer image.
|
|
5107
|
+
*/
|
|
5108
|
+
ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
5109
|
+
name: "shell.overlay",
|
|
5110
|
+
id: "dataops-workspace-upgrade",
|
|
5111
|
+
order: 20,
|
|
1449
5112
|
inject: () => ({ t })
|
|
1450
|
-
},
|
|
5113
|
+
}, WorkspaceUpgradePrompt));
|
|
5114
|
+
/**
|
|
5115
|
+
* The permission centre, a sidebar entry plus the page it opens.
|
|
5116
|
+
*
|
|
5117
|
+
* The entry's glyph hides itself for an ordinary user; the page and every backend endpoint
|
|
5118
|
+
* re-check the caller, so hiding is navigation, not authorization.
|
|
5119
|
+
*/
|
|
5120
|
+
ctx.effect(() => ctx.slots.inject("main", () => ctx.slots.register({
|
|
5121
|
+
name: "main",
|
|
5122
|
+
key: PERMISSION_PANEL_ID
|
|
5123
|
+
}, (() => PermissionCenterSection({ t })))), "dataops-managed: permission centre page");
|
|
5124
|
+
ctx.effect(() => {
|
|
5125
|
+
let disposeEntry;
|
|
5126
|
+
let live = true;
|
|
5127
|
+
const allowed = async () => {
|
|
5128
|
+
if (typeof window !== "undefined" && reviewDemoMode()) return true;
|
|
5129
|
+
if (canSeeManagedSettings()) return true;
|
|
5130
|
+
try {
|
|
5131
|
+
const response = await fetch(REVIEW_CONTEXT_PATH);
|
|
5132
|
+
if (!response.ok) return false;
|
|
5133
|
+
const body = await response.json();
|
|
5134
|
+
return body.primaryRole === "DEPT_ADMIN" || body.review?.canReview === true;
|
|
5135
|
+
} catch {
|
|
5136
|
+
return false;
|
|
5137
|
+
}
|
|
5138
|
+
};
|
|
5139
|
+
const sync = async () => {
|
|
5140
|
+
const next = await allowed();
|
|
5141
|
+
if (!live) return;
|
|
5142
|
+
if (next && disposeEntry === void 0) disposeEntry = ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
|
|
5143
|
+
name: "sidebar.panellist",
|
|
5144
|
+
id: PERMISSION_PANEL_ID,
|
|
5145
|
+
order: 20,
|
|
5146
|
+
label: () => t("permNav"),
|
|
5147
|
+
locale: NS
|
|
5148
|
+
}, PermissionCenterIcon));
|
|
5149
|
+
else if (!next && disposeEntry !== void 0) {
|
|
5150
|
+
disposeEntry();
|
|
5151
|
+
disposeEntry = void 0;
|
|
5152
|
+
}
|
|
5153
|
+
};
|
|
5154
|
+
sync();
|
|
5155
|
+
const off = subscribeManagedSettingsVisibility(() => {
|
|
5156
|
+
sync();
|
|
5157
|
+
});
|
|
5158
|
+
return () => {
|
|
5159
|
+
live = false;
|
|
5160
|
+
off();
|
|
5161
|
+
disposeEntry?.();
|
|
5162
|
+
disposeEntry = void 0;
|
|
5163
|
+
};
|
|
5164
|
+
}, "dataops-managed: permission centre entry gate");
|
|
1451
5165
|
/**
|
|
1452
5166
|
* The skill plaza, contributed to the skill center page.
|
|
1453
5167
|
*
|
|
@@ -1461,6 +5175,11 @@ window.__ModuleLoader__.load({
|
|
|
1461
5175
|
order: 10,
|
|
1462
5176
|
inject: () => ({ t })
|
|
1463
5177
|
}, SkillPlazaSection));
|
|
5178
|
+
/**
|
|
5179
|
+
* The workflow plaza's header seat, declared by the knowledge-base plugin. It renders a slot
|
|
5180
|
+
* rather than importing this plugin; the bell therefore joins only when both are installed.
|
|
5181
|
+
*/
|
|
5182
|
+
ctx.slots.inject("workflow-plaza.actions", () => ctx.slots.register({ name: "workflow-plaza.actions" }, WorkflowPlazaBell));
|
|
1464
5183
|
const synchronizeJwt = async () => {
|
|
1465
5184
|
const response = await fetch(new URL(MANAGED_AUTH_PATH, window.location.origin), {
|
|
1466
5185
|
method: "POST",
|