dsh-dbhub-live 2.0.0 → 3.1.2

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 ADDED
@@ -0,0 +1,604 @@
1
+ // dsh-dbhub-live — browser half (hand-authored lazy-CJS client bundle).
2
+ //
3
+ // Packaging contract: the Node client-modules scanner serves
4
+ // `exports["./client"]` for every Loader entry declaring `dsh.client`; the
5
+ // script must register a lazy CJS factory through `window.__ModuleLoader__.load`
6
+ // whose `id` equals the Loader entry name. The factory receives the shared
7
+ // `require` (React is a seeded baseline module) and returns the Cordis plugin
8
+ // object — `name` / `inject` / `apply`.
9
+ //
10
+ // The card renders the plugin's live status + configurable options + workspace
11
+ // connections from the `dsh-dbhub-live` settings namespace (mirrored by the
12
+ // Host half) and writes back through namespace fields: `enabled` (toggle),
13
+ // `updateIntervalDays`/`idleMinutes` (options), `configOp` (one-way workspace
14
+ // connection commands). Passwords never leave the Host — the mirror only
15
+ // carries masked DSN summaries.
16
+
17
+ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
18
+
19
+ var module = { exports: {} };
20
+ var exports = module.exports;
21
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
22
+ var react = require("react");
23
+ // Product icon primitives (a baseline platform module) — the chevron the
24
+ // built-in plugin cards use. Falls back to a text glyph if unavailable.
25
+ var primitives = require("@deepseek-ai/dsh-client-ui-primitives");
26
+ var ChevronIcon = (primitives && primitives.IconChevronDownOutline14) || function () { return react.createElement("span", null, "▼"); };
27
+
28
+ var NS = "dsh-dbhub-live";
29
+
30
+ // Locale dictionaries (dsh UI language) + fallback zh translator.
31
+ var LOCALE_ZH = {
32
+ title: "DBHub 数据库工具",
33
+ "phase.running": "运行中",
34
+ "phase.initializing": "初始化中",
35
+ "phase.error": "异常",
36
+ "phase.disabled": "已禁用",
37
+ "mode.lazy": "懒加载(首次调用时初始化)",
38
+ "block.status": "状态",
39
+ "block.config": "配置",
40
+ "block.workspaces": "工作区连接",
41
+ "block.add": "添加连接",
42
+ "cell.server": "常驻进程",
43
+ "cell.mode": "工作模式",
44
+ "cell.tools": "工具声明",
45
+ "cell.envs": "环境",
46
+ "cell.error": "最近错误",
47
+ "server.up": "运行中",
48
+ "server.wait": "待启动(按需)",
49
+ "tools.fixed": "{n} 个(固定)",
50
+ "envs.count": "{n} 个 · 已保存 {m}",
51
+ "cfg.update": "自动更新间隔(天)",
52
+ "cfg.idle": "空闲回收(分钟)",
53
+ "btn.save": "保存配置",
54
+ "btn.add": "添加连接",
55
+ "btn.edit": "修改",
56
+ "btn.cancel": "取消",
57
+ "btn.saved": "保存",
58
+ "btn.delete": "删除",
59
+ "btn.disable": "禁用",
60
+ "btn.enable": "启用",
61
+ "saved.ok": "已保存 ✓",
62
+ "badge.saved": "已保存",
63
+ "badge.auto": "自动",
64
+ "ws.source": "来源",
65
+ "ws.env": "环境",
66
+ "ws.srcVal": "source",
67
+ "ws.empty": "暂无已保存或自动发现的连接(可用下方表单添加,或让模型调用 dbhub_configure)。",
68
+ "ws.placeholder": "工作区路径或标题;留空 = 默认当前工作区",
69
+ "env.placeholder": "如 prod / dev / test;默认 default",
70
+ "dsn.placeholder": "mysql://user:pass@host:3306/db",
71
+ "dsn.editPlaceholder": "完整 DSN(密码勿泄露给模型)",
72
+ "desc.unconfigured": "未配置连接",
73
+ "desc.connRunning": "常驻运行中",
74
+ "desc.connWaiting": "常驻待启动",
75
+ "desc.environments": "{n} 个环境 · 已保存 {m}",
76
+ "add.ws": "工作区",
77
+ "add.env": "环境名",
78
+ "add.dsn": "连接串 DSN",
79
+ "src.user": "已保存·用户",
80
+ "src.collected": "已保存·扫描",
81
+ "src.mise": "自动·mise env",
82
+ "src.env": "自动·.env",
83
+ };
84
+
85
+ var LOCALE_EN = {
86
+ title: "DBHub Database Tools",
87
+ "phase.running": "Running",
88
+ "phase.initializing": "Initializing",
89
+ "phase.error": "Error",
90
+ "phase.disabled": "Disabled",
91
+ "mode.lazy": "Lazy (initialized on first use)",
92
+ "block.status": "Status",
93
+ "block.config": "Configuration",
94
+ "block.workspaces": "Workspace connections",
95
+ "block.add": "Add connection",
96
+ "cell.server": "Server",
97
+ "cell.mode": "Mode",
98
+ "cell.tools": "Tool declarations",
99
+ "cell.envs": "Environments",
100
+ "cell.error": "Recent error",
101
+ "server.up": "running",
102
+ "server.wait": "waiting (on demand)",
103
+ "tools.fixed": "{n} (fixed)",
104
+ "envs.count": "{n} · {m} saved",
105
+ "cfg.update": "Update interval (days)",
106
+ "cfg.idle": "Idle recycle (min)",
107
+ "btn.save": "Save",
108
+ "btn.add": "Add connection",
109
+ "btn.edit": "Edit",
110
+ "btn.cancel": "Cancel",
111
+ "btn.saved": "Save",
112
+ "btn.delete": "Delete",
113
+ "btn.disable": "Disable",
114
+ "btn.enable": "Enable",
115
+ "saved.ok": "Saved ✓",
116
+ "badge.saved": "saved",
117
+ "badge.auto": "auto",
118
+ "ws.source": "source",
119
+ "ws.env": "env",
120
+ "ws.srcVal": "source",
121
+ "ws.empty": "No saved or auto-discovered connections yet (add one below or ask the model to run dbhub_configure).",
122
+ "ws.placeholder": "workspace path or title; blank = the current workspace",
123
+ "env.placeholder": "e.g. prod / dev / test; default = default",
124
+ "dsn.placeholder": "mysql://user:pass@host:3306/db",
125
+ "dsn.editPlaceholder": "full DSN (do not reveal the password to the model)",
126
+ "desc.unconfigured": "no connections configured",
127
+ "desc.connRunning": "server running",
128
+ "desc.connWaiting": "server waiting",
129
+ "desc.environments": "{n} environments · {m} saved",
130
+ "add.ws": "Workspace",
131
+ "add.env": "Environment",
132
+ "add.dsn": "DSN",
133
+ "src.user": "saved · user",
134
+ "src.collected": "saved · scan",
135
+ "src.mise": "auto · mise env",
136
+ "src.env": "auto · .env",
137
+ };
138
+
139
+ function tForZh(key, params) {
140
+ var s = LOCALE_ZH[key] || key;
141
+ if (params) {
142
+ for (var k in params) { if (Object.prototype.hasOwnProperty.call(params, k)) s = s.split("{" + k + "}").join(String(params[k])); }
143
+ }
144
+ return s;
145
+ }
146
+
147
+ var PHASE_META = {
148
+ initializing: { key: "phase.initializing", emoji: "🟡", color: "#b58900" },
149
+ running: { key: "phase.running", emoji: "🟢", color: "#2f9e44" },
150
+ error: { key: "phase.error", emoji: "🔴", color: "#e03131" },
151
+ disabled: { key: "phase.disabled", emoji: "⚪", color: "#868e96" },
152
+ };
153
+
154
+ var SOURCE_LABEL_ZH = {
155
+ "persisted(user)": "src.user",
156
+ "persisted(collected)": "src.collected",
157
+ "工作区 mise env": "src.mise",
158
+ "工作目录 .env": "src.env",
159
+ };
160
+
161
+ function valueOf(snapshot) {
162
+ if (!snapshot || typeof snapshot !== "object") return {};
163
+ var value = snapshot.value;
164
+ return value && typeof value === "object" ? value : {};
165
+ }
166
+
167
+ function workspacesOf(value) {
168
+ try {
169
+ var parsed = JSON.parse(typeof value.workspaces === "string" ? value.workspaces : "[]");
170
+ return Array.isArray(parsed) ? parsed : [];
171
+ } catch (e) {
172
+ return [];
173
+ }
174
+ }
175
+
176
+ // ── styles (native card look, mirroring the built-in plugin settings cards) ──
177
+ //
178
+ // Visual language copied from the shipped plugins (e.g. dsh-context's
179
+ // settings card): theme tokens with neutral fallbacks, 12px radius, a
180
+ // full-width clickable head (name + desc on the left, chevron on the right)
181
+ // that turns into an open panel (layer-2) once expanded.
182
+
183
+ var TOK = {
184
+ layer2: "var(--dsw-alias-bg-layer-2, #f6f8fa)",
185
+ layer3: "var(--dsw-alias-bg-layer-3, #ffffff)",
186
+ borderL2: "var(--dsw-alias-border-l2, #d0d7de)",
187
+ labelPrimary: "var(--dsw-alias-label-primary, #1f2328)",
188
+ labelTer: "var(--dsw-alias-label-tertiary, #6e7781)",
189
+ labelDim: "var(--dsw-alias-label-dimmed, #8b949e)",
190
+ };
191
+
192
+ var cardStyle = {
193
+ border: "1px solid " + TOK.borderL2,
194
+ borderRadius: "12px",
195
+ fontFamily: "inherit",
196
+ fontSize: "13px",
197
+ color: TOK.labelPrimary,
198
+ background: TOK.layer3,
199
+ // Span the full column like the built-in plugin cards (no max width).
200
+ width: "100%",
201
+ boxSizing: "border-box",
202
+ transition: "border-color .16s, background .16s",
203
+ };
204
+
205
+ var headStyle = {
206
+ display: "flex",
207
+ alignItems: "center",
208
+ cursor: "pointer",
209
+ gap: "12px",
210
+ padding: "14px 16px",
211
+ borderRadius: "12px",
212
+ };
213
+
214
+ var headTextStyle = {
215
+ display: "flex",
216
+ flexDirection: "column",
217
+ flex: "1",
218
+ gap: "4px",
219
+ minWidth: "0",
220
+ };
221
+
222
+ var nameStyle = {
223
+ color: TOK.labelPrimary,
224
+ fontSize: "15px",
225
+ fontWeight: 600,
226
+ lineHeight: "1.4",
227
+ };
228
+
229
+ var descStyle = {
230
+ color: TOK.labelTer,
231
+ fontSize: "13px",
232
+ lineHeight: "1.5",
233
+ overflow: "hidden",
234
+ textOverflow: "ellipsis",
235
+ whiteSpace: "nowrap",
236
+ };
237
+
238
+ var chevronWrapStyle = {
239
+ display: "flex",
240
+ flex: "none",
241
+ color: TOK.labelTer,
242
+ transition: "transform .16s",
243
+ };
244
+
245
+ var bodyStyle = {
246
+ borderTop: "1px solid " + TOK.borderL2,
247
+ margin: "0 16px",
248
+ padding: "4px 0 12px",
249
+ };
250
+
251
+ var blockTitleStyle = {
252
+ fontWeight: 600,
253
+ fontSize: "12px",
254
+ color: TOK.labelTer,
255
+ padding: "10px 0 2px",
256
+ };
257
+
258
+ // Group box for the 配置 rows: gives the save button an obvious home.
259
+ var groupStyle = {
260
+ border: "1px solid " + TOK.borderL2,
261
+ borderRadius: "8px",
262
+ background: "var(--dsw-alias-bg-module-platform, #f6f8fa)",
263
+ padding: "6px 12px 8px",
264
+ marginBottom: "4px",
265
+ };
266
+
267
+ // Responsive status grid: cells flow to multiple columns when the column
268
+ // is wide enough, and collapse to one per row on narrow screens.
269
+ var statGridStyle = {
270
+ display: "flex",
271
+ flexWrap: "wrap",
272
+ margin: "0 -6px",
273
+ };
274
+
275
+ var statCellStyle = {
276
+ boxSizing: "border-box",
277
+ flex: "1 1 45%",
278
+ minWidth: "150px",
279
+ padding: "6px",
280
+ display: "flex",
281
+ flexDirection: "column",
282
+ gap: "2px",
283
+ };
284
+
285
+ var statLabelStyle = { color: TOK.labelTer, fontSize: "11px" };
286
+
287
+ var statValueStyle = { color: TOK.labelPrimary, fontSize: "13px", fontWeight: 600, wordBreak: "break-all" };
288
+
289
+ var rowStyle = {
290
+ display: "flex",
291
+ alignItems: "center",
292
+ justifyContent: "space-between",
293
+ padding: "6px 0",
294
+ };
295
+
296
+ var labelStyle = { color: "#57606a", marginRight: "12px" };
297
+
298
+ var valueStyle = { fontWeight: 600, textAlign: "right" };
299
+
300
+ var errorStyle = {
301
+ color: "#e03131",
302
+ whiteSpace: "pre-wrap",
303
+ wordBreak: "break-all",
304
+ maxHeight: "96px",
305
+ overflow: "auto",
306
+ };
307
+
308
+ var toggleStyle = {
309
+ cursor: "pointer",
310
+ borderRadius: "6px",
311
+ border: "1px solid #d0d7de",
312
+ padding: "4px 12px",
313
+ fontSize: "13px",
314
+ color: "#1f2328",
315
+ background: "#f6f8fa",
316
+ };
317
+
318
+ var dangerStyle = Object.assign({}, toggleStyle, { color: "#e03131", borderColor: "#f0b8b8" });
319
+
320
+ var inputStyle = {
321
+ marginLeft: "12px",
322
+ width: "150px",
323
+ borderRadius: "4px",
324
+ border: "1px solid #d0d7de",
325
+ padding: "4px 6px",
326
+ fontSize: "13px",
327
+ color: "#1f2328",
328
+ background: "#ffffff",
329
+ textAlign: "right",
330
+ };
331
+
332
+ var wideInputStyle = Object.assign({}, inputStyle, { width: "100%", textAlign: "left", marginLeft: "0", boxSizing: "border-box" });
333
+
334
+ var saveRowStyle = {
335
+ display: "flex",
336
+ alignItems: "center",
337
+ justifyContent: "flex-end",
338
+ gap: "8px",
339
+ padding: "6px 0 0",
340
+ };
341
+
342
+ var savedStyle = { color: "#2f9e44", fontSize: "12px" };
343
+
344
+ var wsRowStyle = {
345
+ padding: "5px 0",
346
+ borderBottom: "1px dashed #eef1f4",
347
+ };
348
+
349
+ var wsTitleStyle = { fontWeight: 600, fontSize: "12px" };
350
+
351
+ var wsMetaStyle = { color: "#57606a", fontSize: "12px", wordBreak: "break-all" };
352
+
353
+ var autoBadgeStyle = { color: "#b58900", fontSize: "11px", border: "1px solid #e6d69a", borderRadius: "4px", padding: "0 4px", marginLeft: "6px" };
354
+
355
+ var savedBadgeStyle = { color: "#2f9e44", fontSize: "11px", border: "1px solid #b7d8bf", borderRadius: "4px", padding: "0 4px", marginLeft: "6px" };
356
+
357
+ var srcStyle = {
358
+ color: "#8b949e",
359
+ fontFamily: "monospace",
360
+ fontSize: "11px",
361
+ wordBreak: "break-all",
362
+ };
363
+
364
+ // ── the card (single collapse: header shows status + toggle; expand to manage) ──
365
+
366
+ function StatusCard(props) {
367
+ var loaded = react.useState(function () { return valueOf(props.getSnapshot()); });
368
+ var value = loaded[0];
369
+ var setValue = loaded[1];
370
+
371
+ react.useEffect(function () {
372
+ return props.subscribe(function () { setValue(valueOf(props.getSnapshot())); });
373
+ }, []);
374
+
375
+ var openState = react.useState(false);
376
+ var open = openState[0];
377
+ var setOpen = openState[1];
378
+
379
+ var t = props.t || tForZh;
380
+ var enabled = value.enabled !== false;
381
+ var phase = enabled ? (value.phase || "initializing") : "disabled";
382
+ var meta = PHASE_META[phase] || PHASE_META.initializing;
383
+ var toolCount = typeof value.toolCount === "number" ? value.toolCount : 0;
384
+ var lastError = typeof value.lastError === "string" && value.lastError ? value.lastError : "";
385
+ var modeLabel = t("mode." + (value.mode || "lazy"));
386
+ var serverUp = value.serverUp === true;
387
+ var workspaces = workspacesOf(value);
388
+ var savedCount = workspaces.filter(function (w) { return w.persisted; }).length;
389
+
390
+ var draftState = react.useState(function () {
391
+ return {
392
+ updateIntervalDays: typeof value.updateIntervalDays === "number" ? String(value.updateIntervalDays) : "7",
393
+ idleMinutes: typeof value.idleMinutes === "number" ? String(value.idleMinutes) : "10",
394
+ };
395
+ });
396
+ var draft = draftState[0];
397
+ var setDraft = draftState[1];
398
+ var savedState = react.useState(false);
399
+ var saved = savedState[0];
400
+ var setSaved = savedState[1];
401
+
402
+ var saveConfig = function () {
403
+ props.saveConfig({
404
+ updateIntervalDays: Number(draft.updateIntervalDays),
405
+ idleMinutes: Number(draft.idleMinutes),
406
+ }).then(function () { setSaved(true); });
407
+ };
408
+
409
+ var addState = react.useState({ ws: "", env: "default", dsn: "" });
410
+ var addForm = addState[0];
411
+ var setAddForm = addState[1];
412
+ var addDsn = function () {
413
+ if (!addForm.dsn.trim()) return;
414
+ props.configOp({ op: "add", workspace: addForm.ws.trim() || undefined, env: addForm.env.trim() || "default", dsn: addForm.dsn.trim() });
415
+ setAddForm({ ws: "", env: "default", dsn: "" });
416
+ };
417
+
418
+ var editState = react.useState(null);
419
+ var editing = editState[0];
420
+ var setEditing = editState[1];
421
+ var saveEdit = function (row) {
422
+ if (!editing || !editing.dsn.trim()) return;
423
+ props.configOp({ op: "add", workspace: row.path, env: row.env, dsn: editing.dsn.trim() });
424
+ setEditing(null);
425
+ };
426
+
427
+ // Collapsed header (native card look): name + desc on the left, status +
428
+ // toggle + chevron on the right. The chevron is the product icon primitive
429
+ // (rotates when open), mirroring the built-in plugin settings cards.
430
+ var descText = (workspaces.length > 0
431
+ ? t("desc.environments", { n: String(workspaces.length), m: String(savedCount) })
432
+ : t("desc.unconfigured")) +
433
+ (serverUp ? " · " + t("desc.connRunning") : " · " + t("desc.connWaiting"));
434
+ var hoverState = react.useState(false);
435
+ var hover = hoverState[0];
436
+ var setHover = hoverState[1];
437
+ var cardStateStyle = Object.assign({}, cardStyle, open
438
+ ? { background: TOK.layer2, borderColor: TOK.labelDim }
439
+ : hover ? { borderColor: TOK.labelDim }
440
+ : undefined);
441
+ var header = react.createElement("div", {
442
+ style: headStyle,
443
+ onClick: function () { setOpen(!open); },
444
+ },
445
+ react.createElement("span", { style: headTextStyle },
446
+ react.createElement("span", { style: nameStyle }, t("title")),
447
+ react.createElement("span", { style: descStyle },
448
+ meta.emoji + " " + t(meta.key) + " · " + descText)),
449
+ react.createElement("button", {
450
+ type: "button",
451
+ style: enabled ? Object.assign({}, toggleStyle, { color: "#2f9e44", borderColor: "#b7d8bf", background: "#ecf7ef", flex: "none" }) : Object.assign({}, toggleStyle, { flex: "none" }),
452
+ onClick: function (e) { e.stopPropagation(); props.setEnabled(!enabled); },
453
+ title: t(enabled ? "btn.disable" : "btn.enable") + " plugin",
454
+ }, t(enabled ? "btn.disable" : "btn.enable")),
455
+ react.createElement("span", { style: Object.assign({}, chevronWrapStyle, open ? { transform: "rotate(180deg)" } : {}) },
456
+ react.createElement(ChevronIcon, null)));
457
+
458
+ if (!open) {
459
+ return react.createElement("div", { style: cardStateStyle, onMouseEnter: function () { setHover(true); }, onMouseLeave: function () { setHover(false); } }, header);
460
+ }
461
+
462
+ // Responsive status grid cells (multi-column when the column is wide).
463
+ var cell = function (key, label, valueNode, full) {
464
+ return react.createElement("div", { key: key, style: Object.assign({}, statCellStyle, full ? { flex: "1 1 100%" } : undefined) },
465
+ react.createElement("span", { style: statLabelStyle }, label),
466
+ react.createElement("span", { style: Object.assign({}, statValueStyle, full ? errorStyle : undefined) }, valueNode));
467
+ };
468
+ var statusCells = [
469
+ cell("server", t("cell.server"), t(serverUp ? "server.up" : "server.wait")),
470
+ cell("mode", t("cell.mode"), modeLabel),
471
+ cell("tools", t("cell.tools"), t("tools.fixed", { n: String(toolCount) })),
472
+ cell("envs", t("cell.envs"), t("envs.count", { n: String(workspaces.length), m: String(savedCount) })),
473
+ ];
474
+ if (lastError) {
475
+ statusCells.push(cell("error", t("cell.error"), lastError, true));
476
+ }
477
+
478
+ var configRows = [
479
+ react.createElement("div", { key: "updateIntervalDays", style: rowStyle },
480
+ react.createElement("span", { style: labelStyle, title: t("cfg.update") }, t("cfg.update")),
481
+ react.createElement("input", {
482
+ type: "number", min: "0", style: inputStyle,
483
+ value: typeof draft.updateIntervalDays === "string" ? draft.updateIntervalDays : "",
484
+ onChange: function (e) { setSaved(false); setDraft(Object.assign({}, draft, { updateIntervalDays: e.target.value })); },
485
+ })),
486
+ react.createElement("div", { key: "idleMinutes", style: rowStyle },
487
+ react.createElement("span", { style: labelStyle, title: t("cfg.idle") }, t("cfg.idle")),
488
+ react.createElement("input", {
489
+ type: "number", min: "1", style: inputStyle,
490
+ value: typeof draft.idleMinutes === "string" ? draft.idleMinutes : "",
491
+ onChange: function (e) { setSaved(false); setDraft(Object.assign({}, draft, { idleMinutes: e.target.value })); },
492
+ })),
493
+ react.createElement("div", { key: "save", style: saveRowStyle },
494
+ saved ? react.createElement("span", { key: "saved", style: savedStyle }, t("saved.ok")) : null,
495
+ react.createElement("button", { type: "button", style: toggleStyle, onClick: saveConfig }, t("btn.save"))),
496
+ ];
497
+
498
+ var wsRows = workspaces.map(function (w) {
499
+ var key = w.path + "|" + w.env;
500
+ var sourceText = SOURCE_LABEL_ZH[w.source] ? t(SOURCE_LABEL_ZH[w.source]) : (w.source || t(w.persisted ? "badge.saved" : "badge.auto"));
501
+ var isEditing = editing && editing.key === key;
502
+ var badgeEl = react.createElement("span", { style: w.persisted ? savedBadgeStyle : autoBadgeStyle }, w.persisted ? t("badge.saved") : t("badge.auto"));
503
+ var titleRow = react.createElement("div", { style: rowStyle },
504
+ react.createElement("span", { style: wsTitleStyle }, w.title, badgeEl),
505
+ react.createElement("span", { style: wsMetaStyle }, t("ws.env") + ": " + w.env));
506
+ var metaRow = react.createElement("div", { style: rowStyle },
507
+ react.createElement("span", { style: srcStyle }, t("ws.srcVal") + ": " + (w.srcId || "")),
508
+ react.createElement("span", { style: wsMetaStyle }, t("ws.source") + ": " + sourceText));
509
+ var actionsRow = react.createElement("div", { style: rowStyle },
510
+ react.createElement("span", { style: wsMetaStyle }, w.dsn),
511
+ react.createElement("span", null,
512
+ react.createElement("button", { type: "button", style: toggleStyle, onClick: function () { setEditing(isEditing ? null : { key: key, dsn: "" }); } }, isEditing ? t("btn.cancel") : t("btn.edit")),
513
+ w.persisted ? react.createElement("button", { type: "button", style: dangerStyle, onClick: function () { props.configOp({ op: "remove", workspace: w.path, env: w.env }); } }, t("btn.delete")) : null));
514
+ var editRow = isEditing ? react.createElement("div", { style: rowStyle },
515
+ react.createElement("input", { style: wideInputStyle, placeholder: t("dsn.editPlaceholder"), value: editing.dsn, onChange: function (e) { setEditing(Object.assign({}, editing, { dsn: e.target.value })); } }),
516
+ react.createElement("button", { type: "button", style: Object.assign({}, toggleStyle, { marginLeft: "8px" }), onClick: function () { saveEdit(w); } }, t("btn.saved"))) : null;
517
+ return react.createElement("div", { key: key, style: wsRowStyle }, titleRow, metaRow, actionsRow, editRow);
518
+ });
519
+
520
+ if (wsRows.length === 0) {
521
+ wsRows.push(react.createElement("div", { key: "empty", style: wsMetaStyle, padding: "6px 0" }, t("ws.empty")));
522
+ }
523
+
524
+ var addRows = [
525
+ react.createElement("div", { key: "add-ws", style: rowStyle },
526
+ react.createElement("span", { style: labelStyle, title: t("add.ws") }, t("add.ws")),
527
+ react.createElement("input", { style: wideInputStyle, placeholder: t("ws.placeholder"), value: addForm.ws, onChange: function (e) { setAddForm(Object.assign({}, addForm, { ws: e.target.value })); } })),
528
+ react.createElement("div", { key: "add-env", style: rowStyle },
529
+ react.createElement("span", { style: labelStyle, title: t("add.env") }, t("add.env")),
530
+ react.createElement("input", { style: wideInputStyle, placeholder: t("env.placeholder"), value: addForm.env, onChange: function (e) { setAddForm(Object.assign({}, addForm, { env: e.target.value })); } })),
531
+ react.createElement("div", { key: "add-dsn", style: rowStyle },
532
+ react.createElement("span", { style: labelStyle }, t("add.dsn")),
533
+ react.createElement("input", { style: wideInputStyle, placeholder: t("dsn.placeholder"), value: addForm.dsn, onChange: function (e) { setAddForm(Object.assign({}, addForm, { dsn: e.target.value })); } })),
534
+ react.createElement("div", { key: "add-save", style: saveRowStyle },
535
+ react.createElement("button", { type: "button", style: toggleStyle, onClick: addDsn }, t("btn.add"))),
536
+ ];
537
+
538
+ return react.createElement("div", {
539
+ style: cardStateStyle,
540
+ onMouseEnter: function () { setHover(true); },
541
+ onMouseLeave: function () { setHover(false); },
542
+ },
543
+ header,
544
+ react.createElement("div", { style: bodyStyle },
545
+ react.createElement("div", { style: blockTitleStyle }, t("block.status")),
546
+ react.createElement("div", { style: statGridStyle }, statusCells),
547
+ react.createElement("div", { style: blockTitleStyle }, t("block.config")),
548
+ react.createElement("div", { style: groupStyle }, configRows),
549
+ react.createElement("div", { style: blockTitleStyle }, t("block.workspaces")),
550
+ wsRows,
551
+ react.createElement("div", { style: blockTitleStyle }, t("block.add")),
552
+ addRows));
553
+ }
554
+
555
+ function apply(ctx) {
556
+ console.log("[dsh-dbhub-live] client apply");
557
+ var localeSvc = ctx.get("locale");
558
+ if (localeSvc) localeSvc.register(NS, { zh: LOCALE_ZH, en: LOCALE_EN });
559
+ var t = localeSvc ? localeSvc.bind(NS) : tForZh;
560
+ var settingsScope = ctx.settingsScope;
561
+ var host = settingsScope.bind({ namespace: NS });
562
+ var noop = function () { return undefined; };
563
+ var face = {
564
+ t: t,
565
+ getSnapshot: function () { return host.getSnapshot(); },
566
+ subscribe: function (fn) { return host.subscribe(fn); },
567
+ setEnabled: function (value) {
568
+ return host.set("enabled", Boolean(value)).then(noop, noop);
569
+ },
570
+ saveConfig: function (patch) {
571
+ // One path-op write per field; invalid values are rejected host-side
572
+ // (applyPatch backstop) and never break the other fields.
573
+ var keys = patch && typeof patch === "object" ? Object.keys(patch) : [];
574
+ var chain = Promise.resolve();
575
+ for (var i = 0; i < keys.length; i++) {
576
+ (function (key) {
577
+ var raw = patch[key];
578
+ if (raw === undefined || raw === null) return;
579
+ chain = chain.then(function () { return host.set(key, raw).then(noop, noop); });
580
+ })(keys[i]);
581
+ }
582
+ return chain;
583
+ },
584
+ configOp: function (op) {
585
+ // One-way workspace-connection command; the Host applies it, then the
586
+ // republished mirror (without configOp) clears the field.
587
+ return host.set("configOp", JSON.stringify(op || {})).then(noop, noop);
588
+ },
589
+ };
590
+ ctx.slots.inject("settings.plugin.item", function () {
591
+ var reg = ctx.slots.register({ name: "settings.plugin.item", key: NS, inject: function () { return face; } }, StatusCard);
592
+ console.log("[dsh-dbhub-live] card 已注册");
593
+ return reg;
594
+ });
595
+ }
596
+
597
+ var name = "dsh-dbhub-live";
598
+ var inject = ["slots", "settingsScope"];
599
+
600
+ exports.apply = apply;
601
+ exports.inject = inject;
602
+ exports.name = name;
603
+ return module.exports;
604
+ }});