@rezti/dsh-rez-suite 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/client.d.ts +29 -155
- package/lib/client.js +365 -468
- package/lib/index.d.ts +20 -0
- package/lib/index.js +92 -13
- package/lib/style.css +82 -111
- package/package.json +9 -7
- package/src/client/index.ts +33 -21
- package/src/client/locales.ts +25 -7
- package/src/client/panel/AuditTab.tsx +6 -6
- package/src/client/panel/ConfigTab.tsx +141 -86
- package/src/client/panel/panel.module.css +89 -118
- package/src/client/settings-card.tsx +68 -0
- package/src/index.ts +37 -11
- package/src/protocol.ts +23 -0
- package/src/routes.ts +32 -4
- package/src/store.ts +37 -1
- package/src/client/mount.tsx +0 -97
- package/src/client/panel/RezPanel.tsx +0 -53
- package/src/client/panel/controller.ts +0 -42
- package/src/client/sidebar-entry.ts +0 -80
package/lib/client.js
CHANGED
|
@@ -4,82 +4,10 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
(function(){var id="dsh-rez-suite-css";if(typeof document==="undefined"||document.getElementById(id))return;var s=document.createElement("style");s.id=id;s.textContent=".WRGRRW_shell {\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n display: flex;\n}\n\n.WRGRRW_card {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-3);\n border-radius: 12px;\n list-style: none;\n transition: border-color .16s, background .16s;\n}\n\n.WRGRRW_card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n.WRGRRW_cardOpen {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n.WRGRRW_header {\n appearance: none;\n width: 100%;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n background: none;\n border: 0;\n border-radius: 12px;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n display: flex;\n}\n\n.WRGRRW_header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.WRGRRW_headText {\n flex-direction: column;\n flex: 1;\n gap: 4px;\n min-width: 0;\n display: flex;\n}\n\n.WRGRRW_name {\n color: var(--dsw-alias-label-primary);\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n}\n\n.WRGRRW_description {\n color: var(--dsw-alias-label-tertiary);\n font-size: 13px;\n line-height: 1.5;\n}\n\n.WRGRRW_chevron {\n color: var(--dsw-alias-label-tertiary);\n flex: none;\n transition: transform .16s;\n}\n\n.WRGRRW_chevronOpen {\n transform: rotate(180deg);\n}\n\n.WRGRRW_body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px 12px;\n padding-top: 8px;\n}\n\n.WRGRRW_tabBar {\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n flex: none;\n gap: 2px;\n display: flex;\n}\n\n.WRGRRW_tab {\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n background: none;\n border: none;\n border-bottom: 2px solid #0000;\n border-radius: 6px 6px 0 0;\n padding: 7px 14px;\n font-size: 13px;\n}\n\n.WRGRRW_tab:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.WRGRRW_tab[data-active] {\n color: var(--dsw-alias-label-primary);\n border-bottom-color: var(--dsw-alias-state-business-primary);\n font-weight: 600;\n}\n\n.WRGRRW_tabBody {\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n padding: 12px 0 4px;\n display: flex;\n}\n\n.WRGRRW_section {\n border: 1px solid var(--dsw-alias-border-l1);\n background: var(--dsw-alias-bg-layer-1, var(--dsw-alias-bg-base));\n border-radius: 10px;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n display: flex;\n}\n\n.WRGRRW_sectionTitle {\n color: var(--dsw-alias-label-primary);\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n}\n\n.WRGRRW_field {\n flex-direction: column;\n align-items: stretch;\n gap: 4px;\n display: flex;\n}\n\n.WRGRRW_label {\n color: var(--dsw-alias-label-secondary);\n font-size: 12.5px;\n}\n\n.WRGRRW_input, .WRGRRW_select {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-3));\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n outline: none;\n padding: 6px 8px;\n font-size: 13px;\n}\n\n.WRGRRW_checkbox {\n justify-self: start;\n width: 15px;\n height: 15px;\n}\n\n.WRGRRW_toolbar {\n flex-wrap: wrap;\n flex: none;\n align-items: center;\n gap: 8px;\n display: flex;\n}\n\n.WRGRRW_button {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-3));\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n border-radius: 8px;\n padding: 7px 12px;\n font-size: 13px;\n}\n\n.WRGRRW_button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.WRGRRW_button:disabled {\n opacity: .4;\n cursor: default;\n}\n\n.WRGRRW_primaryButton {\n background: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n\n.WRGRRW_dangerButton {\n background: var(--dsw-alias-state-danger, #c62828);\n border-color: var(--dsw-alias-state-danger, #c62828);\n color: #fff;\n}\n\n.WRGRRW_qr {\n background: #fff;\n border-radius: 8px;\n width: 200px;\n height: 200px;\n margin: 8px 0;\n display: block;\n}\n\n.WRGRRW_message {\n color: var(--dsw-alias-label-secondary);\n margin: 0;\n font-size: 12.5px;\n line-height: 1.5;\n}\n\n.WRGRRW_error {\n color: var(--dsw-alias-state-danger, #c62828);\n}\n\n.WRGRRW_tableWrap {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n overflow: auto;\n}\n\n.WRGRRW_table {\n border-collapse: collapse;\n width: 100%;\n font-size: 12.5px;\n}\n\n.WRGRRW_table th {\n z-index: 1;\n text-align: left;\n background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1));\n padding: 8px 10px;\n position: sticky;\n top: 0;\n}\n\n.WRGRRW_table td {\n border-top: 1px solid var(--dsw-alias-border-l1);\n vertical-align: top;\n padding: 8px 10px;\n}\n\n.WRGRRW_badge {\n border-radius: 999px;\n padding: 2px 8px;\n font-size: 11px;\n display: inline-block;\n}\n\n.WRGRRW_badgeOk {\n color: #229a55;\n background: #229a5524;\n}\n\n.WRGRRW_badgeFail {\n color: #c62828;\n background: #c6282824;\n}\n\n.WRGRRW_cards {\n grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));\n gap: 8px;\n display: grid;\n}\n\n.WRGRRW_stat {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n padding: 10px;\n}\n\n.WRGRRW_cardLabel {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n}\n\n.WRGRRW_cardValue {\n color: var(--dsw-alias-label-primary);\n font-size: 15px;\n font-weight: 600;\n}\n";document.head.appendChild(s);})();
|
|
7
8
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
8
|
-
let react_dom_client = require("react-dom/client");
|
|
9
9
|
let react = require("react");
|
|
10
10
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
|
-
//#region src/protocol.ts
|
|
12
|
-
const REZ_API = {
|
|
13
|
-
config: "/api/dsh-rez-suite/config",
|
|
14
|
-
test: "/api/dsh-rez-suite/test",
|
|
15
|
-
status: "/api/dsh-rez-suite/status",
|
|
16
|
-
audit: "/api/dsh-rez-suite/audit",
|
|
17
|
-
auditReset: "/api/dsh-rez-suite/audit/reset",
|
|
18
|
-
weixin: "/api/dsh-rez-suite/weixin",
|
|
19
|
-
weixinVerify: "/api/dsh-rez-suite/weixin/verify"
|
|
20
|
-
};
|
|
21
|
-
//#endregion
|
|
22
|
-
//#region src/client/api.ts
|
|
23
|
-
/**
|
|
24
|
-
* Browser-side API client for /api/dsh-rez-suite. Plain same-origin fetch.
|
|
25
|
-
*/
|
|
26
|
-
var RezApiError = class extends Error {
|
|
27
|
-
constructor(message) {
|
|
28
|
-
super(message);
|
|
29
|
-
this.name = "RezApiError";
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
async function readJson(response) {
|
|
33
|
-
let body;
|
|
34
|
-
try {
|
|
35
|
-
body = await response.json();
|
|
36
|
-
} catch {
|
|
37
|
-
throw new RezApiError("HTTP " + response.status + ": invalid JSON response");
|
|
38
|
-
}
|
|
39
|
-
if (!response.ok) throw new RezApiError(typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : "HTTP " + response.status);
|
|
40
|
-
return body;
|
|
41
|
-
}
|
|
42
|
-
var RezApi = class {
|
|
43
|
-
async getConfig() {
|
|
44
|
-
return (await readJson(await fetch(REZ_API.config))).config;
|
|
45
|
-
}
|
|
46
|
-
async saveConfig(patch) {
|
|
47
|
-
return (await readJson(await fetch(REZ_API.config, {
|
|
48
|
-
method: "PUT",
|
|
49
|
-
headers: { "content-type": "application/json" },
|
|
50
|
-
body: JSON.stringify(patch)
|
|
51
|
-
}))).config;
|
|
52
|
-
}
|
|
53
|
-
async test() {
|
|
54
|
-
return (await readJson(await fetch(REZ_API.test, { method: "POST" }))).results;
|
|
55
|
-
}
|
|
56
|
-
async status() {
|
|
57
|
-
return readJson(await fetch(REZ_API.status));
|
|
58
|
-
}
|
|
59
|
-
async audit() {
|
|
60
|
-
return (await readJson(await fetch(REZ_API.audit))).audit;
|
|
61
|
-
}
|
|
62
|
-
async resetAudit() {
|
|
63
|
-
await readJson(await fetch(REZ_API.auditReset, { method: "POST" }));
|
|
64
|
-
}
|
|
65
|
-
async weixinStatus() {
|
|
66
|
-
return readJson(await fetch(REZ_API.weixin));
|
|
67
|
-
}
|
|
68
|
-
async weixinLogin() {
|
|
69
|
-
return readJson(await fetch(REZ_API.weixin, { method: "POST" }));
|
|
70
|
-
}
|
|
71
|
-
async weixinVerify(code) {
|
|
72
|
-
return readJson(await fetch(REZ_API.weixinVerify, {
|
|
73
|
-
method: "POST",
|
|
74
|
-
headers: { "content-type": "application/json" },
|
|
75
|
-
body: JSON.stringify({ code })
|
|
76
|
-
}));
|
|
77
|
-
}
|
|
78
|
-
async weixinLogout() {
|
|
79
|
-
return readJson(await fetch(REZ_API.weixin, { method: "DELETE" }));
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
//#endregion
|
|
83
11
|
//#region src/client/locales.ts
|
|
84
12
|
/**
|
|
85
13
|
* dsh-rez-suite surface copy: zh is the key source, en mirrors every key.
|
|
@@ -87,6 +15,8 @@ var RezApi = class {
|
|
|
87
15
|
const zh = {
|
|
88
16
|
"entry.label": "Rez",
|
|
89
17
|
"entry.tooltip": "Rez Suite 配置与审计面板",
|
|
18
|
+
"settings.title": "ReZ-TI",
|
|
19
|
+
"settings.description": "公司 MCP(Odoo / Nextcloud / 企微 / HA)与个人微信扫码通道。",
|
|
90
20
|
"panel.title": "Rez Suite",
|
|
91
21
|
"tab.config": "配置",
|
|
92
22
|
"tab.weixin": "微信",
|
|
@@ -106,16 +36,23 @@ const zh = {
|
|
|
106
36
|
"config.env": "环境变量 (JSON)",
|
|
107
37
|
"config.headers": "Headers (JSON)",
|
|
108
38
|
"config.jsonInvalid": "JSON 格式错误:{error}",
|
|
39
|
+
"config.mcpHint": "四个公司 MCP 在这里配。密钥写入 ~/.dsh/.credentials.yaml(REZ_*)。新填的密钥会立刻拉起连接;改 URL 后如果已经连上,需要重启 dsh web。",
|
|
40
|
+
"config.secretConfigured": "已配置(留空则保持)",
|
|
41
|
+
"config.secretMissing": "未配置",
|
|
109
42
|
"config.odoo": "Odoo",
|
|
110
|
-
"config.odoo.url": "Odoo URL",
|
|
111
|
-
"config.odoo.apiKey": "API
|
|
43
|
+
"config.odoo.url": "Odoo MCP URL",
|
|
44
|
+
"config.odoo.apiKey": "API Token",
|
|
112
45
|
"config.nextcloud": "Nextcloud",
|
|
113
|
-
"config.nextcloud.url": "Nextcloud
|
|
46
|
+
"config.nextcloud.url": "Nextcloud 主机",
|
|
114
47
|
"config.nextcloud.username": "用户名",
|
|
115
48
|
"config.nextcloud.appPassword": "App Password",
|
|
116
49
|
"config.wecom": "企业微信",
|
|
117
50
|
"config.wecom.botId": "Bot ID",
|
|
118
51
|
"config.wecom.secret": "Secret",
|
|
52
|
+
"config.wecom.webhook": "群机器人 Webhook",
|
|
53
|
+
"config.ha": "Home Assistant",
|
|
54
|
+
"config.ha.url": "HA MCP URL",
|
|
55
|
+
"config.ha.token": "长期访问令牌",
|
|
119
56
|
"config.fs": "本地文件 (fs-mcp-server)",
|
|
120
57
|
"config.fs.root": "根目录",
|
|
121
58
|
"config.sqlite": "SQLite (sqlite-mcp-server)",
|
|
@@ -174,6 +111,8 @@ const zh = {
|
|
|
174
111
|
const en = {
|
|
175
112
|
"entry.label": "Rez",
|
|
176
113
|
"entry.tooltip": "Rez Suite configuration and audit panel",
|
|
114
|
+
"settings.title": "ReZ-TI",
|
|
115
|
+
"settings.description": "Company MCP (Odoo / Nextcloud / WeCom / HA) and personal WeChat scan-and-chat.",
|
|
177
116
|
"panel.title": "Rez Suite",
|
|
178
117
|
"tab.config": "Config",
|
|
179
118
|
"tab.weixin": "WeChat",
|
|
@@ -193,16 +132,23 @@ const en = {
|
|
|
193
132
|
"config.env": "Env vars (JSON)",
|
|
194
133
|
"config.headers": "Headers (JSON)",
|
|
195
134
|
"config.jsonInvalid": "Invalid JSON: {error}",
|
|
135
|
+
"config.mcpHint": "Company MCP lives here. Secrets go to ~/.dsh/.credentials.yaml (REZ_*). A newly saved secret starts the connection immediately; URL changes after a live connection need a dsh web restart.",
|
|
136
|
+
"config.secretConfigured": "Configured (leave blank to keep)",
|
|
137
|
+
"config.secretMissing": "Not configured",
|
|
196
138
|
"config.odoo": "Odoo",
|
|
197
|
-
"config.odoo.url": "Odoo URL",
|
|
198
|
-
"config.odoo.apiKey": "API
|
|
139
|
+
"config.odoo.url": "Odoo MCP URL",
|
|
140
|
+
"config.odoo.apiKey": "API token",
|
|
199
141
|
"config.nextcloud": "Nextcloud",
|
|
200
|
-
"config.nextcloud.url": "Nextcloud
|
|
142
|
+
"config.nextcloud.url": "Nextcloud host",
|
|
201
143
|
"config.nextcloud.username": "Username",
|
|
202
|
-
"config.nextcloud.appPassword": "App
|
|
144
|
+
"config.nextcloud.appPassword": "App password",
|
|
203
145
|
"config.wecom": "WeCom",
|
|
204
146
|
"config.wecom.botId": "Bot ID",
|
|
205
147
|
"config.wecom.secret": "Secret",
|
|
148
|
+
"config.wecom.webhook": "Group bot webhook",
|
|
149
|
+
"config.ha": "Home Assistant",
|
|
150
|
+
"config.ha.url": "HA MCP URL",
|
|
151
|
+
"config.ha.token": "Long-lived access token",
|
|
206
152
|
"config.fs": "Local files (fs-mcp-server)",
|
|
207
153
|
"config.fs.root": "Root directory",
|
|
208
154
|
"config.sqlite": "SQLite (sqlite-mcp-server)",
|
|
@@ -265,6 +211,78 @@ function t(dict, key, values) {
|
|
|
265
211
|
return text;
|
|
266
212
|
}
|
|
267
213
|
//#endregion
|
|
214
|
+
//#region src/protocol.ts
|
|
215
|
+
const REZ_API = {
|
|
216
|
+
config: "/api/dsh-rez-suite/config",
|
|
217
|
+
test: "/api/dsh-rez-suite/test",
|
|
218
|
+
status: "/api/dsh-rez-suite/status",
|
|
219
|
+
audit: "/api/dsh-rez-suite/audit",
|
|
220
|
+
auditReset: "/api/dsh-rez-suite/audit/reset",
|
|
221
|
+
weixin: "/api/dsh-rez-suite/weixin",
|
|
222
|
+
weixinVerify: "/api/dsh-rez-suite/weixin/verify"
|
|
223
|
+
};
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/client/api.ts
|
|
226
|
+
/**
|
|
227
|
+
* Browser-side API client for /api/dsh-rez-suite. Plain same-origin fetch.
|
|
228
|
+
*/
|
|
229
|
+
var RezApiError = class extends Error {
|
|
230
|
+
constructor(message) {
|
|
231
|
+
super(message);
|
|
232
|
+
this.name = "RezApiError";
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
async function readJson(response) {
|
|
236
|
+
let body;
|
|
237
|
+
try {
|
|
238
|
+
body = await response.json();
|
|
239
|
+
} catch {
|
|
240
|
+
throw new RezApiError("HTTP " + response.status + ": invalid JSON response");
|
|
241
|
+
}
|
|
242
|
+
if (!response.ok) throw new RezApiError(typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : "HTTP " + response.status);
|
|
243
|
+
return body;
|
|
244
|
+
}
|
|
245
|
+
var RezApi = class {
|
|
246
|
+
async getConfig() {
|
|
247
|
+
return (await readJson(await fetch(REZ_API.config))).config;
|
|
248
|
+
}
|
|
249
|
+
async saveConfig(patch) {
|
|
250
|
+
return (await readJson(await fetch(REZ_API.config, {
|
|
251
|
+
method: "PUT",
|
|
252
|
+
headers: { "content-type": "application/json" },
|
|
253
|
+
body: JSON.stringify(patch)
|
|
254
|
+
}))).config;
|
|
255
|
+
}
|
|
256
|
+
async test() {
|
|
257
|
+
return (await readJson(await fetch(REZ_API.test, { method: "POST" }))).results;
|
|
258
|
+
}
|
|
259
|
+
async status() {
|
|
260
|
+
return readJson(await fetch(REZ_API.status));
|
|
261
|
+
}
|
|
262
|
+
async audit() {
|
|
263
|
+
return (await readJson(await fetch(REZ_API.audit))).audit;
|
|
264
|
+
}
|
|
265
|
+
async resetAudit() {
|
|
266
|
+
await readJson(await fetch(REZ_API.auditReset, { method: "POST" }));
|
|
267
|
+
}
|
|
268
|
+
async weixinStatus() {
|
|
269
|
+
return readJson(await fetch(REZ_API.weixin));
|
|
270
|
+
}
|
|
271
|
+
async weixinLogin() {
|
|
272
|
+
return readJson(await fetch(REZ_API.weixin, { method: "POST" }));
|
|
273
|
+
}
|
|
274
|
+
async weixinVerify(code) {
|
|
275
|
+
return readJson(await fetch(REZ_API.weixinVerify, {
|
|
276
|
+
method: "POST",
|
|
277
|
+
headers: { "content-type": "application/json" },
|
|
278
|
+
body: JSON.stringify({ code })
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
async weixinLogout() {
|
|
282
|
+
return readJson(await fetch(REZ_API.weixin, { method: "DELETE" }));
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
//#endregion
|
|
268
286
|
//#region src/client/panel/helpers.ts
|
|
269
287
|
/**
|
|
270
288
|
* Shared panel helpers: active-language dictionary pick and error extraction.
|
|
@@ -285,38 +303,39 @@ var panel_module_default = {
|
|
|
285
303
|
"badge": "WRGRRW_badge",
|
|
286
304
|
"badgeFail": "WRGRRW_badgeFail",
|
|
287
305
|
"badgeOk": "WRGRRW_badgeOk",
|
|
306
|
+
"body": "WRGRRW_body",
|
|
288
307
|
"button": "WRGRRW_button",
|
|
289
308
|
"card": "WRGRRW_card",
|
|
290
309
|
"cardLabel": "WRGRRW_cardLabel",
|
|
310
|
+
"cardOpen": "WRGRRW_cardOpen",
|
|
291
311
|
"cards": "WRGRRW_cards",
|
|
292
312
|
"cardValue": "WRGRRW_cardValue",
|
|
293
313
|
"checkbox": "WRGRRW_checkbox",
|
|
314
|
+
"chevron": "WRGRRW_chevron",
|
|
315
|
+
"chevronOpen": "WRGRRW_chevronOpen",
|
|
294
316
|
"dangerButton": "WRGRRW_dangerButton",
|
|
295
|
-
"
|
|
296
|
-
"entryIcon": "WRGRRW_entryIcon",
|
|
297
|
-
"entryLabel": "WRGRRW_entryLabel",
|
|
317
|
+
"description": "WRGRRW_description",
|
|
298
318
|
"error": "WRGRRW_error",
|
|
299
319
|
"field": "WRGRRW_field",
|
|
300
|
-
"
|
|
320
|
+
"header": "WRGRRW_header",
|
|
321
|
+
"headText": "WRGRRW_headText",
|
|
301
322
|
"input": "WRGRRW_input",
|
|
302
323
|
"label": "WRGRRW_label",
|
|
303
324
|
"message": "WRGRRW_message",
|
|
304
|
-
"
|
|
305
|
-
"panelContent": "WRGRRW_panelContent",
|
|
306
|
-
"panelHeader": "WRGRRW_panelHeader",
|
|
307
|
-
"panelTitle": "WRGRRW_panelTitle",
|
|
325
|
+
"name": "WRGRRW_name",
|
|
308
326
|
"primaryButton": "WRGRRW_primaryButton",
|
|
309
327
|
"qr": "WRGRRW_qr",
|
|
310
328
|
"section": "WRGRRW_section",
|
|
311
329
|
"sectionTitle": "WRGRRW_sectionTitle",
|
|
312
330
|
"select": "WRGRRW_select",
|
|
331
|
+
"shell": "WRGRRW_shell",
|
|
332
|
+
"stat": "WRGRRW_stat",
|
|
313
333
|
"tab": "WRGRRW_tab",
|
|
314
334
|
"tabBar": "WRGRRW_tabBar",
|
|
315
335
|
"tabBody": "WRGRRW_tabBody",
|
|
316
336
|
"table": "WRGRRW_table",
|
|
317
337
|
"tableWrap": "WRGRRW_tableWrap",
|
|
318
|
-
"toolbar": "WRGRRW_toolbar"
|
|
319
|
-
"view": "WRGRRW_view"
|
|
338
|
+
"toolbar": "WRGRRW_toolbar"
|
|
320
339
|
};
|
|
321
340
|
//#endregion
|
|
322
341
|
//#region src/client/panel/AuditTab.tsx
|
|
@@ -387,7 +406,7 @@ function AuditTab({ api }) {
|
|
|
387
406
|
className: panel_module_default.cards,
|
|
388
407
|
children: [
|
|
389
408
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
390
|
-
className: panel_module_default.
|
|
409
|
+
className: panel_module_default.stat,
|
|
391
410
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
392
411
|
className: panel_module_default.cardLabel,
|
|
393
412
|
children: tt("audit.totalCalls")
|
|
@@ -397,7 +416,7 @@ function AuditTab({ api }) {
|
|
|
397
416
|
})]
|
|
398
417
|
}),
|
|
399
418
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
400
|
-
className: panel_module_default.
|
|
419
|
+
className: panel_module_default.stat,
|
|
401
420
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
402
421
|
className: panel_module_default.cardLabel,
|
|
403
422
|
children: tt("audit.inputTokens")
|
|
@@ -407,7 +426,7 @@ function AuditTab({ api }) {
|
|
|
407
426
|
})]
|
|
408
427
|
}),
|
|
409
428
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
410
|
-
className: panel_module_default.
|
|
429
|
+
className: panel_module_default.stat,
|
|
411
430
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
412
431
|
className: panel_module_default.cardLabel,
|
|
413
432
|
children: tt("audit.outputTokens")
|
|
@@ -417,7 +436,7 @@ function AuditTab({ api }) {
|
|
|
417
436
|
})]
|
|
418
437
|
}),
|
|
419
438
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
420
|
-
className: panel_module_default.
|
|
439
|
+
className: panel_module_default.stat,
|
|
421
440
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
422
441
|
className: panel_module_default.cardLabel,
|
|
423
442
|
children: tt("audit.totalCost")
|
|
@@ -427,7 +446,7 @@ function AuditTab({ api }) {
|
|
|
427
446
|
})]
|
|
428
447
|
}),
|
|
429
448
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
430
|
-
className: panel_module_default.
|
|
449
|
+
className: panel_module_default.stat,
|
|
431
450
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
432
451
|
className: panel_module_default.cardLabel,
|
|
433
452
|
children: tt("audit.monthCost")
|
|
@@ -437,7 +456,7 @@ function AuditTab({ api }) {
|
|
|
437
456
|
})]
|
|
438
457
|
}),
|
|
439
458
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
440
|
-
className: panel_module_default.
|
|
459
|
+
className: panel_module_default.stat,
|
|
441
460
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
442
461
|
className: panel_module_default.cardLabel,
|
|
443
462
|
children: tt("audit.budget")
|
|
@@ -484,8 +503,8 @@ function AuditTab({ api }) {
|
|
|
484
503
|
//#endregion
|
|
485
504
|
//#region src/client/panel/ConfigTab.tsx
|
|
486
505
|
/**
|
|
487
|
-
* Config tab:
|
|
488
|
-
*
|
|
506
|
+
* Config tab: company MCP connections (Odoo / Nextcloud / WeCom / HA),
|
|
507
|
+
* role, and billing. Secrets are write-only and land in credentials.yaml.
|
|
489
508
|
*/
|
|
490
509
|
const ROLES = [
|
|
491
510
|
{
|
|
@@ -505,6 +524,12 @@ const ROLES = [
|
|
|
505
524
|
label: "role.operations"
|
|
506
525
|
}
|
|
507
526
|
];
|
|
527
|
+
const EMPTY_SECRETS = {
|
|
528
|
+
odoo: "",
|
|
529
|
+
nextcloud: "",
|
|
530
|
+
wechat: "",
|
|
531
|
+
homeassistant: ""
|
|
532
|
+
};
|
|
508
533
|
function Field({ label, children }) {
|
|
509
534
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
510
535
|
className: panel_module_default.field,
|
|
@@ -523,42 +548,81 @@ function Section({ title, children }) {
|
|
|
523
548
|
}), children]
|
|
524
549
|
});
|
|
525
550
|
}
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
551
|
+
function SecretField({ label, configured, value, onChange }) {
|
|
552
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
553
|
+
label,
|
|
554
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
555
|
+
className: panel_module_default.input,
|
|
556
|
+
type: "password",
|
|
557
|
+
autoComplete: "off",
|
|
558
|
+
value,
|
|
559
|
+
placeholder: configured ? tt("config.secretConfigured") : tt("config.secretMissing"),
|
|
560
|
+
onChange: (event) => {
|
|
561
|
+
onChange(event.target.value);
|
|
562
|
+
}
|
|
563
|
+
})
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
function McpSection({ title, row, secret, onEnabled, onUrl, onUsername, onSecret, urlLabel, secretLabel, usernameLabel }) {
|
|
567
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
|
|
568
|
+
title,
|
|
569
|
+
children: [
|
|
570
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
571
|
+
label: tt("config.enabled"),
|
|
572
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
573
|
+
className: panel_module_default.checkbox,
|
|
574
|
+
type: "checkbox",
|
|
575
|
+
checked: row.enabled,
|
|
576
|
+
onChange: (event) => {
|
|
577
|
+
onEnabled(event.target.checked);
|
|
578
|
+
}
|
|
579
|
+
})
|
|
580
|
+
}),
|
|
581
|
+
onUrl !== void 0 && urlLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
582
|
+
label: urlLabel,
|
|
583
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
584
|
+
className: panel_module_default.input,
|
|
585
|
+
value: row.url ?? "",
|
|
586
|
+
onChange: (event) => {
|
|
587
|
+
onUrl(event.target.value);
|
|
588
|
+
}
|
|
589
|
+
})
|
|
590
|
+
}),
|
|
591
|
+
onUsername !== void 0 && usernameLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
592
|
+
label: usernameLabel,
|
|
593
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
594
|
+
className: panel_module_default.input,
|
|
595
|
+
value: row.username ?? "",
|
|
596
|
+
onChange: (event) => {
|
|
597
|
+
onUsername(event.target.value);
|
|
598
|
+
}
|
|
599
|
+
})
|
|
600
|
+
}),
|
|
601
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretField, {
|
|
602
|
+
label: secretLabel,
|
|
603
|
+
configured: row.secretConfigured,
|
|
604
|
+
value: secret,
|
|
605
|
+
onChange: onSecret
|
|
606
|
+
}),
|
|
607
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
608
|
+
className: panel_module_default.message,
|
|
609
|
+
children: row.secretRef
|
|
610
|
+
})
|
|
611
|
+
]
|
|
612
|
+
});
|
|
532
613
|
}
|
|
533
614
|
function ConfigTab({ api }) {
|
|
534
615
|
const [config, setConfig] = (0, react.useState)(null);
|
|
616
|
+
const [secrets, setSecrets] = (0, react.useState)(EMPTY_SECRETS);
|
|
535
617
|
const [loading, setLoading] = (0, react.useState)(true);
|
|
536
618
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
537
619
|
const [testing, setTesting] = (0, react.useState)(false);
|
|
538
620
|
const [message, setMessage] = (0, react.useState)("");
|
|
539
621
|
const [error, setError] = (0, react.useState)("");
|
|
540
622
|
const [results, setResults] = (0, react.useState)([]);
|
|
541
|
-
const [argsText, setArgsText] = (0, react.useState)({});
|
|
542
|
-
const [envText, setEnvText] = (0, react.useState)({});
|
|
543
|
-
const [headerText, setHeaderText] = (0, react.useState)({});
|
|
544
|
-
const initTexts = (next) => {
|
|
545
|
-
const args = {};
|
|
546
|
-
const envs = {};
|
|
547
|
-
const headers = {};
|
|
548
|
-
for (const [id, server] of Object.entries(next.servers)) {
|
|
549
|
-
args[id] = (server.args ?? []).join("\n");
|
|
550
|
-
envs[id] = JSON.stringify(server.env ?? {}, null, 2);
|
|
551
|
-
headers[id] = JSON.stringify(server.headers ?? {}, null, 2);
|
|
552
|
-
}
|
|
553
|
-
setArgsText(args);
|
|
554
|
-
setEnvText(envs);
|
|
555
|
-
setHeaderText(headers);
|
|
556
|
-
};
|
|
557
623
|
const load = async () => {
|
|
558
624
|
try {
|
|
559
|
-
|
|
560
|
-
setConfig(next);
|
|
561
|
-
initTexts(next);
|
|
625
|
+
setConfig(await api.getConfig());
|
|
562
626
|
} catch (err) {
|
|
563
627
|
setError(errorMessage(err));
|
|
564
628
|
} finally {
|
|
@@ -582,13 +646,13 @@ function ConfigTab({ api }) {
|
|
|
582
646
|
[key]: value
|
|
583
647
|
});
|
|
584
648
|
};
|
|
585
|
-
const
|
|
649
|
+
const patchConnection = (id, patch) => {
|
|
586
650
|
setConfig((prev) => prev === null ? prev : {
|
|
587
651
|
...prev,
|
|
588
|
-
|
|
589
|
-
...prev.
|
|
652
|
+
connections: {
|
|
653
|
+
...prev.connections,
|
|
590
654
|
[id]: {
|
|
591
|
-
...prev.
|
|
655
|
+
...prev.connections[id],
|
|
592
656
|
...patch
|
|
593
657
|
}
|
|
594
658
|
}
|
|
@@ -599,9 +663,32 @@ function ConfigTab({ api }) {
|
|
|
599
663
|
setMessage("");
|
|
600
664
|
setError("");
|
|
601
665
|
try {
|
|
602
|
-
const
|
|
666
|
+
const payload = {
|
|
667
|
+
enabled: config.enabled,
|
|
668
|
+
role: config.role,
|
|
669
|
+
billing: config.billing,
|
|
670
|
+
connections: {
|
|
671
|
+
odoo: {
|
|
672
|
+
...config.connections.odoo,
|
|
673
|
+
secret: secrets.odoo || void 0
|
|
674
|
+
},
|
|
675
|
+
nextcloud: {
|
|
676
|
+
...config.connections.nextcloud,
|
|
677
|
+
secret: secrets.nextcloud || void 0
|
|
678
|
+
},
|
|
679
|
+
wechat: {
|
|
680
|
+
...config.connections.wechat,
|
|
681
|
+
secret: secrets.wechat || void 0
|
|
682
|
+
},
|
|
683
|
+
homeassistant: {
|
|
684
|
+
...config.connections.homeassistant,
|
|
685
|
+
secret: secrets.homeassistant || void 0
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
const saved = await api.saveConfig(payload);
|
|
603
690
|
setConfig(saved);
|
|
604
|
-
|
|
691
|
+
setSecrets(EMPTY_SECRETS);
|
|
605
692
|
setMessage(tt("config.saved"));
|
|
606
693
|
} catch (err) {
|
|
607
694
|
setError(errorMessage(err));
|
|
@@ -623,28 +710,22 @@ function ConfigTab({ api }) {
|
|
|
623
710
|
}
|
|
624
711
|
};
|
|
625
712
|
const renderResult = (result) => {
|
|
626
|
-
if (result.ok)
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const commitArgs = (id) => {
|
|
633
|
-
updateServer(id, { args: argsText[id] === "" ? [] : argsText[id].split("\n").map((line) => line.trim()).filter(Boolean) });
|
|
634
|
-
};
|
|
635
|
-
const commitRecord = (id, kind) => {
|
|
636
|
-
const text = kind === "env" ? envText[id] : headerText[id];
|
|
637
|
-
try {
|
|
638
|
-
const record = parseRecord(text ?? "{}");
|
|
639
|
-
updateServer(id, kind === "env" ? { env: record } : { headers: record });
|
|
640
|
-
setError("");
|
|
641
|
-
} catch (err) {
|
|
642
|
-
setError(tt("config.jsonInvalid", { error: errorMessage(err) }));
|
|
713
|
+
if (result.ok) {
|
|
714
|
+
if (result.serverInfo !== void 0) return result.serverInfo;
|
|
715
|
+
return tt("config.testOk", {
|
|
716
|
+
tools: result.toolCount ?? 0,
|
|
717
|
+
latency: result.latencyMs ?? 0
|
|
718
|
+
});
|
|
643
719
|
}
|
|
720
|
+
return tt("config.testFail", { error: result.error ?? "" });
|
|
644
721
|
};
|
|
645
722
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
646
723
|
className: panel_module_default.tabBody,
|
|
647
724
|
children: [
|
|
725
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
726
|
+
className: panel_module_default.message,
|
|
727
|
+
children: tt("config.mcpHint")
|
|
728
|
+
}),
|
|
648
729
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
|
|
649
730
|
title: tt("config.role"),
|
|
650
731
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
@@ -672,110 +753,82 @@ function ConfigTab({ api }) {
|
|
|
672
753
|
})
|
|
673
754
|
})]
|
|
674
755
|
}),
|
|
675
|
-
|
|
676
|
-
title:
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
})
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
752
|
-
label: tt("config.url"),
|
|
753
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
754
|
-
className: panel_module_default.input,
|
|
755
|
-
value: server.url ?? "",
|
|
756
|
-
onChange: (event) => {
|
|
757
|
-
updateServer(id, { url: event.target.value });
|
|
758
|
-
}
|
|
759
|
-
})
|
|
760
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
761
|
-
label: tt("config.headers"),
|
|
762
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
763
|
-
className: panel_module_default.input,
|
|
764
|
-
rows: 4,
|
|
765
|
-
value: headerText[id] ?? "{}",
|
|
766
|
-
onChange: (event) => {
|
|
767
|
-
setHeaderText((prev) => ({
|
|
768
|
-
...prev,
|
|
769
|
-
[id]: event.target.value
|
|
770
|
-
}));
|
|
771
|
-
},
|
|
772
|
-
onBlur: () => {
|
|
773
|
-
commitRecord(id, "headers");
|
|
774
|
-
}
|
|
775
|
-
})
|
|
776
|
-
})] })
|
|
777
|
-
]
|
|
778
|
-
}, id)),
|
|
756
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
|
|
757
|
+
title: tt("config.odoo"),
|
|
758
|
+
row: config.connections.odoo,
|
|
759
|
+
secret: secrets.odoo,
|
|
760
|
+
onEnabled: (enabled) => {
|
|
761
|
+
patchConnection("odoo", { enabled });
|
|
762
|
+
},
|
|
763
|
+
onUrl: (url) => {
|
|
764
|
+
patchConnection("odoo", { url });
|
|
765
|
+
},
|
|
766
|
+
onSecret: (value) => {
|
|
767
|
+
setSecrets((prev) => ({
|
|
768
|
+
...prev,
|
|
769
|
+
odoo: value
|
|
770
|
+
}));
|
|
771
|
+
},
|
|
772
|
+
urlLabel: tt("config.odoo.url"),
|
|
773
|
+
secretLabel: tt("config.odoo.apiKey")
|
|
774
|
+
}),
|
|
775
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
|
|
776
|
+
title: tt("config.nextcloud"),
|
|
777
|
+
row: config.connections.nextcloud,
|
|
778
|
+
secret: secrets.nextcloud,
|
|
779
|
+
onEnabled: (enabled) => {
|
|
780
|
+
patchConnection("nextcloud", { enabled });
|
|
781
|
+
},
|
|
782
|
+
onUrl: (url) => {
|
|
783
|
+
patchConnection("nextcloud", { url });
|
|
784
|
+
},
|
|
785
|
+
onUsername: (username) => {
|
|
786
|
+
patchConnection("nextcloud", { username });
|
|
787
|
+
},
|
|
788
|
+
onSecret: (value) => {
|
|
789
|
+
setSecrets((prev) => ({
|
|
790
|
+
...prev,
|
|
791
|
+
nextcloud: value
|
|
792
|
+
}));
|
|
793
|
+
},
|
|
794
|
+
urlLabel: tt("config.nextcloud.url"),
|
|
795
|
+
usernameLabel: tt("config.nextcloud.username"),
|
|
796
|
+
secretLabel: tt("config.nextcloud.appPassword")
|
|
797
|
+
}),
|
|
798
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
|
|
799
|
+
title: tt("config.wecom"),
|
|
800
|
+
row: config.connections.wechat,
|
|
801
|
+
secret: secrets.wechat,
|
|
802
|
+
onEnabled: (enabled) => {
|
|
803
|
+
patchConnection("wechat", { enabled });
|
|
804
|
+
},
|
|
805
|
+
onSecret: (value) => {
|
|
806
|
+
setSecrets((prev) => ({
|
|
807
|
+
...prev,
|
|
808
|
+
wechat: value
|
|
809
|
+
}));
|
|
810
|
+
},
|
|
811
|
+
secretLabel: tt("config.wecom.webhook")
|
|
812
|
+
}),
|
|
813
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
|
|
814
|
+
title: tt("config.ha"),
|
|
815
|
+
row: config.connections.homeassistant,
|
|
816
|
+
secret: secrets.homeassistant,
|
|
817
|
+
onEnabled: (enabled) => {
|
|
818
|
+
patchConnection("homeassistant", { enabled });
|
|
819
|
+
},
|
|
820
|
+
onUrl: (url) => {
|
|
821
|
+
patchConnection("homeassistant", { url });
|
|
822
|
+
},
|
|
823
|
+
onSecret: (value) => {
|
|
824
|
+
setSecrets((prev) => ({
|
|
825
|
+
...prev,
|
|
826
|
+
homeassistant: value
|
|
827
|
+
}));
|
|
828
|
+
},
|
|
829
|
+
urlLabel: tt("config.ha.url"),
|
|
830
|
+
secretLabel: tt("config.ha.token")
|
|
831
|
+
}),
|
|
779
832
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
|
|
780
833
|
title: tt("config.billing"),
|
|
781
834
|
children: [
|
|
@@ -1090,9 +1143,10 @@ function WeixinTab({ api }) {
|
|
|
1090
1143
|
});
|
|
1091
1144
|
}
|
|
1092
1145
|
//#endregion
|
|
1093
|
-
//#region src/client/
|
|
1146
|
+
//#region src/client/settings-card.tsx
|
|
1094
1147
|
/**
|
|
1095
|
-
*
|
|
1148
|
+
* ReZ settings pages: one accordion card in 设置 → 插件 → 插件配置,
|
|
1149
|
+
* plus a dedicated Plugins tab for the same content.
|
|
1096
1150
|
*/
|
|
1097
1151
|
const TABS = [
|
|
1098
1152
|
{
|
|
@@ -1112,27 +1166,12 @@ const TABS = [
|
|
|
1112
1166
|
label: () => tt("tab.audit")
|
|
1113
1167
|
}
|
|
1114
1168
|
];
|
|
1115
|
-
function
|
|
1169
|
+
function RezSettingsPage() {
|
|
1170
|
+
const api = (0, react.useMemo)(() => new RezApi(), []);
|
|
1116
1171
|
const [activeTab, setActiveTab] = (0, react.useState)("config");
|
|
1117
1172
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1118
|
-
className: panel_module_default.
|
|
1173
|
+
className: panel_module_default.shell,
|
|
1119
1174
|
children: [
|
|
1120
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1121
|
-
className: panel_module_default.panelHeader,
|
|
1122
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1123
|
-
className: panel_module_default.panelTitle,
|
|
1124
|
-
children: tt("panel.title")
|
|
1125
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1126
|
-
type: "button",
|
|
1127
|
-
className: panel_module_default.iconButton,
|
|
1128
|
-
title: tt("common.close"),
|
|
1129
|
-
"aria-label": tt("common.close"),
|
|
1130
|
-
onClick: () => {
|
|
1131
|
-
controller.close();
|
|
1132
|
-
},
|
|
1133
|
-
children: "x"
|
|
1134
|
-
})]
|
|
1135
|
-
}),
|
|
1136
1175
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1137
1176
|
className: panel_module_default.tabBar,
|
|
1138
1177
|
role: "tablist",
|
|
@@ -1148,218 +1187,76 @@ function RezPanel({ controller, api }) {
|
|
|
1148
1187
|
children: tab.label()
|
|
1149
1188
|
}, tab.id))
|
|
1150
1189
|
}),
|
|
1151
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
activeTab === "weixin" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WeixinTab, { api }),
|
|
1156
|
-
activeTab === "status" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusTab, { api }),
|
|
1157
|
-
activeTab === "audit" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuditTab, { api })
|
|
1158
|
-
]
|
|
1159
|
-
})
|
|
1190
|
+
activeTab === "config" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigTab, { api }),
|
|
1191
|
+
activeTab === "weixin" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WeixinTab, { api }),
|
|
1192
|
+
activeTab === "status" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusTab, { api }),
|
|
1193
|
+
activeTab === "audit" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuditTab, { api })
|
|
1160
1194
|
]
|
|
1161
1195
|
});
|
|
1162
1196
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
const waitObserver = new MutationObserver(() => {
|
|
1202
|
-
ensure();
|
|
1203
|
-
});
|
|
1204
|
-
waitObserver.observe(document.body, {
|
|
1205
|
-
childList: true,
|
|
1206
|
-
subtree: true
|
|
1207
|
-
});
|
|
1208
|
-
const retryTimer = window.setInterval(() => {
|
|
1209
|
-
ensure();
|
|
1210
|
-
if (container?.isConnected === true) window.clearInterval(retryTimer);
|
|
1211
|
-
}, 250);
|
|
1212
|
-
const applyActive = () => {
|
|
1213
|
-
if (controller.getSnapshot().panelOpen) {
|
|
1214
|
-
for (const attr of OTHER_ACTIVE_ATTRS) document.documentElement.removeAttribute(attr);
|
|
1215
|
-
document.documentElement.setAttribute(ACTIVE_ATTR, "");
|
|
1216
|
-
document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }));
|
|
1217
|
-
} else document.documentElement.removeAttribute(ACTIVE_ATTR);
|
|
1218
|
-
};
|
|
1219
|
-
const onOtherActivate = (event) => {
|
|
1220
|
-
const detail = event.detail;
|
|
1221
|
-
if ((detail === "taskboard" || detail === "ssh") && controller.getSnapshot().panelOpen) controller.close();
|
|
1222
|
-
};
|
|
1223
|
-
const SIDEBAR_ROW_SELECTOR = "[class*=\"sessionRow\"], [class*=\"projectRow\"], [class*=\"searchResultRow\"], [class*=\"searchResultWorkspace\"], [class*=\"newSession\"]";
|
|
1224
|
-
const onClickSidebarRow = (event) => {
|
|
1225
|
-
if (!controller.getSnapshot().panelOpen) return;
|
|
1226
|
-
const target = event.target;
|
|
1227
|
-
if (target === null) return;
|
|
1228
|
-
if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.close();
|
|
1229
|
-
};
|
|
1230
|
-
document.addEventListener("click", onClickSidebarRow, true);
|
|
1231
|
-
document.addEventListener(ACTIVATE_EVENT, onOtherActivate);
|
|
1232
|
-
const unsubscribe = controller.subscribe(applyActive);
|
|
1233
|
-
applyActive();
|
|
1234
|
-
ensure();
|
|
1235
|
-
return () => {
|
|
1236
|
-
window.clearInterval(retryTimer);
|
|
1237
|
-
document.removeEventListener("click", onClickSidebarRow, true);
|
|
1238
|
-
document.removeEventListener(ACTIVATE_EVENT, onOtherActivate);
|
|
1239
|
-
waitObserver.disconnect();
|
|
1240
|
-
unsubscribe();
|
|
1241
|
-
document.documentElement.removeAttribute(ACTIVE_ATTR);
|
|
1242
|
-
root?.unmount();
|
|
1243
|
-
root = void 0;
|
|
1244
|
-
container?.remove();
|
|
1245
|
-
container = void 0;
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
//#endregion
|
|
1249
|
-
//#region src/client/panel/controller.ts
|
|
1250
|
-
var PanelController = class {
|
|
1251
|
-
panelOpen = false;
|
|
1252
|
-
listeners = /* @__PURE__ */ new Set();
|
|
1253
|
-
getSnapshot() {
|
|
1254
|
-
return { panelOpen: this.panelOpen };
|
|
1255
|
-
}
|
|
1256
|
-
subscribe(fn) {
|
|
1257
|
-
this.listeners.add(fn);
|
|
1258
|
-
return () => {
|
|
1259
|
-
this.listeners.delete(fn);
|
|
1260
|
-
};
|
|
1261
|
-
}
|
|
1262
|
-
open() {
|
|
1263
|
-
if (this.panelOpen) return;
|
|
1264
|
-
this.panelOpen = true;
|
|
1265
|
-
this.notify();
|
|
1266
|
-
}
|
|
1267
|
-
close() {
|
|
1268
|
-
if (!this.panelOpen) return;
|
|
1269
|
-
this.panelOpen = false;
|
|
1270
|
-
this.notify();
|
|
1271
|
-
}
|
|
1272
|
-
toggle() {
|
|
1273
|
-
if (this.panelOpen) this.close();
|
|
1274
|
-
else this.open();
|
|
1275
|
-
}
|
|
1276
|
-
notify() {
|
|
1277
|
-
for (const fn of [...this.listeners]) fn();
|
|
1278
|
-
}
|
|
1279
|
-
};
|
|
1280
|
-
//#endregion
|
|
1281
|
-
//#region src/client/sidebar-entry.ts
|
|
1282
|
-
function sidebarRoot() {
|
|
1283
|
-
const column = document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]");
|
|
1284
|
-
if (column === null) return void 0;
|
|
1285
|
-
return column.querySelector("[class*=\"logoRow\"]")?.parentElement ?? column.firstElementChild;
|
|
1286
|
-
}
|
|
1287
|
-
function newSessionButton(root) {
|
|
1288
|
-
const nested = root.querySelector("button[class*=\"newSession\"]");
|
|
1289
|
-
if (nested !== null) return nested;
|
|
1290
|
-
for (const child of root.children) if (child.tagName === "BUTTON") return child;
|
|
1291
|
-
}
|
|
1292
|
-
function createEntry(controller) {
|
|
1293
|
-
const entry = document.createElement("button");
|
|
1294
|
-
entry.type = "button";
|
|
1295
|
-
entry.dataset.dshRezEntry = "";
|
|
1296
|
-
entry.className = panel_module_default.entry;
|
|
1297
|
-
entry.setAttribute("aria-label", tt("entry.label"));
|
|
1298
|
-
entry.setAttribute("title", tt("entry.tooltip"));
|
|
1299
|
-
entry.innerHTML = "<span class=\"" + panel_module_default.entryIcon + "\"><svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2.5\" y=\"2.5\" width=\"11\" height=\"11\" rx=\"2\"/><path d=\"M5 8l2 2 4-4\"/></svg></span><span class=\"" + panel_module_default.entryLabel + "\">" + tt("entry.label") + "</span>";
|
|
1300
|
-
entry.addEventListener("click", () => {
|
|
1301
|
-
controller.toggle();
|
|
1302
|
-
});
|
|
1303
|
-
return entry;
|
|
1304
|
-
}
|
|
1305
|
-
function placeEntry(root, entry) {
|
|
1306
|
-
const button = newSessionButton(root);
|
|
1307
|
-
if (button === void 0) return false;
|
|
1308
|
-
if (entry.parentElement !== root) {
|
|
1309
|
-
const row = button.closest("[class*=\"logoRow\"]");
|
|
1310
|
-
const base = row !== null && row.parentElement === root ? row : button;
|
|
1311
|
-
const family = Array.from(root.children).filter((el) => el instanceof HTMLElement && el.matches("[data-dsh-taskboard-entry], [data-dsh-ssh-entry], [data-dsh-rez-entry]"));
|
|
1312
|
-
const anchor = family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling;
|
|
1313
|
-
root.insertBefore(entry, anchor);
|
|
1314
|
-
}
|
|
1315
|
-
return true;
|
|
1316
|
-
}
|
|
1317
|
-
function mountSidebarEntry(controller) {
|
|
1318
|
-
const entry = createEntry(controller);
|
|
1319
|
-
let root;
|
|
1320
|
-
let placed = false;
|
|
1321
|
-
const place = () => {
|
|
1322
|
-
const next = sidebarRoot();
|
|
1323
|
-
if (next === void 0) return;
|
|
1324
|
-
if (root !== void 0 && root !== next) {
|
|
1325
|
-
if (entry.parentElement === root) entry.remove();
|
|
1326
|
-
placed = false;
|
|
1327
|
-
}
|
|
1328
|
-
root = next;
|
|
1329
|
-
if (!placed) placed = placeEntry(root, entry);
|
|
1330
|
-
};
|
|
1331
|
-
const observer = new MutationObserver(place);
|
|
1332
|
-
observer.observe(document.body, {
|
|
1333
|
-
childList: true,
|
|
1334
|
-
subtree: true
|
|
1197
|
+
function RezPluginCard() {
|
|
1198
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1199
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1200
|
+
className: panel_module_default.card + (open ? " " + panel_module_default.cardOpen : ""),
|
|
1201
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1202
|
+
type: "button",
|
|
1203
|
+
className: panel_module_default.header,
|
|
1204
|
+
onClick: () => {
|
|
1205
|
+
setOpen(!open);
|
|
1206
|
+
},
|
|
1207
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1208
|
+
className: panel_module_default.headText,
|
|
1209
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1210
|
+
className: panel_module_default.name,
|
|
1211
|
+
children: tt("settings.title")
|
|
1212
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1213
|
+
className: panel_module_default.description,
|
|
1214
|
+
children: tt("settings.description")
|
|
1215
|
+
})]
|
|
1216
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1217
|
+
className: panel_module_default.chevron + (open ? " " + panel_module_default.chevronOpen : ""),
|
|
1218
|
+
viewBox: "0 0 14 14",
|
|
1219
|
+
width: "14",
|
|
1220
|
+
height: "14",
|
|
1221
|
+
"aria-hidden": "true",
|
|
1222
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1223
|
+
d: "M3 5l4 4 4-4",
|
|
1224
|
+
fill: "none",
|
|
1225
|
+
stroke: "currentColor",
|
|
1226
|
+
strokeWidth: "1.4",
|
|
1227
|
+
strokeLinecap: "round",
|
|
1228
|
+
strokeLinejoin: "round"
|
|
1229
|
+
})
|
|
1230
|
+
})]
|
|
1231
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1232
|
+
className: panel_module_default.body,
|
|
1233
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RezSettingsPage, {})
|
|
1234
|
+
}) : null]
|
|
1335
1235
|
});
|
|
1336
|
-
place();
|
|
1337
|
-
return () => {
|
|
1338
|
-
observer.disconnect();
|
|
1339
|
-
entry.remove();
|
|
1340
|
-
};
|
|
1341
1236
|
}
|
|
1342
1237
|
//#endregion
|
|
1343
1238
|
//#region src/client/index.ts
|
|
1344
1239
|
const NS = "dsh-rez-suite";
|
|
1345
1240
|
const inject = ["slots", "locale"];
|
|
1346
1241
|
function apply(ctx) {
|
|
1242
|
+
const t = ctx.locale.bind(NS);
|
|
1347
1243
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
1348
1244
|
zh,
|
|
1349
1245
|
en
|
|
1350
1246
|
}), "dsh-rez-suite: dictionaries");
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1247
|
+
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
1248
|
+
name: "settings.plugin.item",
|
|
1249
|
+
id: "rez-suite",
|
|
1250
|
+
order: 40,
|
|
1251
|
+
locale: NS
|
|
1252
|
+
}, RezPluginCard));
|
|
1253
|
+
ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
|
|
1254
|
+
name: "settings.plugins.tab",
|
|
1255
|
+
id: "rez",
|
|
1256
|
+
order: 20,
|
|
1257
|
+
label: () => t("settings.title"),
|
|
1258
|
+
locale: NS
|
|
1259
|
+
}, RezSettingsPage));
|
|
1363
1260
|
}
|
|
1364
1261
|
//#endregion
|
|
1365
1262
|
exports.apply = apply;
|