dsh-dbhub-live 5.1.0 → 5.2.0
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/AGENTS.md +95 -233
- package/README.en.md +96 -11
- package/README.md +97 -12
- package/doc/ARCHITECTURE.md +108 -0
- package/doc/CLIENT.md +124 -0
- package/doc/DESENSITIZE.md +56 -0
- package/doc/DEVELOPMENT.md +139 -0
- package/doc/IMPACT-MATRIX.md +36 -0
- package/doc/RELEASE.md +93 -0
- package/doc/REQUIREMENTS.md +41 -3
- package/lib/client.js +704 -27
- package/lib/config.mjs +3 -0
- package/lib/i18n.mjs +14 -4
- package/lib/index.mjs +107 -0
- package/lib/meta.mjs +267 -0
- package/lib/options.mjs +16 -1
- package/lib/policy.mjs +92 -0
- package/lib/tools.mjs +4 -1
- package/lib/update.mjs +244 -0
- package/package.json +32 -9
- package/scripts/desensitize-check.mjs +331 -324
- package/scripts/desensitize.config.mjs +18 -10
- package/scripts/docs-check.mjs +214 -0
- package/scripts/release-preflight.mjs +180 -0
- package/scripts/render-check.mjs +286 -2
- package/test/client-format.test.mjs +89 -5
- package/test/meta.test.mjs +234 -0
- package/test/options.test.mjs +28 -15
- package/test/policy.test.mjs +132 -0
- package/test/update.test.mjs +270 -0
package/lib/client.js
CHANGED
|
@@ -41,10 +41,49 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
41
41
|
}
|
|
42
42
|
var ChevronIcon = pickIcon("IconChevronDownOutline14", "▼");
|
|
43
43
|
var CopyIcon = pickIcon("IconCopyOutline16", "⧉");
|
|
44
|
-
var EditIcon = pickIcon("IconEditOutline16", "✏");
|
|
45
44
|
var CloseIcon = pickIcon("IconCloseOutline16", "✕");
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
// Row-action glyphs are drawn here instead of taken from the icon set: at 1x the
|
|
46
|
+
// bundled edit glyph reads as an unidentifiable dash, while these three shapes
|
|
47
|
+
// (filled play, pencil, lidded trash) stay recognisable at 16px on any DPI.
|
|
48
|
+
var svgIcon = function (children) {
|
|
49
|
+
return function (props) {
|
|
50
|
+
var size = (props && props.size) || 16;
|
|
51
|
+
return h("svg", {
|
|
52
|
+
className: "dbh-svgicon",
|
|
53
|
+
width: size, height: size, viewBox: "0 0 16 16",
|
|
54
|
+
fill: "none", stroke: "currentColor", strokeWidth: "1.5",
|
|
55
|
+
strokeLinecap: "round", strokeLinejoin: "round",
|
|
56
|
+
"aria-hidden": true, focusable: false,
|
|
57
|
+
}, children);
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
var RunIcon = svgIcon(h("path", {
|
|
61
|
+
key: "play", d: "M5.2 3.3v9.4L12.6 8 5.2 3.3z",
|
|
62
|
+
fill: "currentColor", stroke: "none",
|
|
63
|
+
}));
|
|
64
|
+
var EditIcon = svgIcon([
|
|
65
|
+
h("path", { key: "body", d: "M10.9 2.6a1.6 1.6 0 0 1 2.3 2.3L5.6 12.5l-3 .7.7-3 7.6-7.6z" }),
|
|
66
|
+
h("path", { key: "tip", d: "M9.7 3.8l2.3 2.3" }),
|
|
67
|
+
]);
|
|
68
|
+
var TrashIcon = svgIcon([
|
|
69
|
+
h("path", { key: "lid", d: "M3 4.5h10" }),
|
|
70
|
+
h("path", { key: "handle", d: "M6.3 4.5V3.1h3.4v1.4" }),
|
|
71
|
+
h("path", { key: "body", d: "M4.5 4.5l.6 8.6h5.8l.6-8.6" }),
|
|
72
|
+
h("path", { key: "line1", d: "M6.6 7v4.2" }),
|
|
73
|
+
h("path", { key: "line2", d: "M9.4 7v4.2" }),
|
|
74
|
+
]);
|
|
75
|
+
// Read-only state: the user asked for the universally recognised emoji lock
|
|
76
|
+
// pair — 🔓 when writable, 🔒 when writes are blocked. The hand-drawn 16px
|
|
77
|
+
// shackle was too small to tell open from closed at a glance.
|
|
78
|
+
// The refresh glyph is the update check's affordance: a named primitive when
|
|
79
|
+
// the running dsh ships it, a text glyph otherwise (a rename must not blank
|
|
80
|
+
// the button).
|
|
81
|
+
var RefreshIcon = pickIcon("IconRefreshOutlineRegular", "↻");
|
|
82
|
+
// The platform's own toggle: same geometry, tokens and 120ms thumb animation
|
|
83
|
+
// as the harness settings pages, so it follows every theme/skin. A local
|
|
84
|
+
// fallback with the same geometry keeps the control alive if a baseline build
|
|
85
|
+
// predates the primitive.
|
|
86
|
+
var SwitchPrimitive = primitives && typeof primitives.Switch === "function" ? primitives.Switch : null;
|
|
48
87
|
|
|
49
88
|
var NS = "dsh-dbhub-live";
|
|
50
89
|
|
|
@@ -67,9 +106,57 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
67
106
|
"cfg.off": "隐藏",
|
|
68
107
|
"cfg.update": "自动更新间隔(天)",
|
|
69
108
|
"cfg.sidebar": "在侧边栏显示入口",
|
|
109
|
+
"cfg.policy": "向模型注入数据库访问策略",
|
|
110
|
+
"cfg.policyHint": "开启后,系统提示词会声明「本插件已配置的连接一律走 dbhub_* 工具,不要用命令行客户端/脚本/驱动或其它插件绕过」,并说明本插件不支持的库可以用你安装的其它数据库插件。关闭只是不再注入这段文字——工具、只读与其它设置全部照常工作。",
|
|
70
111
|
"cfg.sidebarHint": "关闭后左侧栏不再出现「DBHub 数据库工具」快捷入口——但插件照常工作(工具仍可用),本设置页与插件页也始终可进入。注意:点「禁用」会让入口无条件消失,与这个开关无关。",
|
|
71
112
|
"cfg.enabledHint": "侧边栏入口 = 本开关 且 插件处于启用状态。",
|
|
72
113
|
"cfg.disabledHint": "插件已禁用:侧边栏入口已隐藏(与上面的开关无关)。本设置页与插件页仍然可用——点上方的「启用」即可恢复。",
|
|
114
|
+
"cfg.enableHint": "停用后模型无法调用本插件的工具;连接与配置全部保留,随时可以再启用。",
|
|
115
|
+
"cfg.updateHint": "dbhub 可执行文件的自动更新间隔(天);0 = 不自动更新。",
|
|
116
|
+
// Modern surfaces (5.2 redesign): one compact status line, DSH-style settings
|
|
117
|
+
// rows, an on-demand update link, and a collapsed technical-details block.
|
|
118
|
+
"status.conns": "{n} 个连接",
|
|
119
|
+
"settings.title": "插件设置",
|
|
120
|
+
"settings.toggle": "设置",
|
|
121
|
+
"settings.more": "说明与技术详情",
|
|
122
|
+
"settings.notes": "说明",
|
|
123
|
+
"settings.less": "收起说明",
|
|
124
|
+
"row.enable": "启用插件",
|
|
125
|
+
"row.sidebar": "侧边栏入口",
|
|
126
|
+
"row.policy": "向模型注入访问策略",
|
|
127
|
+
"row.update": "dbhub 自动更新",
|
|
128
|
+
"state.enable": "启用",
|
|
129
|
+
"state.disable": "停用",
|
|
130
|
+
"state.show": "显示",
|
|
131
|
+
"state.hide": "隐藏",
|
|
132
|
+
"state.on": "开启",
|
|
133
|
+
"state.off": "关闭",
|
|
134
|
+
"unit.days": "天",
|
|
135
|
+
"note.tools": "模型侧固定 {n} 个工具;每次调用使用一次性 dbhub 进程,无常驻服务。",
|
|
136
|
+
"note.update": "“dbhub 自动更新”只控制 dbhub 可执行文件;插件自身的版本更新走页头的升级提示。",
|
|
137
|
+
"note.sidebar": "关闭侧边栏入口只是隐藏快捷入口,插件仍然工作;停用插件才会让模型无法调用工具。",
|
|
138
|
+
"note.policy": "开启后,系统提示词会声明「本插件已配置的连接一律走 dbhub_* 工具,不要用命令行客户端/脚本/驱动或其它插件绕过」,并说明本插件不支持的库可以用你安装的其它数据库插件。关闭只是不再注入这段文字,工具、只读与其它设置全部照常工作。",
|
|
139
|
+
"note.version": "插件 v{plugin} · DSH {dsh} · {runtime}",
|
|
140
|
+
"ver.link": "可升级到 {v}",
|
|
141
|
+
"ro.chipOnHint": "只读已开启,模型无法写入;点此关闭",
|
|
142
|
+
"ro.chipOffHint": "开启只读,阻止模型写入(模型只能开、不能关)",
|
|
143
|
+
"ro.chip": "只读",
|
|
144
|
+
"ro.writable": "可写",
|
|
145
|
+
"ver.copySpec": "点击复制 dsh-dbhub-live@{v}",
|
|
146
|
+
"ver.check": "检查更新",
|
|
147
|
+
"ver.checking": "检查中…",
|
|
148
|
+
"ver.checkFailed": "检查更新失败",
|
|
149
|
+
"ver.checkedAgo": "检查于 {n} 分钟前",
|
|
150
|
+
"ver.webHint": "Web 版:在终端执行 dsh plugin --profile web up dsh-dbhub-live@{v},然后重启 dsh web。",
|
|
151
|
+
"ver.desktopHint": "桌面版:打开左侧菜单 → 插件,卸载 dsh-dbhub-live 后重新安装最新版;连接与凭据保存在 ~/.dsh/storages/dsh-dbhub-live,不会被删除。",
|
|
152
|
+
"ver.genericHint": "Web 版执行 dsh plugin --profile web up dsh-dbhub-live@{v} 后重启 dsh web;桌面版在插件页卸载后重新安装。",
|
|
153
|
+
"ver.releaseNotes": "更新说明",
|
|
154
|
+
"ver.copy": "复制",
|
|
155
|
+
"ver.copyCmd": "复制命令",
|
|
156
|
+
"ver.runtimeDesktop": "桌面版",
|
|
157
|
+
"ver.runtimeWeb": "Web 版",
|
|
158
|
+
"ro.turnOn": "开启只读",
|
|
159
|
+
"ro.turnOff": "关闭只读",
|
|
73
160
|
"cap.tooOld": "dbhub {v} 不支持只读 / SSH 隧道(需要 1.4.0 及以上)",
|
|
74
161
|
"cap.untested": "dbhub {v} 未经验证(只读 / SSH 在 1.4.0+ 实测)",
|
|
75
162
|
"add.readOnly": "只读模式",
|
|
@@ -169,9 +256,55 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
169
256
|
"cfg.off": "hidden",
|
|
170
257
|
"cfg.update": "Update interval (days)",
|
|
171
258
|
"cfg.sidebar": "Show the sidebar entry",
|
|
259
|
+
"cfg.policy": "Announce the database-access policy to the model",
|
|
260
|
+
"cfg.policyHint": "When on, the system prompt states that this plugin's configured connections go through the dbhub_* tools (no command-line client, script, driver or other plugin detour) and that databases it does not cover may use your other database plugin. Turning it off only removes that text — tools, read-only and every other setting keep working.",
|
|
172
261
|
"cfg.sidebarHint": "When off, the “DBHub Database Tools” shortcut no longer appears in the sidebar — the plugin keeps working (its tools stay available) and this settings page plus the Plugins row stay reachable. Note: pressing Disable hides the entry unconditionally, independently of this switch.",
|
|
173
262
|
"cfg.enabledHint": "The sidebar entry = this switch AND the plugin being enabled.",
|
|
174
263
|
"cfg.disabledHint": "The plugin is disabled, so the sidebar entry is hidden (independently of the switch above). This settings page and the Plugins row still work — press Enable above to bring it back.",
|
|
264
|
+
"cfg.enableHint": "While disabled the model cannot call this plugin's tools; every connection and setting is kept and can be re-enabled at any time.",
|
|
265
|
+
"cfg.updateHint": "Auto-update interval (days) for the dbhub executable; 0 = never update automatically.",
|
|
266
|
+
"status.conns": "{n} connections",
|
|
267
|
+
"settings.title": "Plugin settings",
|
|
268
|
+
"settings.toggle": "Settings",
|
|
269
|
+
"settings.more": "Details & diagnostics",
|
|
270
|
+
"settings.notes": "Details",
|
|
271
|
+
"settings.less": "Hide details",
|
|
272
|
+
"row.enable": "Enable plugin",
|
|
273
|
+
"row.sidebar": "Sidebar entry",
|
|
274
|
+
"row.policy": "Inject database access policy into the model",
|
|
275
|
+
"row.update": "dbhub auto-update",
|
|
276
|
+
"state.enable": "Enabled",
|
|
277
|
+
"state.disable": "Disabled",
|
|
278
|
+
"state.show": "Shown",
|
|
279
|
+
"state.hide": "Hidden",
|
|
280
|
+
"state.on": "On",
|
|
281
|
+
"state.off": "Off",
|
|
282
|
+
"unit.days": "days",
|
|
283
|
+
"note.tools": "The model always sees {n} tools; every call runs a one-shot dbhub process (no resident service).",
|
|
284
|
+
"note.update": "The dbhub auto-update interval only controls the dbhub executable; the plugin's own update is offered by the header link.",
|
|
285
|
+
"note.sidebar": "Hiding the sidebar entry only removes the shortcut — the plugin keeps working. Only disabling the plugin stops the model from calling its tools.",
|
|
286
|
+
"note.policy": "When on, the system prompt states that every connection configured here goes through the dbhub_* tools (no CLI clients, scripts, drivers or other plugins), and that databases this plugin does not support may use other database plugins the user installed. Turning it off removes only that text; tools, read-only and every other setting keep working.",
|
|
287
|
+
"note.version": "Plugin v{plugin} · DSH {dsh} · {runtime}",
|
|
288
|
+
"ver.link": "Upgrade to {v}",
|
|
289
|
+
"ro.chipOnHint": "Read-only is on and the model cannot write; click to turn it off",
|
|
290
|
+
"ro.chipOffHint": "Turn read-only on to block model writes (the model can only turn it on, never off)",
|
|
291
|
+
"ro.chip": "RO",
|
|
292
|
+
"ro.writable": "Writable",
|
|
293
|
+
"ver.copySpec": "Click to copy dsh-dbhub-live@{v}",
|
|
294
|
+
"ver.check": "Check for updates",
|
|
295
|
+
"ver.checking": "Checking…",
|
|
296
|
+
"ver.checkFailed": "Update check failed",
|
|
297
|
+
"ver.checkedAgo": "Checked {n} min ago",
|
|
298
|
+
"ver.webHint": "Web: run dsh plugin --profile web up dsh-dbhub-live@{v} in a terminal, then restart dsh web.",
|
|
299
|
+
"ver.desktopHint": "Desktop: open Plugins in the left menu, uninstall dsh-dbhub-live, then install the latest version again. Connections and credentials live in ~/.dsh/storages/dsh-dbhub-live and are preserved.",
|
|
300
|
+
"ver.genericHint": "Web: run dsh plugin --profile web up dsh-dbhub-live@{v} and restart dsh web. Desktop: uninstall the plugin on the Plugins page, then reinstall it.",
|
|
301
|
+
"ver.releaseNotes": "Release notes",
|
|
302
|
+
"ver.copy": "Copy",
|
|
303
|
+
"ver.copyCmd": "Copy command",
|
|
304
|
+
"ver.runtimeDesktop": "Desktop",
|
|
305
|
+
"ver.runtimeWeb": "Web",
|
|
306
|
+
"ro.turnOn": "Turn read-only on",
|
|
307
|
+
"ro.turnOff": "Turn read-only off",
|
|
175
308
|
"cap.tooOld": "dbhub {v} does not support read-only / SSH tunnels (1.4.0 or newer required)",
|
|
176
309
|
"cap.untested": "dbhub {v} is untested (read-only / SSH were measured on 1.4.0+)",
|
|
177
310
|
"add.readOnly": "Read-only mode",
|
|
@@ -367,8 +500,8 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
367
500
|
".dbh-iconbtn:disabled{cursor:default;}",
|
|
368
501
|
".dbh-iconbtn-busy{opacity:.5;}",
|
|
369
502
|
".dbh-iconbtn-danger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover-danger);}",
|
|
370
|
-
".dbh-sect{display:flex;align-items:center;gap:8px;padding:
|
|
371
|
-
".dbh-secttitle{font-size:
|
|
503
|
+
".dbh-sect{display:flex;align-items:center;gap:8px;padding:14px 0 2px;flex-wrap:wrap;}",
|
|
504
|
+
".dbh-secttitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);letter-spacing:.01em;}",
|
|
372
505
|
".dbh-sectcount{font-size:11px;color:var(--dsw-alias-label-tertiary);}",
|
|
373
506
|
".dbh-grow{flex:1;min-width:0;}",
|
|
374
507
|
// Level 1: one bordered card per workspace. Transparent surface + hairline
|
|
@@ -398,6 +531,7 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
398
531
|
".dbh-conn-pre{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);}",
|
|
399
532
|
".dbh-conn-db{flex:none;color:var(--dsw-alias-label-primary);font-weight:600;}",
|
|
400
533
|
".dbh-acts{display:flex;align-items:center;gap:2px;flex:none;margin-left:auto;}",
|
|
534
|
+
".dbh-svgicon{display:block;flex:none;pointer-events:none;}",
|
|
401
535
|
// The source handle: dashed outline + copy glyph says "id, click to copy".
|
|
402
536
|
".dbh-src{display:inline-flex;align-items:center;gap:4px;flex:none;height:22px;padding:0 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;color:var(--dsw-alias-label-secondary);background:transparent;border:1px dashed var(--dsw-alias-border-l2,rgba(127,127,127,.45));border-radius:6px;cursor:pointer;transition:background .14s,border-color .14s,color .14s;}",
|
|
403
537
|
".dbh-src:hover{background:var(--dsw-alias-interactive-bg-hover);border-style:solid;color:var(--dsw-alias-label-primary);}",
|
|
@@ -457,6 +591,115 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
457
591
|
".dbh-warncard{display:flex;flex-direction:column;gap:6px;padding:10px;border:1px solid var(--dsw-alias-state-warn-primary,rgba(127,127,127,.6));border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent);}",
|
|
458
592
|
".dbh-warncard-title{font-weight:600;font-size:13px;color:var(--dsw-alias-state-warn-primary);}",
|
|
459
593
|
".dbh-summary{font-size:12px;color:var(--dsw-alias-label-secondary);}",
|
|
594
|
+
// ── modern surfaces (dsh >= 0.2.1) ──────────────────────────────────────
|
|
595
|
+
// One component, three surfaces: the settings dialog (narrow, unchanged from
|
|
596
|
+
// the 0.1.x layout), the wide page reached from the left menu / the Plugins
|
|
597
|
+
// row, and a phone browser. The page classes come from the MEASURED
|
|
598
|
+
// container width, so a docked desktop window degrades like a phone does.
|
|
599
|
+
".dbh-titlebar{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;padding:2px 0 2px;}",
|
|
600
|
+
".dbh-title{margin:0;font-size:18px;font-weight:600;line-height:1.25;color:var(--dsw-alias-label-primary);}",
|
|
601
|
+
".dbh-ver{flex:none;font-size:12px;color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;}",
|
|
602
|
+
// Update affordance: a quiet text link (brand colour only) that expands into a
|
|
603
|
+
// neutral panel on demand — never a red/error-styled banner.
|
|
604
|
+
".dbh-updatelink{display:inline-flex;align-items:center;gap:3px;height:22px;padding:0 4px;font-size:12px;font-family:inherit;line-height:1;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--dsw-alias-brand-text);cursor:pointer;}",
|
|
605
|
+
".dbh-updatelink:hover{text-decoration:underline;}",
|
|
606
|
+
".dbh-updatelink:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px;}",
|
|
607
|
+
".dbh-vercheck{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;padding:0;font-size:11px;font-family:inherit;line-height:1;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer;}",
|
|
608
|
+
".dbh-vercheck:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
|
|
609
|
+
".dbh-vercheck:disabled{cursor:default;opacity:.7;}",
|
|
610
|
+
".dbh-vercheck:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px;}",
|
|
611
|
+
// One status line: running/disabled + how many connections. Tool count and the
|
|
612
|
+
// one-shot execution mode are implementation facts and live in the notes.
|
|
613
|
+
".dbh-statusline{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);padding:0 0 6px;}",
|
|
614
|
+
".dbh-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-state-success-primary);}",
|
|
615
|
+
".dbh-dot-off{background:var(--dsw-alias-state-idle-primary);}",
|
|
616
|
+
".dbh-statussep{color:var(--dsw-alias-label-tertiary);}",
|
|
617
|
+
".dbh-linkbtn{display:inline-flex;align-items:center;gap:3px;padding:0 4px;height:20px;border:1px solid transparent;border-radius:6px;background:transparent;font-family:inherit;font-size:12px;line-height:1;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;}",
|
|
618
|
+
".dbh-linkbtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
|
|
619
|
+
".dbh-linkbtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px;}",
|
|
620
|
+
".dbh-updatepanel{display:flex;flex-direction:column;gap:6px;margin:0 0 8px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3));border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent);}",
|
|
621
|
+
".dbh-updaterow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0;}",
|
|
622
|
+
".dbh-spec{flex:1 1 auto;min-width:0;padding:3px 8px;border-radius:6px;background:var(--dsw-alias-bg-layer-3,transparent);border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3));font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;color:var(--dsw-alias-label-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
|
|
623
|
+
".dbh-link{font-size:11px;color:var(--dsw-alias-brand-text);text-decoration:none;align-self:flex-start;}",
|
|
624
|
+
".dbh-link:hover{text-decoration:underline;}",
|
|
625
|
+
// Settings: the harness's own row pattern — title left, control right, thin
|
|
626
|
+
// separators, no card/grid, no coloured chrome of our own.
|
|
627
|
+
".dbh-inner.dbh-wide{max-width:1320px;gap:12px;}",
|
|
628
|
+
".dbh-wide .dbh-tiles{grid-template-columns:repeat(auto-fill,minmax(320px,1fr));}",
|
|
629
|
+
".dbh-settings{display:flex;flex-direction:column;}",
|
|
630
|
+
".dbh-settings-title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary);letter-spacing:.02em;padding:2px 0 2px;}",
|
|
631
|
+
// Wide page: all four controls on one quiet line — the labels stay attached
|
|
632
|
+
// to their switches instead of a label at the far left and a control at the
|
|
633
|
+
// far right with half a screen of empty middle.
|
|
634
|
+
".dbh-settingsbar{padding:0 0 2px;}",
|
|
635
|
+
".dbh-barline{display:flex;align-items:center;gap:8px 22px;flex-wrap:wrap;padding:2px 0 4px;}",
|
|
636
|
+
".dbh-bartitle{font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary);letter-spacing:.02em;margin-right:2px;}",
|
|
637
|
+
".dbh-bargroup{display:inline-flex;align-items:center;gap:6px;flex:none;}",
|
|
638
|
+
".dbh-barlabel{font-size:12px;color:var(--dsw-alias-label-secondary);}",
|
|
639
|
+
".dbh-barstate{font-size:12px;color:var(--dsw-alias-label-tertiary);}",
|
|
640
|
+
".dbh-settingsbar [role=\"switch\"]{transform:scale(.85);}",
|
|
641
|
+
// Narrow desktop column: drop the section title and the state words, tighten
|
|
642
|
+
// the gaps, so the four controls still flow on one/two lines instead of
|
|
643
|
+
// collapsing into a vertical list.
|
|
644
|
+
".dbh-barnarrow .dbh-barline{gap:6px 12px;}",
|
|
645
|
+
".dbh-barnarrow .dbh-bartitle{display:none;}",
|
|
646
|
+
".dbh-barnarrow .dbh-barstate{display:none;}",
|
|
647
|
+
".dbh-barnarrow .dbh-barlabel{font-size:11px;}",
|
|
648
|
+
".dbh-barnarrow .dbh-srow-input{width:50px;}",
|
|
649
|
+
// Narrow surfaces: the same controls as compact rows (weak by design — the
|
|
650
|
+
// connection list below is the main content).
|
|
651
|
+
".dbh-srow{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:8px 0;border-bottom:0.5px solid var(--dsw-alias-border-l2,rgba(127,127,127,.3));}",
|
|
652
|
+
".dbh-srow:last-child{border-bottom:none;}",
|
|
653
|
+
".dbh-srow-main{min-width:0;}",
|
|
654
|
+
".dbh-srow-title{font-size:13px;line-height:18px;color:var(--dsw-alias-label-secondary);}",
|
|
655
|
+
".dbh-srow-ctl{display:flex;align-items:center;gap:8px;flex:none;flex-wrap:nowrap;}",
|
|
656
|
+
".dbh-srow-state{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);min-width:2.4em;text-align:right;white-space:nowrap;}",
|
|
657
|
+
".dbh-srow-input{width:58px;height:26px;flex:none;text-align:center;}",
|
|
658
|
+
".dbh-srow-unit{font-size:12px;color:var(--dsw-alias-label-secondary);}",
|
|
659
|
+
".dbh-btn-sm{height:26px;padding:0 10px;font-size:12px;}",
|
|
660
|
+
".dbh-settings-foot{display:flex;align-items:center;gap:8px;padding:6px 0 2px;}",
|
|
661
|
+
".dbh-notes{display:flex;flex-direction:column;gap:4px;margin:2px 0 4px;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent);font-size:12px;line-height:1.55;color:var(--dsw-alias-label-secondary);}",
|
|
662
|
+
// Read-only toggle: a quiet chip on the connection row, in EVERY layout, so it
|
|
663
|
+
// can be flipped while reading the list instead of opening the edit form.
|
|
664
|
+
".dbh-rochip{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 7px;flex:none;border-radius:999px;border:1px dashed var(--dsw-alias-border-l2,rgba(127,127,127,.45));background:transparent;color:var(--dsw-alias-label-tertiary);font-size:11px;font-family:inherit;line-height:1;cursor:pointer;transition:background .12s,border-color .12s,color .12s;}",
|
|
665
|
+
".dbh-rochip:hover:not(:disabled){border-color:var(--dsw-alias-border-l3,rgba(127,127,127,.6));color:var(--dsw-alias-label-secondary);}",
|
|
666
|
+
".dbh-rochip-on{border-style:solid;border-color:var(--dsw-alias-border-l3,rgba(127,127,127,.6));background:var(--dsw-alias-bg-layer-3,rgba(127,127,127,.16));color:var(--dsw-alias-label-primary);}",
|
|
667
|
+
".dbh-rochip:disabled{opacity:.55;cursor:default;}",
|
|
668
|
+
".dbh-rochip:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px;}",
|
|
669
|
+
".dbh-roemoji{font-size:13px;line-height:1;}",
|
|
670
|
+
// Fallback switch only when the baseline predates the Switch primitive: same
|
|
671
|
+
// 36x20 geometry and token colours as the platform control.
|
|
672
|
+
".dbh-fbswitch{position:relative;flex:none;width:36px;height:20px;padding:2px;border:0;border-radius:999px;background:var(--dsw-alias-border-l3,rgba(127,127,127,.45));cursor:pointer;transition:background .12s;}",
|
|
673
|
+
".dbh-fbswitch-on{background:var(--dsw-alias-brand-primary);}",
|
|
674
|
+
".dbh-fbswitch:disabled{opacity:.5;cursor:default;}",
|
|
675
|
+
".dbh-fbswitch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;}",
|
|
676
|
+
".dbh-fbswitch-thumb{display:block;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform .12s ease;}",
|
|
677
|
+
".dbh-fbswitch-on .dbh-fbswitch-thumb{transform:translateX(16px);}",
|
|
678
|
+
// Phone browser: one column, touch-sized controls, actions on their own row.
|
|
679
|
+
".dbh-inner.dbh-mobile{font-size:13px;gap:8px;}",
|
|
680
|
+
".dbh-mobile .dbh-titlebar{gap:6px;}",
|
|
681
|
+
".dbh-mobile .dbh-title{font-size:16px;}",
|
|
682
|
+
".dbh-mobile .dbh-statusline{font-size:12px;}",
|
|
683
|
+
".dbh-mobile .dbh-updatepanel{padding:8px 10px;}",
|
|
684
|
+
".dbh-mobile .dbh-envrow{gap:6px;}",
|
|
685
|
+
".dbh-mobile .dbh-envname{max-width:120px;}",
|
|
686
|
+
".dbh-mobile .dbh-conn{flex:1 1 100%;order:1;min-width:0;}",
|
|
687
|
+
".dbh-mobile .dbh-rochip{height:24px;padding:0 8px;order:2;}",
|
|
688
|
+
".dbh-mobile .dbh-roemoji{font-size:14px;}",
|
|
689
|
+
".dbh-mobile .dbh-acts{order:3;width:100%;margin-left:0;justify-content:flex-end;gap:6px;padding:2px 0 0;}",
|
|
690
|
+
".dbh-mobile .dbh-iconbtn{width:32px;height:32px;}",
|
|
691
|
+
".dbh-mobile .dbh-btn{height:30px;padding:0 12px;}",
|
|
692
|
+
".dbh-mobile .dbh-tiles{grid-template-columns:1fr;}",
|
|
693
|
+
".dbh-mobile .dbh-srow{gap:8px;padding:6px 0;}",
|
|
694
|
+
".dbh-mobile .dbh-srow-title{font-size:12px;}",
|
|
695
|
+
".dbh-mobile .dbh-srow-state{font-size:11px;min-width:2.2em;}",
|
|
696
|
+
".dbh-mobile .dbh-srow [role=\"switch\"]{transform:scale(.8);}",
|
|
697
|
+
".dbh-mobile .dbh-btn-sm{height:24px;padding:0 8px;font-size:11px;}",
|
|
698
|
+
".dbh-mobile .dbh-groupbody{margin:0 4px 6px 8px;padding-left:8px;}",
|
|
699
|
+
".dbh-mobile .dbh-editrow .dbh-envinput{flex:1 1 100%;}",
|
|
700
|
+
".dbh-mobile .dbh-editrow .dbh-input{flex:1 1 100%;}",
|
|
701
|
+
".dbh-mobile .dbh-srcid{max-width:72px;}",
|
|
702
|
+
".dbh-mobile .dbh-updatehead{align-items:flex-start;}",
|
|
460
703
|
].join("");
|
|
461
704
|
|
|
462
705
|
function ensureStyle() {
|
|
@@ -489,10 +732,24 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
489
732
|
// The settings section's page (also the sidebar panel's body): the same
|
|
490
733
|
// management page the Plugins page renders in its 'page' view, wrapped in its
|
|
491
734
|
// own scroll container. The Plugins-row mount keeps the shell's container.
|
|
735
|
+
// `surface` names the MOUNT, not the width: the settings dialog is a 760px
|
|
736
|
+
// modal, the left-menu entry and the Plugins row are full pages, and the
|
|
737
|
+
// card itself still measures its column before choosing a layout.
|
|
492
738
|
function ConfigPanel(props) {
|
|
493
739
|
ensureStyle();
|
|
740
|
+
var surface = props && typeof props.surface === "string" ? props.surface : "modal";
|
|
494
741
|
return h("div", { className: "dbh-wrap" },
|
|
495
|
-
h(StatusCard, Object.assign({}, props, { view: "page" })));
|
|
742
|
+
h(StatusCard, Object.assign({}, props, { view: "page", surface: surface })));
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/** Settings -> DBHub dialog (narrow, unchanged density). */
|
|
746
|
+
function SettingsSectionPanel(props) {
|
|
747
|
+
return h(ConfigPanel, Object.assign({}, props, { surface: "modal" }));
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/** Left-menu DBHub panel: a full page with most of the viewport available. */
|
|
751
|
+
function MainPanel(props) {
|
|
752
|
+
return h(ConfigPanel, Object.assign({}, props, { surface: "main" }));
|
|
496
753
|
}
|
|
497
754
|
|
|
498
755
|
// ── pure layout helpers ───────────────────────────────────────────────────
|
|
@@ -766,6 +1023,95 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
766
1023
|
};
|
|
767
1024
|
}
|
|
768
1025
|
|
|
1026
|
+
/**
|
|
1027
|
+
* Parse the Host's transient update snapshot (a JSON string). Always returns
|
|
1028
|
+
* the full shape so render code never has to guard field by field.
|
|
1029
|
+
* @returns `{ latest, latestSpec, hasUpdate, checkedAt, source, error }`
|
|
1030
|
+
*/
|
|
1031
|
+
function parseUpdate(value) {
|
|
1032
|
+
var out = { latest: "", latestSpec: "", hasUpdate: false, checkedAt: 0, source: "", error: "" };
|
|
1033
|
+
var raw = value && typeof value.update === "string" ? value.update : "";
|
|
1034
|
+
if (!raw) return out;
|
|
1035
|
+
try {
|
|
1036
|
+
var parsed = JSON.parse(raw);
|
|
1037
|
+
if (!parsed || typeof parsed !== "object") return out;
|
|
1038
|
+
out.latest = typeof parsed.latest === "string" ? parsed.latest.trim() : "";
|
|
1039
|
+
out.latestSpec = typeof parsed.latestSpec === "string" && parsed.latestSpec
|
|
1040
|
+
? parsed.latestSpec
|
|
1041
|
+
: (out.latest ? "dsh-dbhub-live@" + out.latest : "");
|
|
1042
|
+
out.hasUpdate = parsed.hasUpdate === true && !!out.latest;
|
|
1043
|
+
out.checkedAt = typeof parsed.checkedAt === "number" && parsed.checkedAt > 0 ? parsed.checkedAt : 0;
|
|
1044
|
+
out.source = typeof parsed.source === "string" ? parsed.source : "";
|
|
1045
|
+
out.error = typeof parsed.error === "string" ? parsed.error : "";
|
|
1046
|
+
} catch (e) {
|
|
1047
|
+
/* a malformed snapshot is simply "no update information" */
|
|
1048
|
+
}
|
|
1049
|
+
return out;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Measured width of the page's own content column. The same component serves
|
|
1054
|
+
* a 500px settings dialog, a full page and a phone browser, so the layout is
|
|
1055
|
+
* decided by how much room it actually has. ResizeObserver is absent in older
|
|
1056
|
+
* browsers and in the offscreen render gate; then the caller falls back to
|
|
1057
|
+
* `window.innerWidth` (or to the mount surface's default).
|
|
1058
|
+
*/
|
|
1059
|
+
function useMeasuredWidth(ref) {
|
|
1060
|
+
var state = react.useState(0);
|
|
1061
|
+
var setWidth = state[1];
|
|
1062
|
+
react.useEffect(function () {
|
|
1063
|
+
var el = ref && ref.current;
|
|
1064
|
+
if (typeof ResizeObserver !== "function" || !el) return undefined;
|
|
1065
|
+
var observer = new ResizeObserver(function (entries) {
|
|
1066
|
+
var entry = entries && entries[0];
|
|
1067
|
+
var w = entry && entry.contentRect ? entry.contentRect.width : 0;
|
|
1068
|
+
if (w > 0) setWidth(Math.round(w));
|
|
1069
|
+
});
|
|
1070
|
+
try {
|
|
1071
|
+
observer.observe(el);
|
|
1072
|
+
} catch (e) {
|
|
1073
|
+
return undefined;
|
|
1074
|
+
}
|
|
1075
|
+
return function () {
|
|
1076
|
+
try { observer.disconnect(); } catch (e) { /* disposal must never throw */ }
|
|
1077
|
+
};
|
|
1078
|
+
}, []);
|
|
1079
|
+
return state[0];
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* The 显示/隐藏 style switch used by the modern surfaces. A native checkbox
|
|
1084
|
+
* stays in the DOM (keyboard + screen-reader semantics, and the offscreen gate
|
|
1085
|
+
* can drive it); the track/knob are decoration.
|
|
1086
|
+
*/
|
|
1087
|
+
function switchControl(opts) {
|
|
1088
|
+
var o = opts || {};
|
|
1089
|
+
var label = o.ariaLabel || o.label || "";
|
|
1090
|
+
if (SwitchPrimitive) {
|
|
1091
|
+
return h(SwitchPrimitive, {
|
|
1092
|
+
checked: o.checked === true,
|
|
1093
|
+
onChange: function (next) { if (typeof o.onChange === "function") o.onChange(next); },
|
|
1094
|
+
label: label,
|
|
1095
|
+
disabled: o.disabled === true,
|
|
1096
|
+
title: o.title || "",
|
|
1097
|
+
className: o.className,
|
|
1098
|
+
key: o.key,
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
// Fallback with the same 36x20 geometry and token colours as the primitive.
|
|
1102
|
+
return h("button", {
|
|
1103
|
+
type: "button",
|
|
1104
|
+
role: "switch",
|
|
1105
|
+
"aria-checked": o.checked === true,
|
|
1106
|
+
"aria-label": label,
|
|
1107
|
+
title: o.title || "",
|
|
1108
|
+
disabled: o.disabled === true,
|
|
1109
|
+
className: "dbh-fbswitch" + (o.checked === true ? " dbh-fbswitch-on" : "") + (o.className ? " " + o.className : ""),
|
|
1110
|
+
key: o.key,
|
|
1111
|
+
onClick: function () { if (typeof o.onChange === "function") o.onChange(o.checked !== true); },
|
|
1112
|
+
}, h("span", { className: "dbh-fbswitch-thumb" }));
|
|
1113
|
+
}
|
|
1114
|
+
|
|
769
1115
|
// ── the page ──────────────────────────────────────────────────────────────
|
|
770
1116
|
|
|
771
1117
|
function StatusCard(props) {
|
|
@@ -809,6 +1155,79 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
809
1155
|
meta.emoji + " " + t(meta.key) + " · " + t("tools.fixed", { n: String(toolCount) }) + " · " + summary);
|
|
810
1156
|
}
|
|
811
1157
|
|
|
1158
|
+
// ── modern surfaces (dsh >= 0.2.1) ───────────────────────────────────────
|
|
1159
|
+
//
|
|
1160
|
+
// The Host decides `dshModern` from the RUNNING dsh version (lib/meta.mjs):
|
|
1161
|
+
// on 0.1.x every flag below stays false and the page renders exactly the old
|
|
1162
|
+
// DOM, which the offscreen gate's legacy assertions pin.
|
|
1163
|
+
var modern = value.dshModern === true;
|
|
1164
|
+
var surface = props.surface === "main" || props.surface === "modal" || props.surface === "page"
|
|
1165
|
+
? props.surface
|
|
1166
|
+
: "page";
|
|
1167
|
+
var pluginVersion = typeof value.pluginVersion === "string" ? String(value.pluginVersion).trim() : "";
|
|
1168
|
+
var dshVersion = typeof value.dshVersion === "string" ? String(value.dshVersion).trim() : "";
|
|
1169
|
+
var runtime = value.runtime === "desktop" ? "desktop" : value.runtime === "web" ? "web" : "";
|
|
1170
|
+
var update = parseUpdate(value);
|
|
1171
|
+
var innerRef = react.useRef(null);
|
|
1172
|
+
var measuredWidth = useMeasuredWidth(innerRef);
|
|
1173
|
+
var viewportWidth = 0;
|
|
1174
|
+
try {
|
|
1175
|
+
if (typeof window !== "undefined" && window && typeof window.innerWidth === "number") viewportWidth = window.innerWidth;
|
|
1176
|
+
} catch (e) { /* an embedded webview may restrict access */ }
|
|
1177
|
+
var contentWidth = measuredWidth > 0 ? measuredWidth : viewportWidth;
|
|
1178
|
+
// Phone browser = the VIEWPORT is small. A narrow settings dialog on a
|
|
1179
|
+
// desktop is not a phone: its column is small but the window is not.
|
|
1180
|
+
var isMobile = modern && (viewportWidth > 0 ? viewportWidth < 700 : (contentWidth > 0 && contentWidth < 600));
|
|
1181
|
+
var isWide = modern && !isMobile && surface !== "modal" && (contentWidth === 0 || contentWidth >= 720);
|
|
1182
|
+
var checkingState = react.useState(false);
|
|
1183
|
+
var checking = checkingState[0];
|
|
1184
|
+
var setChecking = checkingState[1];
|
|
1185
|
+
// The update explanation is progressive disclosure: the header only shows the
|
|
1186
|
+
// quiet "可升级到 x" link, and this state opens the neutral detail panel.
|
|
1187
|
+
var updateOpenState = react.useState(false);
|
|
1188
|
+
var updateOpen = updateOpenState[0];
|
|
1189
|
+
var setUpdateOpen = updateOpenState[1];
|
|
1190
|
+
// Implementation details (tool count, one-shot mode, policy text, versions)
|
|
1191
|
+
// start collapsed: the settings page is for acting, not reading.
|
|
1192
|
+
var notesState = react.useState(false);
|
|
1193
|
+
var notesOpen = notesState[0];
|
|
1194
|
+
var setNotesOpen = notesState[1];
|
|
1195
|
+
var checkBaseline = react.useRef(0);
|
|
1196
|
+
var checkTimer = react.useRef(null);
|
|
1197
|
+
var runUpdateCheck = function (force) {
|
|
1198
|
+
setChecking(true);
|
|
1199
|
+
checkBaseline.current = update.checkedAt;
|
|
1200
|
+
if (checkTimer.current) clearTimeout(checkTimer.current);
|
|
1201
|
+
checkTimer.current = setTimeout(function () {
|
|
1202
|
+
checkTimer.current = null;
|
|
1203
|
+
setChecking(false);
|
|
1204
|
+
}, 20000);
|
|
1205
|
+
try {
|
|
1206
|
+
if (typeof props.configOp === "function") props.configOp({ op: "updateCheck", force: force === true });
|
|
1207
|
+
} catch (e) {
|
|
1208
|
+
setChecking(false);
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
// Opening the page asks the Host for a (throttled) update check; the Host
|
|
1212
|
+
// owns the network call and the cache, so a refresh here costs nothing.
|
|
1213
|
+
react.useEffect(function () {
|
|
1214
|
+
if (!modern) return undefined;
|
|
1215
|
+
try {
|
|
1216
|
+
if (typeof props.configOp === "function") props.configOp({ op: "updateCheck", force: false });
|
|
1217
|
+
} catch (e) { /* an offline host is not a page failure */ }
|
|
1218
|
+
return undefined;
|
|
1219
|
+
}, [modern]);
|
|
1220
|
+
react.useEffect(function () {
|
|
1221
|
+
if (!checking) return;
|
|
1222
|
+
if (update.checkedAt > 0 && update.checkedAt !== checkBaseline.current) {
|
|
1223
|
+
if (checkTimer.current) {
|
|
1224
|
+
clearTimeout(checkTimer.current);
|
|
1225
|
+
checkTimer.current = null;
|
|
1226
|
+
}
|
|
1227
|
+
setChecking(false);
|
|
1228
|
+
}
|
|
1229
|
+
}, [value.update, checking]);
|
|
1230
|
+
|
|
812
1231
|
// ── view state (page only) ───────────────────────────────────────────────
|
|
813
1232
|
//
|
|
814
1233
|
// `useWorkspaces` is a standard prop of root-scoped slots: it tells us which
|
|
@@ -869,13 +1288,16 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
869
1288
|
setAddTarget(nextTarget);
|
|
870
1289
|
};
|
|
871
1290
|
|
|
872
|
-
// ── config draft (interval + the sidebar switch)
|
|
1291
|
+
// ── config draft (interval + the sidebar switch + the policy switch) ──────
|
|
873
1292
|
var draftState = react.useState(function () {
|
|
874
1293
|
return {
|
|
875
1294
|
updateIntervalDays: typeof value.updateIntervalDays === "number" ? String(value.updateIntervalDays) : "7",
|
|
876
1295
|
// The user's own "hide the shortcut" preference. Independent of the
|
|
877
1296
|
// plugin's enabled state: hiding the entry does NOT disable anything.
|
|
878
1297
|
showSidebarEntry: value.showSidebarEntry !== false,
|
|
1298
|
+
// The user's own "let the plugin announce its access policy" preference.
|
|
1299
|
+
// Independent of everything else: off removes one prompt section only.
|
|
1300
|
+
announceAccessPolicy: value.announceAccessPolicy !== false,
|
|
879
1301
|
};
|
|
880
1302
|
});
|
|
881
1303
|
var draft = draftState[0];
|
|
@@ -888,6 +1310,7 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
888
1310
|
props.saveConfig({
|
|
889
1311
|
updateIntervalDays: Number(draft.updateIntervalDays),
|
|
890
1312
|
showSidebarEntry: draft.showSidebarEntry !== false,
|
|
1313
|
+
announceAccessPolicy: draft.announceAccessPolicy !== false,
|
|
891
1314
|
}).then(function () { setSaved(true); });
|
|
892
1315
|
};
|
|
893
1316
|
|
|
@@ -898,6 +1321,13 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
898
1321
|
props.saveConfig({ showSidebarEntry: next });
|
|
899
1322
|
};
|
|
900
1323
|
|
|
1324
|
+
// Same immediate-apply contract for the policy announcement.
|
|
1325
|
+
var togglePolicy = function (next) {
|
|
1326
|
+
setDraft(Object.assign({}, draft, { announceAccessPolicy: next }));
|
|
1327
|
+
setSaved(false);
|
|
1328
|
+
props.saveConfig({ announceAccessPolicy: next });
|
|
1329
|
+
};
|
|
1330
|
+
|
|
901
1331
|
// ── pre-test + confirm dialog for add/edit ────────────────────────────────
|
|
902
1332
|
//
|
|
903
1333
|
// Both the add form and the edit form gate saves behind a live connection
|
|
@@ -1246,6 +1676,7 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
1246
1676
|
return function () {
|
|
1247
1677
|
for (var id in timers) { if (Object.prototype.hasOwnProperty.call(timers, id)) clearTimeout(timers[id]); }
|
|
1248
1678
|
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
1679
|
+
if (checkTimer.current) { clearTimeout(checkTimer.current); checkTimer.current = null; }
|
|
1249
1680
|
};
|
|
1250
1681
|
}, []);
|
|
1251
1682
|
|
|
@@ -1273,7 +1704,14 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
1273
1704
|
};
|
|
1274
1705
|
|
|
1275
1706
|
// ── derived copy ──
|
|
1276
|
-
|
|
1707
|
+
//
|
|
1708
|
+
// Modern surfaces show only what a user acts on: state + connection count.
|
|
1709
|
+
// The legacy (0.1.x) DOM keeps its original status sentence verbatim.
|
|
1710
|
+
var statusText = t(meta.key);
|
|
1711
|
+
var connSummary = workspaces.length > 0
|
|
1712
|
+
? t("status.conns", { n: String(workspaces.length) })
|
|
1713
|
+
: t("desc.unconfigured");
|
|
1714
|
+
var legacyStatusText = meta.emoji + " " + t(meta.key) + " · " + t("tools.fixed", { n: String(toolCount) })
|
|
1277
1715
|
+ (workspaces.length === 0 ? " · " + t("desc.unconfigured") : "");
|
|
1278
1716
|
var cfgSummary = t("cfg.summary", {
|
|
1279
1717
|
n: typeof draft.updateIntervalDays === "string" && draft.updateIntervalDays ? draft.updateIntervalDays : "7",
|
|
@@ -1297,12 +1735,34 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
1297
1735
|
if (w.ssh) text += " · " + t("ro.sshTitle", { host: String(w.ssh.host || ""), port: String(w.ssh.port || ""), user: String(w.ssh.user || "") });
|
|
1298
1736
|
return text;
|
|
1299
1737
|
};
|
|
1300
|
-
// Environment chip: the name plus an inline read-only
|
|
1301
|
-
//
|
|
1738
|
+
// Environment chip: the name plus, on the legacy layout, an inline read-only
|
|
1739
|
+
// marker. The modern layout renders read-only as its own row control below so
|
|
1740
|
+
// it can be flipped without opening the edit form.
|
|
1302
1741
|
var envChip = function (w) {
|
|
1303
1742
|
return h("span", { className: "dbh-envname", title: originTitle(w, w.env) },
|
|
1304
1743
|
w.env,
|
|
1305
|
-
w.ro === true ? h("span", { className: "dbh-ro", title: t("ro.title") }, t("ro.chip")) : null);
|
|
1744
|
+
!modern && w.ro === true ? h("span", { className: "dbh-ro", title: t("ro.title") }, t("ro.chip")) : null);
|
|
1745
|
+
};
|
|
1746
|
+
/**
|
|
1747
|
+
* Per-row read-only toggle, on EVERY modern layout (dialog, wide page, phone).
|
|
1748
|
+
* One click writes `{op:'options', readOnly}` — no edit form, no re-typing a
|
|
1749
|
+
* DSN. The chip itself is the state: outlined = writable, filled + lock = the
|
|
1750
|
+
* model cannot write. The legacy layout keeps its marker-in-the-chip form.
|
|
1751
|
+
*/
|
|
1752
|
+
var roChip = function (w) {
|
|
1753
|
+
if (!modern) return null;
|
|
1754
|
+
var on = w.ro === true;
|
|
1755
|
+
return h("button", {
|
|
1756
|
+
type: "button",
|
|
1757
|
+
className: "dbh-rochip" + (on ? " dbh-rochip-on" : ""),
|
|
1758
|
+
title: roSupported ? (on ? t("ro.chipOnHint") : t("ro.chipOffHint")) : capHint,
|
|
1759
|
+
"aria-label": on ? t("ro.turnOff") : t("ro.turnOn"),
|
|
1760
|
+
"aria-pressed": on,
|
|
1761
|
+
disabled: !roSupported,
|
|
1762
|
+
onClick: function () {
|
|
1763
|
+
props.configOp({ op: "options", workspace: w.path, env: w.env, readOnly: w.ro !== true });
|
|
1764
|
+
},
|
|
1765
|
+
}, h("span", { className: "dbh-roemoji", "aria-hidden": true }, on ? "🔒" : "🔓"), t(on ? "ro.chip" : "ro.writable"));
|
|
1306
1766
|
};
|
|
1307
1767
|
// The source handle is a first-class, visibly-copyable chip: it shows the
|
|
1308
1768
|
// trailing characters that tell look-alike handles apart, and its dashed
|
|
@@ -1333,7 +1793,7 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
1333
1793
|
"aria-busy": isTesting,
|
|
1334
1794
|
disabled: isTesting,
|
|
1335
1795
|
onClick: function () { startTest(w, key); },
|
|
1336
|
-
}, h(RunIcon, { size:
|
|
1796
|
+
}, h(RunIcon, { size: 16 })),
|
|
1337
1797
|
h("button", {
|
|
1338
1798
|
type: "button", className: "dbh-iconbtn",
|
|
1339
1799
|
title: isEditing ? t("btn.cancel") : t("btn.edit"),
|
|
@@ -1343,13 +1803,13 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
1343
1803
|
key: key, dsn: "", env: w.env, ro: w.ro === true, adv: uiMemory.advOpen === true,
|
|
1344
1804
|
}, sshDraftOf(w)));
|
|
1345
1805
|
},
|
|
1346
|
-
}, isEditing ? h(CloseIcon, { size: 14 }) : h(EditIcon, { size:
|
|
1806
|
+
}, isEditing ? h(CloseIcon, { size: 14 }) : h(EditIcon, { size: 16 })),
|
|
1347
1807
|
w.persisted ? h("button", {
|
|
1348
1808
|
type: "button", className: "dbh-iconbtn dbh-iconbtn-danger",
|
|
1349
1809
|
title: t("btn.delete"),
|
|
1350
1810
|
"aria-label": t("btn.delete"),
|
|
1351
1811
|
onClick: function () { props.configOp({ op: "remove", workspace: w.path, env: w.env }); },
|
|
1352
|
-
}, h(TrashIcon, { size:
|
|
1812
|
+
}, h(TrashIcon, { size: 16 })) : null);
|
|
1353
1813
|
};
|
|
1354
1814
|
// Browsers serialize a copy of adjacent BLOCK-level boxes with a separator,
|
|
1355
1815
|
// and the split label is two flex items — pasting it into the DSN field used
|
|
@@ -1532,7 +1992,11 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
1532
1992
|
var connLabelView = function (w) {
|
|
1533
1993
|
var parts = splitConn(w.conn);
|
|
1534
1994
|
return h("span", { className: "dbh-conn", title: w.conn, onCopy: function (e) { onConnCopy(w, e); } },
|
|
1535
|
-
|
|
1995
|
+
// The lock used to sit in front of every connection string, which read
|
|
1996
|
+
// like "this row needs a password" and clashed with the read-only lock.
|
|
1997
|
+
// Modern rows show the plain type://host:port/db; the only lock left is
|
|
1998
|
+
// the read-only chip. Legacy keeps its original marker.
|
|
1999
|
+
!modern ? h("span", { className: "dbh-conn-lock", "aria-hidden": true }, "🔒") : null,
|
|
1536
2000
|
h("span", { className: "dbh-conn-pre" }, parts.prefix),
|
|
1537
2001
|
parts.tail ? h("span", { className: "dbh-conn-db" }, parts.tail) : null);
|
|
1538
2002
|
};
|
|
@@ -1542,6 +2006,7 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
1542
2006
|
h("div", { className: "dbh-envrow" },
|
|
1543
2007
|
envChip(w),
|
|
1544
2008
|
connLabelView(w),
|
|
2009
|
+
roChip(w),
|
|
1545
2010
|
rowActions(w, key)),
|
|
1546
2011
|
editRow(w, key),
|
|
1547
2012
|
testRow(w, key));
|
|
@@ -1580,16 +2045,203 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
1580
2045
|
h("span", { className: "dbh-tiletitle", title: g.path }, label)),
|
|
1581
2046
|
h("div", { className: "dbh-tilemeta" },
|
|
1582
2047
|
envChip(w),
|
|
1583
|
-
connLabelView(w)
|
|
2048
|
+
connLabelView(w),
|
|
2049
|
+
roChip(w)),
|
|
1584
2050
|
h("div", { className: "dbh-tileacts" }, rowActions(w, key)),
|
|
1585
2051
|
editRow(w, key),
|
|
1586
2052
|
testRow(w, key));
|
|
1587
2053
|
};
|
|
1588
2054
|
|
|
1589
2055
|
// ── assemble the page ──
|
|
1590
|
-
|
|
2056
|
+
//
|
|
2057
|
+
// Modern header: title + subdued version plus a quiet "可升级到 x" link that
|
|
2058
|
+
// expands into a neutral panel on demand. The legacy (0.1.x) header below is
|
|
2059
|
+
// byte-for-byte the old one.
|
|
2060
|
+
var latestSpec = update.latestSpec || (update.latest ? "dsh-dbhub-live@" + update.latest : "");
|
|
2061
|
+
var webCmd = update.latest ? "dsh plugin --profile web up dsh-dbhub-live@" + update.latest : "";
|
|
2062
|
+
var checkedMinutes = update.checkedAt ? Math.max(0, Math.round((Date.now() - update.checkedAt) / 60000)) : 0;
|
|
2063
|
+
var copyLatest = function () { if (latestSpec) copySource(latestSpec, "ver"); };
|
|
2064
|
+
var copyWebCmd = function () { if (webCmd) copySource(webCmd, "cmd"); };
|
|
2065
|
+
var checkLabel = checking
|
|
2066
|
+
? t("ver.checking")
|
|
2067
|
+
: update.error
|
|
2068
|
+
? t("ver.checkFailed") + ":" + update.error
|
|
2069
|
+
: update.checkedAt
|
|
2070
|
+
? t("ver.checkedAgo", { n: String(checkedMinutes) })
|
|
2071
|
+
: t("ver.check");
|
|
2072
|
+
var titlebar = modern ? h("div", { className: "dbh-titlebar" },
|
|
2073
|
+
h("h2", { className: "dbh-title" }, t("title")),
|
|
2074
|
+
pluginVersion ? h("span", { className: "dbh-ver", title: dshVersion ? "DSH " + dshVersion : "" }, "v" + pluginVersion) : null,
|
|
2075
|
+
h("span", { className: "dbh-grow" }),
|
|
2076
|
+
update.hasUpdate ? h("button", {
|
|
2077
|
+
type: "button", className: "dbh-updatelink",
|
|
2078
|
+
title: t("ver.copySpec", { v: update.latest }),
|
|
2079
|
+
onClick: function () { setUpdateOpen(!updateOpen); },
|
|
2080
|
+
}, "↑ " + t("ver.link", { v: update.latest })) : null,
|
|
2081
|
+
h("button", {
|
|
2082
|
+
type: "button", className: "dbh-vercheck", disabled: checking,
|
|
2083
|
+
title: checkLabel, "aria-label": t("ver.check"),
|
|
2084
|
+
onClick: function () { runUpdateCheck(true); },
|
|
2085
|
+
}, h(RefreshIcon, { size: 12 }))) : null;
|
|
2086
|
+
|
|
2087
|
+
// Update guidance, expanded on demand: the Host names the runtime (web or
|
|
2088
|
+
// desktop) because the two profiles update differently. Neutral panel — the
|
|
2089
|
+
// old red "error" styling made users think something had broken.
|
|
2090
|
+
var updateView = null;
|
|
2091
|
+
if (modern && updateOpen && update.hasUpdate) {
|
|
2092
|
+
updateView = h("div", { className: "dbh-updatepanel" },
|
|
2093
|
+
h("div", { className: "dbh-updaterow" },
|
|
2094
|
+
h("code", { className: "dbh-spec" }, latestSpec),
|
|
2095
|
+
h("button", {
|
|
2096
|
+
type: "button", className: "dbh-linkbtn",
|
|
2097
|
+
title: t("ver.copySpec", { v: update.latest }),
|
|
2098
|
+
onClick: copyLatest,
|
|
2099
|
+
}, copiedKey === "ver" ? t("copy.ok") : t("ver.copy"))),
|
|
2100
|
+
h("div", { className: "dbh-hint" }, runtime === "desktop"
|
|
2101
|
+
? t("ver.desktopHint", { v: update.latest })
|
|
2102
|
+
: runtime === "web"
|
|
2103
|
+
? t("ver.webHint", { v: update.latest })
|
|
2104
|
+
: t("ver.genericHint", { v: update.latest })),
|
|
2105
|
+
runtime !== "desktop" && webCmd ? h("div", { className: "dbh-updaterow" },
|
|
2106
|
+
h("code", { className: "dbh-spec" }, webCmd),
|
|
2107
|
+
h("button", { type: "button", className: "dbh-linkbtn", onClick: copyWebCmd },
|
|
2108
|
+
copiedKey === "cmd" ? t("copy.ok") : t("ver.copyCmd"))) : null,
|
|
2109
|
+
h("a", {
|
|
2110
|
+
className: "dbh-link",
|
|
2111
|
+
href: "https://github.com/mr-mihu/dsh-dbhub-live/releases/tag/v" + update.latest,
|
|
2112
|
+
target: "_blank", rel: "noreferrer noopener",
|
|
2113
|
+
}, t("ver.releaseNotes")));
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
// One status line: state + connection count (+ the dialog's 设置 toggle).
|
|
2117
|
+
var statusline = modern ? h("div", { className: "dbh-statusline" },
|
|
2118
|
+
h("span", { className: "dbh-dot" + (enabled ? "" : " dbh-dot-off"), "aria-hidden": true }),
|
|
2119
|
+
h("span", null, statusText),
|
|
2120
|
+
h("span", { className: "dbh-statussep" }, "·"),
|
|
2121
|
+
h("span", null, connSummary),
|
|
2122
|
+
h("span", { className: "dbh-grow" }),
|
|
2123
|
+
!isWide ? h("button", {
|
|
2124
|
+
type: "button", className: "dbh-linkbtn",
|
|
2125
|
+
"aria-expanded": settingsOpen,
|
|
2126
|
+
onClick: toggleSettings,
|
|
2127
|
+
}, t("settings.toggle") + (settingsOpen ? " ▴" : " ▾")) : null) : null;
|
|
2128
|
+
var errorLine = modern && lastError ? h("div", { className: "dbh-error" }, lastError) : null;
|
|
2129
|
+
|
|
2130
|
+
// Technical details live here, collapsed, so the rows above stay actionable.
|
|
2131
|
+
var notesView = h("div", { className: "dbh-notes" },
|
|
2132
|
+
h("div", null, t("note.tools", { n: String(toolCount) })),
|
|
2133
|
+
h("div", null, t("note.update")),
|
|
2134
|
+
h("div", null, t("note.sidebar")),
|
|
2135
|
+
h("div", null, t("note.policy")),
|
|
2136
|
+
h("div", null, t("note.version", {
|
|
2137
|
+
plugin: pluginVersion || "?",
|
|
2138
|
+
dsh: dshVersion || "?",
|
|
2139
|
+
runtime: runtime === "desktop" ? t("ver.runtimeDesktop") : t("ver.runtimeWeb"),
|
|
2140
|
+
})),
|
|
2141
|
+
capHint ? h("div", null, capHint) : null);
|
|
2142
|
+
|
|
2143
|
+
// Shared settings rows: the wide page shows them directly, the dialog behind
|
|
2144
|
+
// its 设置 toggle. Controls are the harness's own Switch primitive; the state
|
|
2145
|
+
// word (显示/隐藏, 启用/停用, 开启/关闭) sits next to it so the row's meaning
|
|
2146
|
+
// never depends on a colour or a tick alone.
|
|
2147
|
+
// The four settings controls, shared by the wide one-line bar and the narrow
|
|
2148
|
+
// stacked rows so both surfaces write through the same callbacks.
|
|
2149
|
+
var ctlEnable = function () {
|
|
2150
|
+
return switchControl({
|
|
2151
|
+
checked: enabled, ariaLabel: t("row.enable"),
|
|
2152
|
+
onChange: function (next) { props.setEnabled(next); },
|
|
2153
|
+
});
|
|
2154
|
+
};
|
|
2155
|
+
var ctlSidebar = function () {
|
|
2156
|
+
return switchControl({
|
|
2157
|
+
checked: draft.showSidebarEntry !== false, ariaLabel: t("row.sidebar"),
|
|
2158
|
+
title: t("cfg.sidebarHint"), onChange: toggleSidebar,
|
|
2159
|
+
});
|
|
2160
|
+
};
|
|
2161
|
+
var ctlPolicy = function () {
|
|
2162
|
+
return switchControl({
|
|
2163
|
+
checked: draft.announceAccessPolicy !== false, ariaLabel: t("row.policy"),
|
|
2164
|
+
title: t("cfg.policyHint"), onChange: togglePolicy,
|
|
2165
|
+
});
|
|
2166
|
+
};
|
|
2167
|
+
var ctlInterval = function () {
|
|
2168
|
+
return [
|
|
2169
|
+
h("input", {
|
|
2170
|
+
key: "input", className: "dbh-input dbh-srow-input", type: "number", min: "0",
|
|
2171
|
+
"aria-label": t("row.update"), title: t("cfg.updateHint"),
|
|
2172
|
+
value: typeof draft.updateIntervalDays === "string" ? draft.updateIntervalDays : "",
|
|
2173
|
+
onChange: function (e) { setSaved(false); setDraft(Object.assign({}, draft, { updateIntervalDays: e.target.value })); },
|
|
2174
|
+
}),
|
|
2175
|
+
h("span", { key: "unit", className: "dbh-srow-unit" }, t("unit.days")),
|
|
2176
|
+
h("button", { key: "save", type: "button", className: "dbh-btn dbh-btn-sm", onClick: saveConfig }, t("btn.save")),
|
|
2177
|
+
];
|
|
2178
|
+
};
|
|
2179
|
+
var notesButton = function (labelKey) {
|
|
2180
|
+
return h("button", {
|
|
2181
|
+
type: "button", className: "dbh-linkbtn",
|
|
2182
|
+
"aria-expanded": notesOpen,
|
|
2183
|
+
onClick: function () { setNotesOpen(!notesOpen); },
|
|
2184
|
+
}, "ⓘ " + t(notesOpen ? "settings.less" : labelKey));
|
|
2185
|
+
};
|
|
2186
|
+
|
|
2187
|
+
// Wide page: one compact line so the four controls (and their labels) sit
|
|
2188
|
+
// together instead of stretching across the page with a gaping middle. It
|
|
2189
|
+
// wraps only when the docked window is genuinely narrow.
|
|
2190
|
+
var settingsBar = function (narrow) {
|
|
2191
|
+
return h("div", { className: "dbh-settings dbh-settingsbar" + (narrow ? " dbh-barnarrow" : "") },
|
|
2192
|
+
h("div", { className: "dbh-barline" },
|
|
2193
|
+
h("span", { className: "dbh-bartitle" }, t("settings.title")),
|
|
2194
|
+
h("div", { className: "dbh-bargroup" },
|
|
2195
|
+
h("span", { className: "dbh-barlabel" }, t("row.enable")),
|
|
2196
|
+
h("span", { className: "dbh-barstate" }, t(enabled ? "state.enable" : "state.disable")),
|
|
2197
|
+
ctlEnable()),
|
|
2198
|
+
h("div", { className: "dbh-bargroup" },
|
|
2199
|
+
h("span", { className: "dbh-barlabel" }, t("row.sidebar")),
|
|
2200
|
+
h("span", { className: "dbh-barstate" }, t(draft.showSidebarEntry !== false ? "state.show" : "state.hide")),
|
|
2201
|
+
ctlSidebar()),
|
|
2202
|
+
h("div", { className: "dbh-bargroup" },
|
|
2203
|
+
h("span", { className: "dbh-barlabel" }, t("row.policy")),
|
|
2204
|
+
h("span", { className: "dbh-barstate" }, t(draft.announceAccessPolicy !== false ? "state.on" : "state.off")),
|
|
2205
|
+
ctlPolicy()),
|
|
2206
|
+
h("div", { className: "dbh-bargroup" },
|
|
2207
|
+
h("span", { className: "dbh-barlabel" }, t("row.update")),
|
|
2208
|
+
ctlInterval()),
|
|
2209
|
+
h("span", { className: "dbh-grow" }),
|
|
2210
|
+
notesButton("settings.notes"),
|
|
2211
|
+
saved ? h("span", { className: "dbh-saved" }, t("saved.ok")) : null),
|
|
2212
|
+
notesOpen ? notesView : null);
|
|
2213
|
+
};
|
|
2214
|
+
|
|
2215
|
+
// Narrow surfaces (dialog + phone): compact rows, still behind the 设置 toggle.
|
|
2216
|
+
var settingsRows = function () {
|
|
2217
|
+
return h("div", { className: "dbh-settings" },
|
|
2218
|
+
h("div", { className: "dbh-settings-title" }, t("settings.title")),
|
|
2219
|
+
h("div", { className: "dbh-srow" },
|
|
2220
|
+
h("div", { className: "dbh-srow-main" }, h("div", { className: "dbh-srow-title" }, t("row.enable"))),
|
|
2221
|
+
h("div", { className: "dbh-srow-ctl" },
|
|
2222
|
+
h("span", { className: "dbh-srow-state" }, t(enabled ? "state.enable" : "state.disable")),
|
|
2223
|
+
ctlEnable())),
|
|
2224
|
+
h("div", { className: "dbh-srow" },
|
|
2225
|
+
h("div", { className: "dbh-srow-main" }, h("div", { className: "dbh-srow-title" }, t("row.sidebar"))),
|
|
2226
|
+
h("div", { className: "dbh-srow-ctl" },
|
|
2227
|
+
h("span", { className: "dbh-srow-state" }, t(draft.showSidebarEntry !== false ? "state.show" : "state.hide")),
|
|
2228
|
+
ctlSidebar())),
|
|
2229
|
+
h("div", { className: "dbh-srow" },
|
|
2230
|
+
h("div", { className: "dbh-srow-main" }, h("div", { className: "dbh-srow-title" }, t("row.policy"))),
|
|
2231
|
+
h("div", { className: "dbh-srow-ctl" },
|
|
2232
|
+
h("span", { className: "dbh-srow-state" }, t(draft.announceAccessPolicy !== false ? "state.on" : "state.off")),
|
|
2233
|
+
ctlPolicy())),
|
|
2234
|
+
h("div", { className: "dbh-srow" },
|
|
2235
|
+
h("div", { className: "dbh-srow-main" }, h("div", { className: "dbh-srow-title" }, t("row.update"))),
|
|
2236
|
+
h("div", { className: "dbh-srow-ctl" }, ctlInterval())),
|
|
2237
|
+
h("div", { className: "dbh-settings-foot" }, notesButton("settings.more")),
|
|
2238
|
+
notesOpen ? notesView : null);
|
|
2239
|
+
};
|
|
2240
|
+
|
|
2241
|
+
// Legacy (dsh 0.1.x): the original header + settings strip, byte-for-byte.
|
|
2242
|
+
var legacyHead = h("div", { className: "dbh-head" },
|
|
1591
2243
|
h("div", { className: "dbh-headtext" },
|
|
1592
|
-
h("span", { className: "dbh-statustext", title: modeLabel },
|
|
2244
|
+
h("span", { className: "dbh-statustext", title: modeLabel }, legacyStatusText),
|
|
1593
2245
|
lastError ? h("span", { className: "dbh-error" }, lastError) : null),
|
|
1594
2246
|
h("span", { className: "dbh-cfgsum" }, cfgSummary),
|
|
1595
2247
|
h("button", { type: "button", className: "dbh-btn", onClick: toggleSettings },
|
|
@@ -1599,7 +2251,7 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
1599
2251
|
onClick: function () { props.setEnabled(!enabled); },
|
|
1600
2252
|
}, t(enabled ? "btn.disable" : "btn.enable")));
|
|
1601
2253
|
|
|
1602
|
-
var
|
|
2254
|
+
var legacySettingsPanel = settingsOpen ? h("div", { className: "dbh-strip" },
|
|
1603
2255
|
h("div", { className: "dbh-row" },
|
|
1604
2256
|
h("span", { className: "dbh-label" }, t("cfg.update")),
|
|
1605
2257
|
h("input", {
|
|
@@ -1616,14 +2268,30 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
1616
2268
|
onChange: function (e) { toggleSidebar(e.target.checked); },
|
|
1617
2269
|
}),
|
|
1618
2270
|
h("span", null, t("cfg.sidebar")))),
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
2271
|
+
h("div", { className: "dbh-row" },
|
|
2272
|
+
h("label", { className: "dbh-check", title: t("cfg.policyHint") },
|
|
2273
|
+
h("input", {
|
|
2274
|
+
type: "checkbox", className: "dbh-checkbox",
|
|
2275
|
+
"aria-label": t("cfg.policy"),
|
|
2276
|
+
checked: draft.announceAccessPolicy !== false,
|
|
2277
|
+
onChange: function (e) { togglePolicy(e.target.checked); },
|
|
2278
|
+
}),
|
|
2279
|
+
h("span", null, t("cfg.policy")))),
|
|
1622
2280
|
h("div", { className: "dbh-hint" }, (enabled ? t("cfg.enabledHint") : t("cfg.disabledHint")) + " " + t("cfg.sidebarHint")),
|
|
1623
2281
|
h("div", { className: "dbh-addfoot" },
|
|
1624
2282
|
saved ? h("span", { className: "dbh-saved" }, t("saved.ok")) : null,
|
|
1625
2283
|
h("button", { type: "button", className: "dbh-btn dbh-btn-primary", onClick: saveConfig }, t("btn.save")))) : null;
|
|
1626
2284
|
|
|
2285
|
+
// Any non-modal page (left-menu panel, Plugins page) uses the one-line bar,
|
|
2286
|
+
// even when the measured column is narrow: a docked/small desktop window used
|
|
2287
|
+
// to fall back to four stacked rows ("all buttons in a column"). Only the
|
|
2288
|
+
// dialog and the phone keep the stacked form.
|
|
2289
|
+
var useSettingsBar = modern && !isMobile && surface !== "modal";
|
|
2290
|
+
var settingsPanel = modern
|
|
2291
|
+
? (settingsOpen && !useSettingsBar ? settingsRows() : null)
|
|
2292
|
+
: legacySettingsPanel;
|
|
2293
|
+
var configBlock = useSettingsBar ? settingsBar(!isWide) : null;
|
|
2294
|
+
|
|
1627
2295
|
var section = h("div", { className: "dbh-sect" },
|
|
1628
2296
|
h("span", { className: "dbh-secttitle" }, t("conn.title")),
|
|
1629
2297
|
h("span", { className: "dbh-sectcount" }, t("conn.total", { n: String(workspaces.length), m: String(groups.length) })),
|
|
@@ -1735,9 +2403,18 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
1735
2403
|
var wsList = h("datalist", { id: "dbh-ws-list" },
|
|
1736
2404
|
groups.map(function (g) { return h("option", { key: g.key, value: g.path }, g.title); }));
|
|
1737
2405
|
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
2406
|
+
var innerClass = "dbh-inner"
|
|
2407
|
+
+ (modern ? " dbh-modern" : "")
|
|
2408
|
+
+ (modern && isWide ? " dbh-wide" : "")
|
|
2409
|
+
+ (modern && isMobile ? " dbh-mobile" : "")
|
|
2410
|
+
+ (modern && surface === "modal" ? " dbh-modal" : "");
|
|
2411
|
+
|
|
2412
|
+
return h("div", { className: innerClass, ref: innerRef },
|
|
2413
|
+
modern ? titlebar : legacyHead,
|
|
2414
|
+
modern ? updateView : null,
|
|
2415
|
+
modern ? statusline : null,
|
|
2416
|
+
modern ? errorLine : null,
|
|
2417
|
+
configBlock || settingsPanel,
|
|
1741
2418
|
section,
|
|
1742
2419
|
body,
|
|
1743
2420
|
overwriteView || pretestView,
|
|
@@ -1965,7 +2642,7 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
1965
2642
|
label: function () { return t("title"); },
|
|
1966
2643
|
locale: NS,
|
|
1967
2644
|
inject: function () { return face; },
|
|
1968
|
-
},
|
|
2645
|
+
}, SettingsSectionPanel);
|
|
1969
2646
|
console.log("[dsh-dbhub-live] 设置页入口已注册: settings.section id=dbhub");
|
|
1970
2647
|
return reg;
|
|
1971
2648
|
});
|
|
@@ -2020,7 +2697,7 @@ window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => {
|
|
|
2020
2697
|
key: "dbhub",
|
|
2021
2698
|
locale: NS,
|
|
2022
2699
|
inject: function () { return face; },
|
|
2023
|
-
},
|
|
2700
|
+
}, MainPanel);
|
|
2024
2701
|
});
|
|
2025
2702
|
face.subscribe(syncSidebar);
|
|
2026
2703
|
}
|