dsh-dbhub-live 3.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 CHANGED
@@ -1,18 +1,18 @@
1
1
  // dsh-dbhub-live — browser half (hand-authored lazy-CJS client bundle).
2
2
  //
3
- // Packaging contract (see docs/cookbook/adding-a-settings-card.md and
4
- // packages/client/modules): the Node client-modules scanner serves
3
+ // Packaging contract: the Node client-modules scanner serves
5
4
  // `exports["./client"]` for every Loader entry declaring `dsh.client`; the
6
5
  // script must register a lazy CJS factory through `window.__ModuleLoader__.load`
7
6
  // whose `id` equals the Loader entry name. The factory receives the shared
8
7
  // `require` (React is a seeded baseline module) and returns the Cordis plugin
9
8
  // object — `name` / `inject` / `apply`.
10
9
  //
11
- // The card renders the plugin's live status from the `dsh-dbhub-live` settings
12
- // namespace (mirrored by the Host half) and writes the enable/disable toggle
13
- // back through the namespace's `enabled` field. No other plugin value is
14
- // imported: all cross-package collaboration goes through the slot + settings
15
- // services by name.
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
16
 
17
17
  window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
18
18
 
@@ -20,59 +20,282 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
20
20
  var exports = module.exports;
21
21
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
22
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, "▼"); };
23
27
 
24
28
  var NS = "dsh-dbhub-live";
25
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
+
26
147
  var PHASE_META = {
27
- initializing: { emoji: "🟡", label: "初始化中", color: "#b58900" },
28
- running: { emoji: "🟢", label: "运行中", color: "#2f9e44" },
29
- error: { emoji: "🔴", label: "异常", color: "#e03131" },
30
- disabled: { emoji: "⚪", label: "已禁用", color: "#868e96" },
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" },
31
152
  };
32
153
 
33
- var MODE_LABEL = { lazy: "懒加载(首次调用时初始化)" };
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
+ };
34
160
 
35
- // Normalize a settingsScope snapshot into the namespace value object.
36
161
  function valueOf(snapshot) {
37
162
  if (!snapshot || typeof snapshot !== "object") return {};
38
163
  var value = snapshot.value;
39
164
  return value && typeof value === "object" ? value : {};
40
165
  }
41
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
+
42
192
  var cardStyle = {
43
- border: "1px solid #d0d7de",
44
- borderRadius: "8px",
45
- padding: "12px 14px",
193
+ border: "1px solid " + TOK.borderL2,
194
+ borderRadius: "12px",
46
195
  fontFamily: "inherit",
47
196
  fontSize: "13px",
48
- color: "#1f2328",
49
- background: "#ffffff",
50
- maxWidth: "440px",
51
- display: "flex",
52
- flexDirection: "column",
53
- gap: "2px",
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",
54
203
  };
55
204
 
56
205
  var headStyle = {
57
206
  display: "flex",
58
207
  alignItems: "center",
59
- justifyContent: "space-between",
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",
60
225
  fontWeight: 600,
61
- fontSize: "14px",
62
- paddingBottom: "6px",
63
- borderBottom: "1px solid #eef1f4",
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",
64
283
  };
65
284
 
285
+ var statLabelStyle = { color: TOK.labelTer, fontSize: "11px" };
286
+
287
+ var statValueStyle = { color: TOK.labelPrimary, fontSize: "13px", fontWeight: 600, wordBreak: "break-all" };
288
+
66
289
  var rowStyle = {
67
290
  display: "flex",
68
291
  alignItems: "center",
69
292
  justifyContent: "space-between",
70
- padding: "7px 0",
293
+ padding: "6px 0",
71
294
  };
72
295
 
73
- var labelStyle = { color: "#57606a" };
296
+ var labelStyle = { color: "#57606a", marginRight: "12px" };
74
297
 
75
- var valueStyle = { fontWeight: 600, textAlign: "right", marginLeft: "12px" };
298
+ var valueStyle = { fontWeight: 600, textAlign: "right" };
76
299
 
77
300
  var errorStyle = {
78
301
  color: "#e03131",
@@ -86,17 +309,59 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
86
309
  cursor: "pointer",
87
310
  borderRadius: "6px",
88
311
  border: "1px solid #d0d7de",
89
- padding: "5px 12px",
312
+ padding: "4px 12px",
90
313
  fontSize: "13px",
91
314
  color: "#1f2328",
92
315
  background: "#f6f8fa",
93
316
  };
94
317
 
95
- var toggleOnStyle = Object.assign({}, toggleStyle, {
96
- color: "#2f9e44",
97
- borderColor: "#b7d8bf",
98
- background: "#ecf7ef",
99
- });
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) ──
100
365
 
101
366
  function StatusCard(props) {
102
367
  var loaded = react.useState(function () { return valueOf(props.getSnapshot()); });
@@ -107,63 +372,225 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
107
372
  return props.subscribe(function () { setValue(valueOf(props.getSnapshot())); });
108
373
  }, []);
109
374
 
375
+ var openState = react.useState(false);
376
+ var open = openState[0];
377
+ var setOpen = openState[1];
378
+
379
+ var t = props.t || tForZh;
110
380
  var enabled = value.enabled !== false;
111
381
  var phase = enabled ? (value.phase || "initializing") : "disabled";
112
382
  var meta = PHASE_META[phase] || PHASE_META.initializing;
113
383
  var toolCount = typeof value.toolCount === "number" ? value.toolCount : 0;
114
384
  var lastError = typeof value.lastError === "string" && value.lastError ? value.lastError : "";
115
- var modeLabel = MODE_LABEL[value.mode] || (value.mode || "lazy");
116
-
117
- var rows = [
118
- react.createElement("div", { key: "phase", style: rowStyle },
119
- react.createElement("span", { style: labelStyle }, "运行状态"),
120
- react.createElement("span", { style: Object.assign({}, valueStyle, { color: meta.color }) },
121
- meta.emoji + " " + meta.label)),
122
- react.createElement("div", { key: "tools", style: rowStyle },
123
- react.createElement("span", { style: labelStyle }, "已注册工具"),
124
- react.createElement("span", { style: valueStyle }, String(toolCount) + " 个")),
125
- react.createElement("div", { key: "mode", style: rowStyle },
126
- react.createElement("span", { style: labelStyle }, "工作模式"),
127
- react.createElement("span", { style: valueStyle }, modeLabel)),
128
- ];
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
+ }
129
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
+ ];
130
474
  if (lastError) {
131
- rows.push(
132
- react.createElement("div", { key: "error", style: rowStyle },
133
- react.createElement("span", { style: labelStyle }, "最近错误"),
134
- react.createElement("span", { style: Object.assign({}, errorStyle, valueStyle) }, lastError)),
135
- );
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")));
136
522
  }
137
523
 
138
- rows.push(
139
- react.createElement("div", { key: "toggle", style: Object.assign({}, rowStyle, { paddingTop: "8px", borderTop: "1px solid #eef1f4" }) },
140
- react.createElement("span", { style: labelStyle }, enabled ? "插件已启用" : "插件已禁用"),
141
- react.createElement("button", {
142
- type: "button",
143
- style: enabled ? toggleOnStyle : toggleStyle,
144
- onClick: function () { props.setEnabled(!enabled); },
145
- }, enabled ? "禁用" : "启用")),
146
- );
147
-
148
- return react.createElement("div", { style: cardStyle },
149
- react.createElement("div", { style: headStyle },
150
- react.createElement("span", null, "dsh-dbhub-live"),
151
- react.createElement("span", null, meta.emoji)),
152
- rows);
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));
153
553
  }
154
554
 
155
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;
156
560
  var settingsScope = ctx.settingsScope;
157
561
  var host = settingsScope.bind({ namespace: NS });
562
+ var noop = function () { return undefined; };
158
563
  var face = {
564
+ t: t,
159
565
  getSnapshot: function () { return host.getSnapshot(); },
160
566
  subscribe: function (fn) { return host.subscribe(fn); },
161
567
  setEnabled: function (value) {
162
- return host.set("enabled", Boolean(value)).then(function () { return undefined; }, function () { return undefined; });
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);
163
588
  },
164
589
  };
165
590
  ctx.slots.inject("settings.plugin.item", function () {
166
- return ctx.slots.register({ name: "settings.plugin.item", key: NS, inject: function () { return face; } }, StatusCard);
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;
167
594
  });
168
595
  }
169
596