@linxin666/dsh-ssh 0.3.19 → 0.3.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -63,6 +63,12 @@ window.__ModuleLoader__.load({
63
63
  "hosts.delete": "删除",
64
64
  "hosts.deleteConfirm": "确定删除主机「{alias}」?将同时停止其隧道。",
65
65
  "hosts.imported": "解析 {parsed} 个配置块,新增 {added} 台主机,跳过 {skipped} 个。",
66
+ "hosts.proxyBadge": "代理",
67
+ "import.reason.wildcard": "通配符 pattern,需手动配置",
68
+ "import.reason.existing": "别名已存在",
69
+ "import.reason.match": "Match 条件块,不支持",
70
+ "import.reason.invalid": "字段不合法,无法导入",
71
+ "import.more": "其余 {count} 个未显示",
66
72
  "hosts.testing": "连接测试中…",
67
73
  "hosts.testOk": "连接成功({latency} ms)",
68
74
  "hosts.testFail": "连接失败:{error}",
@@ -89,7 +95,9 @@ window.__ModuleLoader__.load({
89
95
  "form.passwordHint": "明文保存在 $DSH_HOME/dsh-ssh.json(本机私有文件)",
90
96
  "form.passwordRequired": "密码认证需要填写密码。",
91
97
  "form.proxyJump": "跳板机别名",
92
- "form.proxyJumpHint": "逗号分隔的本地主机别名,按顺序连接",
98
+ "form.proxyJumpHint": "逗号分隔的本地主机别名,或 [user@]host[:port] 地址,按顺序连接",
99
+ "form.proxyCommand": "ProxyCommand",
100
+ "form.proxyCommandHint": "OpenSSH ProxyCommand:%h 主机、%p 端口、%r 用户、%n 别名;留空或填 none 表示不使用。与跳板机别名互斥。",
93
101
  "form.description": "备注",
94
102
  "form.environment": "环境",
95
103
  "form.environmentHint": "如 development / production",
@@ -210,6 +218,12 @@ window.__ModuleLoader__.load({
210
218
  "hosts.delete": "Delete",
211
219
  "hosts.deleteConfirm": "Delete host \"{alias}\"? Its tunnels will be stopped.",
212
220
  "hosts.imported": "Parsed {parsed} blocks, added {added} hosts, skipped {skipped}.",
221
+ "hosts.proxyBadge": "proxy",
222
+ "import.reason.wildcard": "wildcard pattern — configure it by hand",
223
+ "import.reason.existing": "alias already configured",
224
+ "import.reason.match": "Match block — not supported",
225
+ "import.reason.invalid": "invalid fields — not imported",
226
+ "import.more": "{count} more not shown",
213
227
  "hosts.testing": "Testing connection…",
214
228
  "hosts.testOk": "Connected ({latency} ms)",
215
229
  "hosts.testFail": "Connection failed: {error}",
@@ -236,7 +250,9 @@ window.__ModuleLoader__.load({
236
250
  "form.passwordHint": "Stored in plaintext in $DSH_HOME/dsh-ssh.json (private local file)",
237
251
  "form.passwordRequired": "Password auth requires a password.",
238
252
  "form.proxyJump": "Jump host aliases",
239
- "form.proxyJumpHint": "Comma-separated local aliases, chained in order",
253
+ "form.proxyJumpHint": "Comma-separated local aliases or [user@]host[:port] addresses, chained in order",
254
+ "form.proxyCommand": "ProxyCommand",
255
+ "form.proxyCommandHint": "OpenSSH ProxyCommand: %h host, %p port, %r user, %n alias; empty or none disables it. Mutually exclusive with the jump chain.",
240
256
  "form.description": "Description",
241
257
  "form.environment": "Environment",
242
258
  "form.environmentHint": "e.g. development / production",
@@ -689,7 +705,7 @@ window.__ModuleLoader__.load({
689
705
  };
690
706
  //#endregion
691
707
  //#region \0dsh-css:packages/dsh-ssh/src/client/panel/panel.module.css.mjs
692
- const css = "[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-ssh-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0}html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [data-dsh-ssh-view]{display:block}html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [data-pane=conversation]>:not([data-dsh-ssh-view]),html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [class*=centerCol]>:not([data-dsh-ssh-view]){display:none!important}.mL8Uca_entry{box-sizing:border-box;width:100%;min-height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:10px;padding:0 10px;font-size:13px;display:flex}.mL8Uca_entry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.mL8Uca_entry[data-active]{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:600}.mL8Uca_entryIcon{flex:none;justify-content:center;align-items:center;width:24px;height:24px;display:inline-flex}.mL8Uca_entryIcon svg{width:18px;height:18px;display:block}.mL8Uca_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] .mL8Uca_entry,[data-sidebar-collapsed] .mL8Uca_entry{border-radius:50%;justify-content:center;width:36px;min-height:36px;margin:0 auto 12px;padding:0}[data-dsh-frame][data-sidebar-collapsed] .mL8Uca_entryLabel,[data-sidebar-collapsed] .mL8Uca_entryLabel{display:none}.mL8Uca_view{overflow:hidden}.mL8Uca_panel{background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:10px;padding:14px 16px 16px;display:flex}.mL8Uca_panelHeader{flex:none;align-items:center;gap:10px;display:flex}.mL8Uca_panelTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;flex:1;margin:0;font-size:16px;font-weight:700}.mL8Uca_backButton{align-items:center;gap:4px;display:inline-flex}.mL8Uca_tabBar{border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;gap:2px;display:flex}.mL8Uca_tab{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;border-radius:6px 6px 0 0;padding:7px 14px;font-size:13px}.mL8Uca_tab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_tab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}.mL8Uca_panelContent{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.mL8Uca_tabBody{flex-direction:column;flex:1;gap:10px;min-height:0;display:flex;overflow-y:auto}.mL8Uca_fillBody{flex-direction:column;flex:1;gap:10px;min-height:0;display:flex;overflow:hidden}.mL8Uca_toolbar,.mL8Uca_controls{flex-wrap:wrap;flex:none;align-items:center;gap:8px;display:flex}.mL8Uca_controls .mL8Uca_input{flex:0 260px;min-width:150px}.mL8Uca_toolbarSpacer{flex:1}.mL8Uca_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}.mL8Uca_search::placeholder{color:var(--dsw-alias-label-tertiary)}.mL8Uca_groupBySelect{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;padding:6px 8px;font-size:13px}.mL8Uca_groupSection+.mL8Uca_groupSection{border-top:1px solid var(--dsw-alias-border-l1)}.mL8Uca_groupHeader{background:var(--dsw-alias-bg-base,var(--dsw-specific-input-major));align-items:center;gap:8px;padding:4px 8px;display:flex;position:sticky;top:0}.mL8Uca_groupToggle{min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;background:0 0;border:none;border-radius:6px;flex:1;align-items:center;gap:6px;padding:4px 6px;font-size:12.5px;font-weight:500;display:flex}.mL8Uca_groupToggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_groupChevron{border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);flex:none;width:7px;height:7px;margin-bottom:2px;transition:transform .12s;transform:rotate(45deg)}.mL8Uca_groupChevron[data-collapsed]{margin-bottom:0;transform:rotate(-45deg)}.mL8Uca_groupName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.mL8Uca_groupCount{color:var(--dsw-alias-label-tertiary);flex:none;font-weight:400}.mL8Uca_tableWrap{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex:1;min-height:0;overflow:auto}.mL8Uca_table{border-collapse:collapse;width:100%;font-size:12.5px}.mL8Uca_table th{z-index:1;text-align:left;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);white-space:nowrap;padding:8px 10px;font-weight:600;position:sticky;top:0}.mL8Uca_table td{border-bottom:1px solid var(--dsw-alias-separator-primary);vertical-align:top;padding:7px 10px}.mL8Uca_table tbody tr:last-child td{border-bottom:none}.mL8Uca_table tbody tr:hover td{background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.mL8Uca_cellMuted{color:var(--dsw-alias-label-tertiary)}.mL8Uca_actions{white-space:nowrap;align-items:center;gap:8px;display:flex}.mL8Uca_inlineTest{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:11.5px;display:inline-flex}.mL8Uca_inlineTest[data-status=ok]{color:var(--dsw-alias-state-success-primary)}.mL8Uca_inlineTest[data-status=fail]{color:var(--dsw-alias-state-error-primary)}.mL8Uca_badge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;line-height:1.6;display:inline-block}.mL8Uca_badge[data-kind=key]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}.mL8Uca_badge[data-kind=password]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}.mL8Uca_badge[data-kind=agent],.mL8Uca_badge[data-status=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_badge[data-status=fail]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.mL8Uca_badge[data-status=timeout]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}.mL8Uca_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}.mL8Uca_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}.mL8Uca_primaryButton:disabled{opacity:.5;cursor:default}.mL8Uca_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}.mL8Uca_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_ghostButton:disabled{opacity:.45;cursor:default}.mL8Uca_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}.mL8Uca_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.mL8Uca_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}.mL8Uca_linkButton:hover:not(:disabled){text-decoration:underline}.mL8Uca_linkButton:disabled{opacity:.45;cursor:default}.mL8Uca_linkButton[data-danger]{color:var(--dsw-alias-state-error-primary)}.mL8Uca_spinner{border:2px solid var(--dsw-alias-state-business-primary);vertical-align:-1px;border-top-color:#0000;border-radius:50%;flex:none;width:11px;height:11px;animation:.8s linear infinite mL8Uca_dshSshSpin;display:inline-block}@keyframes mL8Uca_dshSshSpin{to{transform:rotate(360deg)}}.mL8Uca_banner{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;border-radius:8px;padding:8px 12px;font-size:12.5px;line-height:1.5}.mL8Uca_banner[data-kind=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_banner[data-kind=error]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.mL8Uca_banner[data-kind=info]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}.mL8Uca_empty,.mL8Uca_loading{text-align:center;color:var(--dsw-alias-label-tertiary);padding:28px 12px;font-size:12.5px}.mL8Uca_modalBackdrop{z-index:40;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.mL8Uca_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(560px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:12px;padding:18px;display:flex;overflow-y:auto}.mL8Uca_modalTitle{margin:0;font-size:15px;font-weight:700}.mL8Uca_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}.mL8Uca_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}.mL8Uca_hint{color:var(--dsw-alias-label-tertiary);font-size:11.5px}.mL8Uca_field{flex-direction:column;gap:5px;display:flex}.mL8Uca_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}.mL8Uca_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}.mL8Uca_input:focus{border-color:var(--dsw-alias-state-business-primary)}.mL8Uca_input::placeholder{color:var(--dsw-alias-label-tertiary)}.mL8Uca_input:disabled{opacity:.55}.mL8Uca_formRow{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px 12px;display:grid}.mL8Uca_radioRow{align-items:center;gap:16px;display:flex}.mL8Uca_radioLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:13px;display:inline-flex}.mL8Uca_termBody{flex-direction:column;flex:1;gap:8px;min-height:0;display:flex;overflow:hidden}.mL8Uca_termWrap{border:1px solid var(--dsw-alias-border-l1);background:#0b0e14;border-radius:10px;flex:1;min-height:0;position:relative;overflow:hidden}.mL8Uca_termContainer{position:absolute;inset:8px 10px}.mL8Uca_termPlaceholder{z-index:2;text-align:center;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-base);justify-content:center;align-items:center;padding:0 24px;font-size:12.5px;display:flex;position:absolute;inset:0}.mL8Uca_hiddenFile{display:none}.mL8Uca_browsePanel{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;display:flex;overflow:hidden}.mL8Uca_browseHeader{background:var(--dsw-alias-bg-layer-2);border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:8px;padding:8px 10px;display:flex}.mL8Uca_browsePath{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);flex:1;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;overflow:hidden}.mL8Uca_browseList{max-height:240px;overflow-y:auto}.mL8Uca_dirRow{text-align:left;width:100%;font:inherit;color:var(--dsw-alias-label-primary);border:none;border-bottom:1px solid var(--dsw-alias-separator-primary);cursor:pointer;background:0 0;grid-template-columns:minmax(0,1fr) auto auto;gap:12px;padding:5px 10px;font-size:12.5px;display:grid}.mL8Uca_dirRow:hover{background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_dirRow[data-type=dir] .mL8Uca_dirName{color:var(--dsw-alias-state-business-primary)}.mL8Uca_dirRow[data-up]{color:var(--dsw-alias-label-secondary)}.mL8Uca_dirName{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow:hidden}.mL8Uca_dirType{color:var(--dsw-alias-label-tertiary);white-space:nowrap}.mL8Uca_dirSize{color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-align:right}.mL8Uca_transferBlock{flex-direction:column;flex:none;gap:6px;display:flex}.mL8Uca_progressMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:12px;font-size:12px;display:flex}.mL8Uca_progressTrack{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;height:8px;overflow:hidden}.mL8Uca_progressBar{background:var(--dsw-alias-state-business-primary);border-radius:999px;height:100%;transition:width .12s linear}.mL8Uca_tunnelList{flex-direction:column;flex:none;gap:8px;display:flex}.mL8Uca_tunnelRow{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;align-items:center;gap:10px;padding:8px 12px;display:flex}.mL8Uca_tunnelRow[data-state=forwarding]{border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_tunnelRow[data-state=connecting]{border-color:var(--dsw-alias-state-warn-primary)}.mL8Uca_tunnelRow[data-state=failed]{border-color:var(--dsw-alias-state-error-primary)}.mL8Uca_tunnelLabel{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;overflow:hidden}.mL8Uca_formCard{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;gap:10px;padding:12px;display:flex}.mL8Uca_clusterForm{flex-direction:column;flex:none;gap:8px;display:flex}.mL8Uca_clusterFilters{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;display:grid}.mL8Uca_commandInput{min-height:96px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.mL8Uca_cellDetails summary{cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px}.mL8Uca_cellPre{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;border-radius:6px;max-height:180px;margin:4px 0 0;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;overflow:auto}";
708
+ const css = "[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-ssh-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0}html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [data-dsh-ssh-view]{display:block}html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [data-pane=conversation]>:not([data-dsh-ssh-view]),html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [class*=centerCol]>:not([data-dsh-ssh-view]){display:none!important}.mL8Uca_entry{box-sizing:border-box;width:100%;min-height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:10px;padding:0 10px;font-size:13px;display:flex}.mL8Uca_entry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.mL8Uca_entry[data-active]{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:600}.mL8Uca_entryIcon{flex:none;justify-content:center;align-items:center;width:24px;height:24px;display:inline-flex}.mL8Uca_entryIcon svg{width:18px;height:18px;display:block}.mL8Uca_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] .mL8Uca_entry,[data-sidebar-collapsed] .mL8Uca_entry{border-radius:50%;justify-content:center;width:36px;min-height:36px;margin:0 auto 12px;padding:0}[data-dsh-frame][data-sidebar-collapsed] .mL8Uca_entryLabel,[data-sidebar-collapsed] .mL8Uca_entryLabel{display:none}.mL8Uca_view{overflow:hidden}.mL8Uca_panel{background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:10px;padding:14px 16px 16px;display:flex}.mL8Uca_panelHeader{flex:none;align-items:center;gap:10px;display:flex}.mL8Uca_panelTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;flex:1;margin:0;font-size:16px;font-weight:700}.mL8Uca_backButton{align-items:center;gap:4px;display:inline-flex}.mL8Uca_tabBar{border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;gap:2px;display:flex}.mL8Uca_tab{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;border-radius:6px 6px 0 0;padding:7px 14px;font-size:13px}.mL8Uca_tab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_tab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}.mL8Uca_panelContent{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.mL8Uca_tabBody{flex-direction:column;flex:1;gap:10px;min-height:0;display:flex;overflow-y:auto}.mL8Uca_fillBody{flex-direction:column;flex:1;gap:10px;min-height:0;display:flex;overflow:hidden}.mL8Uca_toolbar,.mL8Uca_controls{flex-wrap:wrap;flex:none;align-items:center;gap:8px;display:flex}.mL8Uca_controls .mL8Uca_input{flex:0 260px;min-width:150px}.mL8Uca_toolbarSpacer{flex:1}.mL8Uca_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}.mL8Uca_search::placeholder{color:var(--dsw-alias-label-tertiary)}.mL8Uca_groupBySelect{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;padding:6px 8px;font-size:13px}.mL8Uca_groupSection+.mL8Uca_groupSection{border-top:1px solid var(--dsw-alias-border-l1)}.mL8Uca_groupHeader{background:var(--dsw-alias-bg-base,var(--dsw-specific-input-major));align-items:center;gap:8px;padding:4px 8px;display:flex;position:sticky;top:0}.mL8Uca_groupToggle{min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;background:0 0;border:none;border-radius:6px;flex:1;align-items:center;gap:6px;padding:4px 6px;font-size:12.5px;font-weight:500;display:flex}.mL8Uca_groupToggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_groupChevron{border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);flex:none;width:7px;height:7px;margin-bottom:2px;transition:transform .12s;transform:rotate(45deg)}.mL8Uca_groupChevron[data-collapsed]{margin-bottom:0;transform:rotate(-45deg)}.mL8Uca_groupName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.mL8Uca_groupCount{color:var(--dsw-alias-label-tertiary);flex:none;font-weight:400}.mL8Uca_tableWrap{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex:1;min-height:0;overflow:auto}.mL8Uca_table{border-collapse:collapse;width:100%;font-size:12.5px}.mL8Uca_table th{z-index:1;text-align:left;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);white-space:nowrap;padding:8px 10px;font-weight:600;position:sticky;top:0}.mL8Uca_table td{border-bottom:1px solid var(--dsw-alias-separator-primary);vertical-align:top;padding:7px 10px}.mL8Uca_table tbody tr:last-child td{border-bottom:none}.mL8Uca_table tbody tr:hover td{background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.mL8Uca_cellMuted{color:var(--dsw-alias-label-tertiary)}.mL8Uca_actions{white-space:nowrap;align-items:center;gap:8px;display:flex}.mL8Uca_inlineTest{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:11.5px;display:inline-flex}.mL8Uca_inlineTest[data-status=ok]{color:var(--dsw-alias-state-success-primary)}.mL8Uca_inlineTest[data-status=fail]{color:var(--dsw-alias-state-error-primary)}.mL8Uca_badge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;line-height:1.6;display:inline-block}.mL8Uca_badge[data-kind=key]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}.mL8Uca_badge[data-kind=password]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}.mL8Uca_badge[data-kind=agent]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_badge[data-kind=proxy]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);margin-left:6px}.mL8Uca_badge[data-status=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_badge[data-status=fail]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.mL8Uca_badge[data-status=timeout]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}.mL8Uca_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}.mL8Uca_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}.mL8Uca_primaryButton:disabled{opacity:.5;cursor:default}.mL8Uca_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}.mL8Uca_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_ghostButton:disabled{opacity:.45;cursor:default}.mL8Uca_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}.mL8Uca_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.mL8Uca_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}.mL8Uca_linkButton:hover:not(:disabled){text-decoration:underline}.mL8Uca_linkButton:disabled{opacity:.45;cursor:default}.mL8Uca_linkButton[data-danger]{color:var(--dsw-alias-state-error-primary)}.mL8Uca_spinner{border:2px solid var(--dsw-alias-state-business-primary);vertical-align:-1px;border-top-color:#0000;border-radius:50%;flex:none;width:11px;height:11px;animation:.8s linear infinite mL8Uca_dshSshSpin;display:inline-block}@keyframes mL8Uca_dshSshSpin{to{transform:rotate(360deg)}}.mL8Uca_banner{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;border-radius:8px;padding:8px 12px;font-size:12.5px;line-height:1.5}.mL8Uca_importSkips{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);border-radius:8px;flex-direction:column;gap:4px;margin:6px 0 0;padding:8px 12px;list-style:none;display:flex}.mL8Uca_importSkipRow{justify-content:space-between;align-items:baseline;gap:12px;font-size:12px;display:flex}.mL8Uca_banner[data-kind=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_banner[data-kind=error]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.mL8Uca_banner[data-kind=info]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}.mL8Uca_empty,.mL8Uca_loading{text-align:center;color:var(--dsw-alias-label-tertiary);padding:28px 12px;font-size:12.5px}.mL8Uca_modalBackdrop{z-index:40;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.mL8Uca_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(560px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:12px;padding:18px;display:flex;overflow-y:auto}.mL8Uca_modalTitle{margin:0;font-size:15px;font-weight:700}.mL8Uca_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}.mL8Uca_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}.mL8Uca_hint{color:var(--dsw-alias-label-tertiary);font-size:11.5px}.mL8Uca_field{flex-direction:column;gap:5px;display:flex}.mL8Uca_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}.mL8Uca_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}.mL8Uca_input:focus{border-color:var(--dsw-alias-state-business-primary)}.mL8Uca_input::placeholder{color:var(--dsw-alias-label-tertiary)}.mL8Uca_input:disabled{opacity:.55}.mL8Uca_formRow{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px 12px;display:grid}.mL8Uca_radioRow{align-items:center;gap:16px;display:flex}.mL8Uca_radioLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:13px;display:inline-flex}.mL8Uca_termBody{flex-direction:column;flex:1;gap:8px;min-height:0;display:flex;overflow:hidden}.mL8Uca_termWrap{border:1px solid var(--dsw-alias-border-l1);background:#0b0e14;border-radius:10px;flex:1;min-height:0;position:relative;overflow:hidden}.mL8Uca_termContainer{position:absolute;inset:8px 10px}.mL8Uca_termPlaceholder{z-index:2;text-align:center;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-base);justify-content:center;align-items:center;padding:0 24px;font-size:12.5px;display:flex;position:absolute;inset:0}.mL8Uca_hiddenFile{display:none}.mL8Uca_browsePanel{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;display:flex;overflow:hidden}.mL8Uca_browseHeader{background:var(--dsw-alias-bg-layer-2);border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:8px;padding:8px 10px;display:flex}.mL8Uca_browsePath{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);flex:1;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;overflow:hidden}.mL8Uca_browseList{max-height:240px;overflow-y:auto}.mL8Uca_dirRow{text-align:left;width:100%;font:inherit;color:var(--dsw-alias-label-primary);border:none;border-bottom:1px solid var(--dsw-alias-separator-primary);cursor:pointer;background:0 0;grid-template-columns:minmax(0,1fr) auto auto;gap:12px;padding:5px 10px;font-size:12.5px;display:grid}.mL8Uca_dirRow:hover{background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_dirRow[data-type=dir] .mL8Uca_dirName{color:var(--dsw-alias-state-business-primary)}.mL8Uca_dirRow[data-up]{color:var(--dsw-alias-label-secondary)}.mL8Uca_dirName{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow:hidden}.mL8Uca_dirType{color:var(--dsw-alias-label-tertiary);white-space:nowrap}.mL8Uca_dirSize{color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-align:right}.mL8Uca_transferBlock{flex-direction:column;flex:none;gap:6px;display:flex}.mL8Uca_progressMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:12px;font-size:12px;display:flex}.mL8Uca_progressTrack{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;height:8px;overflow:hidden}.mL8Uca_progressBar{background:var(--dsw-alias-state-business-primary);border-radius:999px;height:100%;transition:width .12s linear}.mL8Uca_tunnelList{flex-direction:column;flex:none;gap:8px;display:flex}.mL8Uca_tunnelRow{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;align-items:center;gap:10px;padding:8px 12px;display:flex}.mL8Uca_tunnelRow[data-state=forwarding]{border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_tunnelRow[data-state=connecting]{border-color:var(--dsw-alias-state-warn-primary)}.mL8Uca_tunnelRow[data-state=failed]{border-color:var(--dsw-alias-state-error-primary)}.mL8Uca_tunnelLabel{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;overflow:hidden}.mL8Uca_formCard{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;gap:10px;padding:12px;display:flex}.mL8Uca_clusterForm{flex-direction:column;flex:none;gap:8px;display:flex}.mL8Uca_clusterFilters{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;display:grid}.mL8Uca_commandInput{min-height:96px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.mL8Uca_cellDetails summary{cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px}.mL8Uca_cellPre{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;border-radius:6px;max-height:180px;margin:4px 0 0;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;overflow:auto}";
693
709
  const tagId = "@linxin666/dsh-ssh/packages/dsh-ssh/src/client/panel/panel.module.css";
694
710
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
695
711
  const tag = document.createElement("style");
@@ -740,6 +756,8 @@ window.__ModuleLoader__.load({
740
756
  "hiddenFile": "mL8Uca_hiddenFile",
741
757
  "hint": "mL8Uca_hint",
742
758
  "iconButton": "mL8Uca_iconButton",
759
+ "importSkipRow": "mL8Uca_importSkipRow",
760
+ "importSkips": "mL8Uca_importSkips",
743
761
  "inlineTest": "mL8Uca_inlineTest",
744
762
  "input": "mL8Uca_input",
745
763
  "linkButton": "mL8Uca_linkButton",
@@ -974,6 +992,7 @@ window.__ModuleLoader__.load({
974
992
  password: "",
975
993
  agentPath: "",
976
994
  proxyJump: (editing?.proxyJump ?? []).join(", "),
995
+ proxyCommand: editing?.proxyCommand ?? "",
977
996
  description: editing?.description ?? "",
978
997
  environment: editing?.environment ?? "",
979
998
  tags: (editing?.tags ?? []).join(", "),
@@ -1034,6 +1053,7 @@ window.__ModuleLoader__.load({
1034
1053
  agentPath: form.agentPath.trim() === "" ? void 0 : form.agentPath.trim()
1035
1054
  },
1036
1055
  proxyJump: splitList(form.proxyJump),
1056
+ proxyCommand: form.proxyCommand.trim(),
1037
1057
  description: form.description.trim() === "" ? void 0 : form.description.trim(),
1038
1058
  environment: form.environment.trim() === "" ? void 0 : form.environment.trim(),
1039
1059
  tags: splitList(form.tags),
@@ -1279,6 +1299,27 @@ window.__ModuleLoader__.load({
1279
1299
  })
1280
1300
  ]
1281
1301
  }),
1302
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1303
+ className: panel_module_css_default.field,
1304
+ children: [
1305
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1306
+ className: panel_module_css_default.fieldLabel,
1307
+ children: tt("form.proxyCommand")
1308
+ }),
1309
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1310
+ className: panel_module_css_default.input,
1311
+ value: form.proxyCommand,
1312
+ placeholder: "corp-vpn proxy %h %p %r",
1313
+ onChange: (event) => {
1314
+ set("proxyCommand", event.target.value);
1315
+ }
1316
+ }),
1317
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1318
+ className: panel_module_css_default.hint,
1319
+ children: tt("form.proxyCommandHint")
1320
+ })
1321
+ ]
1322
+ }),
1282
1323
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1283
1324
  className: panel_module_css_default.formRow,
1284
1325
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
@@ -1380,6 +1421,15 @@ window.__ModuleLoader__.load({
1380
1421
  * add/edit/delete/test actions, ~/.ssh/config import, and a connect action
1381
1422
  * that hands the alias to the terminal tab via onConnect.
1382
1423
  */
1424
+ /** Skipped-block rows rendered before the "and N more" summary. */
1425
+ const IMPORT_SKIP_LIMIT = 8;
1426
+ /** Locale key per import skip reason (no dynamic key concatenation). */
1427
+ const IMPORT_REASON_KEY = {
1428
+ wildcard: "import.reason.wildcard",
1429
+ existing: "import.reason.existing",
1430
+ match: "import.reason.match",
1431
+ invalid: "import.reason.invalid"
1432
+ };
1383
1433
  /**
1384
1434
  * Bucket hosts into collapsible groups (#379). Grouping by tags places a
1385
1435
  * multi-tag host in every one of its tag groups (folder view); hosts without
@@ -1414,6 +1464,8 @@ window.__ModuleLoader__.load({
1414
1464
  const [testResults, setTestResults] = (0, react.useState)({});
1415
1465
  const [importing, setImporting] = (0, react.useState)(false);
1416
1466
  const [notice, setNotice] = (0, react.useState)(null);
1467
+ /** Blocks the last import skipped, with the reason each one was left out. */
1468
+ const [importSkips, setImportSkips] = (0, react.useState)([]);
1417
1469
  const [dialog, setDialog] = (0, react.useState)(null);
1418
1470
  const [groupBy, setGroupBy] = (0, react.useState)("none");
1419
1471
  const [collapsed, setCollapsed] = (0, react.useState)({});
@@ -1501,6 +1553,7 @@ window.__ModuleLoader__.load({
1501
1553
  added: result.added,
1502
1554
  skipped: result.skipped
1503
1555
  }));
1556
+ setImportSkips(result.skippedBlocks);
1504
1557
  load();
1505
1558
  } catch (cause) {
1506
1559
  if (!mountedRef.current) return;
@@ -1521,7 +1574,13 @@ window.__ModuleLoader__.load({
1521
1574
  children: [
1522
1575
  host.host,
1523
1576
  ":",
1524
- host.port
1577
+ host.port,
1578
+ host.proxyCommand !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1579
+ className: panel_module_css_default.badge,
1580
+ "data-kind": "proxy",
1581
+ title: host.proxyCommand,
1582
+ children: tt("hosts.proxyBadge")
1583
+ })
1525
1584
  ]
1526
1585
  }),
1527
1586
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: host.user }),
@@ -1671,6 +1730,25 @@ window.__ModuleLoader__.load({
1671
1730
  "data-kind": "ok",
1672
1731
  children: notice
1673
1732
  }),
1733
+ notice !== null && importSkips.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
1734
+ className: panel_module_css_default.importSkips,
1735
+ children: [importSkips.slice(0, IMPORT_SKIP_LIMIT).map((block) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1736
+ className: panel_module_css_default.importSkipRow,
1737
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1738
+ className: panel_module_css_default.mono,
1739
+ children: block.name
1740
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1741
+ className: panel_module_css_default.cellMuted,
1742
+ children: tt(IMPORT_REASON_KEY[block.reason])
1743
+ })]
1744
+ }, block.name)), importSkips.length > IMPORT_SKIP_LIMIT && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
1745
+ className: panel_module_css_default.importSkipRow,
1746
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1747
+ className: panel_module_css_default.cellMuted,
1748
+ children: tt("import.more", { count: importSkips.length - IMPORT_SKIP_LIMIT })
1749
+ })
1750
+ })]
1751
+ }),
1674
1752
  error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1675
1753
  className: panel_module_css_default.banner,
1676
1754
  "data-kind": "error",
@@ -15439,10 +15517,10 @@ window.__ModuleLoader__.load({
15439
15517
  //#region src/client/panel/TunnelsTab.tsx
15440
15518
  /**
15441
15519
  * Tunnels tab: the live local port-forward list (auto-refresh every 5s while
15442
- * mounted) with per-row stop, a stop-all action scoped to the selected alias,
15520
+ * visible) with per-row stop, a stop-all action scoped to the selected alias,
15443
15521
  * and a new-tunnel form.
15444
15522
  */
15445
- /** Live-tunnel polling interval while the tab is mounted (ms). */
15523
+ /** Live-tunnel polling interval while the tab and page are visible (ms). */
15446
15524
  const TUNNEL_POLL_MS = 5e3;
15447
15525
  /**
15448
15526
  * Return `next` only when the tunnel list changed in a user-visible way
@@ -15461,7 +15539,7 @@ window.__ModuleLoader__.load({
15461
15539
  return null;
15462
15540
  }
15463
15541
  /** The tunnels tab. */
15464
- function TunnelsTab({ api }) {
15542
+ function TunnelsTab({ api, active = true }) {
15465
15543
  const [hosts, setHosts] = (0, react.useState)([]);
15466
15544
  const [tunnels, setTunnels] = (0, react.useState)(null);
15467
15545
  const [error, setError] = (0, react.useState)(null);
@@ -15484,27 +15562,61 @@ window.__ModuleLoader__.load({
15484
15562
  };
15485
15563
  }, [api]);
15486
15564
  const seqRef = (0, react.useRef)(0);
15565
+ const automaticRead = (0, react.useRef)({ running: false });
15487
15566
  (0, react.useEffect)(() => {
15567
+ if (!active) return;
15568
+ let disposed = false;
15569
+ const read = automaticRead.current;
15570
+ let timer;
15488
15571
  const load = async () => {
15572
+ if (disposed || read.running || document.visibilityState === "hidden") return;
15573
+ read.running = true;
15489
15574
  const seq = ++seqRef.current;
15490
15575
  try {
15491
15576
  const list = await api.listTunnels();
15492
- if (seq !== seqRef.current) return;
15577
+ if (disposed || seq !== seqRef.current) return;
15493
15578
  setTunnels((prev) => diffTunnels(prev, list) ?? prev);
15494
15579
  setError(null);
15495
15580
  } catch (cause) {
15496
- if (seq !== seqRef.current) return;
15581
+ if (disposed || seq !== seqRef.current) return;
15497
15582
  setError(errorMessage(cause));
15583
+ } finally {
15584
+ read.running = false;
15585
+ const resume = read.resume;
15586
+ read.resume = void 0;
15587
+ resume?.();
15498
15588
  }
15499
15589
  };
15500
- load();
15501
- const timer = setInterval(() => {
15502
- load();
15503
- }, TUNNEL_POLL_MS);
15590
+ const resume = () => {
15591
+ if (disposed || document.visibilityState === "hidden") return;
15592
+ if (read.running) read.resume = resume;
15593
+ else load();
15594
+ };
15595
+ const stop = () => {
15596
+ if (timer !== void 0) clearInterval(timer);
15597
+ timer = void 0;
15598
+ };
15599
+ const onVisibility = () => {
15600
+ if (document.visibilityState === "hidden") {
15601
+ stop();
15602
+ if (read.resume === resume) read.resume = void 0;
15603
+ } else if (timer === void 0) {
15604
+ resume();
15605
+ timer = setInterval(() => {
15606
+ load();
15607
+ }, TUNNEL_POLL_MS);
15608
+ }
15609
+ };
15610
+ document.addEventListener("visibilitychange", onVisibility);
15611
+ onVisibility();
15504
15612
  return () => {
15505
- clearInterval(timer);
15613
+ disposed = true;
15614
+ seqRef.current += 1;
15615
+ if (read.resume === resume) read.resume = void 0;
15616
+ stop();
15617
+ document.removeEventListener("visibilitychange", onVisibility);
15506
15618
  };
15507
- }, [api]);
15619
+ }, [api, active]);
15508
15620
  const refresh = async () => {
15509
15621
  const seq = ++seqRef.current;
15510
15622
  try {
@@ -15748,6 +15860,7 @@ window.__ModuleLoader__.load({
15748
15860
  ];
15749
15861
  /** The tabbed SSH panel. */
15750
15862
  function SshPanel({ controller, api, terminalFont }) {
15863
+ const panelOpen = (0, react.useSyncExternalStore)((0, react.useCallback)((listener) => controller.subscribe(listener), [controller]), (0, react.useCallback)(() => controller.getSnapshot().panelOpen, [controller]));
15751
15864
  const [activeTab, setActiveTab] = (0, react.useState)("hosts");
15752
15865
  const [connectRequest, setConnectRequest] = (0, react.useState)(null);
15753
15866
  const handleConnect = (alias) => {
@@ -15811,7 +15924,10 @@ window.__ModuleLoader__.load({
15811
15924
  terminalFont
15812
15925
  }),
15813
15926
  activeTab === "transfer" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TransferTab, { api }),
15814
- activeTab === "tunnels" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TunnelsTab, { api }),
15927
+ activeTab === "tunnels" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TunnelsTab, {
15928
+ api,
15929
+ active: panelOpen
15930
+ }),
15815
15931
  activeTab === "cluster" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClusterTab, { api })
15816
15932
  ]
15817
15933
  })
@@ -15819,6 +15935,94 @@ window.__ModuleLoader__.load({
15819
15935
  });
15820
15936
  }
15821
15937
  //#endregion
15938
+ //#region src/client/body-mutations.ts
15939
+ /** Cross-bundle registry key; `Symbol.for` so every module copy agrees. */
15940
+ const HUB_KEY = Symbol.for("dsh-web.body-mutation-hub");
15941
+ const INVALIDATION_ONLY = Symbol.for("dsh-web.body-mutation-invalidation");
15942
+ function needsRecords(subscribers) {
15943
+ for (const listener of subscribers) if (!listener[INVALIDATION_ONLY]) return true;
15944
+ return false;
15945
+ }
15946
+ /**
15947
+ * Subscribe to a coalesced DOM re-check without retaining mutation records.
15948
+ * The marked wrapper also works with an older hub, which delivers records
15949
+ * that it simply ignores until a page reload picks up the updated hub.
15950
+ */
15951
+ function subscribeBodyInvalidations(subscriber) {
15952
+ const listener = () => {
15953
+ subscriber();
15954
+ };
15955
+ listener[INVALIDATION_ONLY] = true;
15956
+ return subscribeBodyMutations(listener);
15957
+ }
15958
+ /**
15959
+ * Subscribe to body-level childList mutations.
15960
+ * @param subscriber - called at most once per animation frame with the records
15961
+ * collected since the previous flush; must be safe to run repeatedly.
15962
+ * @returns the disposer removing this subscriber (and the observer when it was
15963
+ * the last one).
15964
+ */
15965
+ function subscribeBodyMutations(subscriber) {
15966
+ if (typeof globalThis === "undefined" || typeof document === "undefined") return () => {};
15967
+ if (typeof MutationObserver !== "function") return () => {};
15968
+ const registry = globalThis;
15969
+ let hub = registry[HUB_KEY];
15970
+ if (hub === void 0) {
15971
+ const subscribers = /* @__PURE__ */ new Set();
15972
+ const created = {
15973
+ observer: void 0,
15974
+ subscribers,
15975
+ pending: [],
15976
+ scheduled: false
15977
+ };
15978
+ const flush = () => {
15979
+ created.frame = void 0;
15980
+ created.scheduled = false;
15981
+ const batch = created.pending;
15982
+ created.pending = [];
15983
+ for (const listener of [...subscribers]) {
15984
+ if (!subscribers.has(listener)) continue;
15985
+ try {
15986
+ listener(batch);
15987
+ } catch {}
15988
+ }
15989
+ };
15990
+ const schedule = () => {
15991
+ if (created.scheduled) return;
15992
+ created.scheduled = true;
15993
+ if (typeof requestAnimationFrame === "function") created.frame = requestAnimationFrame(flush);
15994
+ else flush();
15995
+ };
15996
+ created.observer = new MutationObserver((records) => {
15997
+ if (needsRecords(subscribers)) for (const record of records) created.pending.push(record);
15998
+ schedule();
15999
+ });
16000
+ created.observer.observe(document.body ?? document.documentElement, {
16001
+ childList: true,
16002
+ subtree: true
16003
+ });
16004
+ registry[HUB_KEY] = created;
16005
+ hub = created;
16006
+ }
16007
+ const active = hub;
16008
+ active.subscribers.add(subscriber);
16009
+ let subscribed = true;
16010
+ return () => {
16011
+ if (!subscribed) return;
16012
+ subscribed = false;
16013
+ active.subscribers.delete(subscriber);
16014
+ if (!needsRecords(active.subscribers)) active.pending = [];
16015
+ if (active.subscribers.size === 0 && registry[HUB_KEY] === active) {
16016
+ active.observer.disconnect();
16017
+ if (active.frame !== void 0 && typeof cancelAnimationFrame === "function") cancelAnimationFrame(active.frame);
16018
+ active.frame = void 0;
16019
+ active.pending = [];
16020
+ active.scheduled = false;
16021
+ delete registry[HUB_KEY];
16022
+ }
16023
+ };
16024
+ }
16025
+ //#endregion
15822
16026
  //#region src/client/panel-mount-core.ts
15823
16027
  /**
15824
16028
  * Center-column panel takeover lifecycle.
@@ -15863,32 +16067,31 @@ window.__ModuleLoader__.load({
15863
16067
  });
15864
16068
  } catch {}
15865
16069
  const ensure = () => {
15866
- if (container !== void 0) {
15867
- if (container.isConnected) return;
16070
+ if (container !== void 0 && !container.isConnected) {
15868
16071
  root?.unmount();
15869
16072
  root = void 0;
15870
16073
  container.remove();
15871
16074
  container = void 0;
15872
16075
  }
15873
- const column = conversationColumn();
15874
- if (column === void 0) return;
15875
- container = document.createElement("div");
15876
- container.dataset[options.viewDatasetKey] = "";
15877
- container.dataset.dshPlugin = options.pluginName;
15878
- container.className = options.viewClassName;
15879
- column.appendChild(container);
16076
+ if (container === void 0) {
16077
+ const column = conversationColumn();
16078
+ if (column === void 0) return;
16079
+ container = document.createElement("div");
16080
+ container.dataset[options.viewDatasetKey] = "";
16081
+ container.dataset.dshPlugin = options.pluginName;
16082
+ container.className = options.viewClassName;
16083
+ column.appendChild(container);
16084
+ }
16085
+ if (root !== void 0 || !options.isOpen()) return;
15880
16086
  root = (0, react_dom_client.createRoot)(container);
15881
16087
  options.render(root);
15882
16088
  };
15883
- const waitObserver = new MutationObserver(() => {
16089
+ const unsubscribeBody = subscribeBodyInvalidations(() => {
15884
16090
  ensure();
15885
16091
  });
15886
- waitObserver.observe(document.body, {
15887
- childList: true,
15888
- subtree: true
15889
- });
15890
16092
  const applyActive = () => {
15891
16093
  if (options.isOpen()) {
16094
+ ensure();
15892
16095
  document.documentElement.removeAttribute(options.siblingActiveAttribute);
15893
16096
  document.documentElement.setAttribute(options.activeAttribute, "");
15894
16097
  document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: options.panelName }));
@@ -15911,7 +16114,7 @@ window.__ModuleLoader__.load({
15911
16114
  return () => {
15912
16115
  document.removeEventListener("click", onClickSidebarRow, true);
15913
16116
  document.removeEventListener(ACTIVATE_EVENT, onOtherActivate);
15914
- waitObserver.disconnect();
16117
+ unsubscribeBody();
15915
16118
  unsubscribe();
15916
16119
  unsubscribeLocale?.();
15917
16120
  document.documentElement.removeAttribute(options.activeAttribute);
@@ -15988,6 +16191,21 @@ window.__ModuleLoader__.load({
15988
16191
  };
15989
16192
  //#endregion
15990
16193
  //#region src/client/sidebar-entry-core.ts
16194
+ /**
16195
+ * Shared sidebar entry injection core.
16196
+ *
16197
+ * dsh's sidebar shell exposes no slot an external plugin can register into,
16198
+ * so the entry row is injected between the shell's New Session button and the
16199
+ * workspace browser. The injection self-heals: a MutationObserver watches the
16200
+ * sidebar root and re-inserts the row whenever a React re-render displaces it
16201
+ * (re-insertion happens in the same frame, before paint, so no flicker).
16202
+ *
16203
+ * The row is plain DOM (no React tree) so it can never disturb the shell's
16204
+ * reconciliation; the view it toggles is a separate root owned by the caller.
16205
+ *
16206
+ * Packages receive this file as a generated copy via scripts/sync-shared.mjs;
16207
+ * edit the shared source and re-run the sync instead of editing a copy.
16208
+ */
15991
16209
  /** Find the sidebar shell root element, or undefined while not yet mounted. */
15992
16210
  function sidebarRoot() {
15993
16211
  const column = document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]");
@@ -16076,13 +16294,9 @@ window.__ModuleLoader__.load({
16076
16294
  subtree: true
16077
16295
  });
16078
16296
  };
16079
- const waitObserver = new MutationObserver(() => {
16297
+ const unsubscribeBody = subscribeBodyInvalidations(() => {
16080
16298
  tryPlace();
16081
16299
  });
16082
- waitObserver.observe(document.body, {
16083
- childList: true,
16084
- subtree: true
16085
- });
16086
16300
  const rootObserver = new MutationObserver(() => {
16087
16301
  if (root === void 0 || !root.isConnected) {
16088
16302
  placed = false;
@@ -16102,7 +16316,7 @@ window.__ModuleLoader__.load({
16102
16316
  })();
16103
16317
  tryPlace();
16104
16318
  return () => {
16105
- waitObserver.disconnect();
16319
+ unsubscribeBody();
16106
16320
  rootObserver.disconnect();
16107
16321
  unsubscribeRefresh?.();
16108
16322
  unsubscribeActive?.();
@@ -16152,7 +16366,7 @@ window.__ModuleLoader__.load({
16152
16366
  /** The building package's version, when the bundle carries it. */
16153
16367
  function bakedVersion() {
16154
16368
  try {
16155
- return "0.3.19";
16369
+ return "0.3.21";
16156
16370
  } catch {
16157
16371
  return;
16158
16372
  }