@linxin666/dsh-ssh 0.4.2 → 0.4.4
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 +371 -461
- package/lib/client.js.map +1 -1
- package/lib/index.js +707 -466
- package/lib/types/client/api.d.ts +55 -4
- package/lib/types/client/locales.d.ts +1 -0
- package/lib/types/client/native-panel.d.ts +70 -0
- package/lib/types/client/panel/SshPanel.d.ts +1 -3
- package/lib/types/client/panel/TerminalTab.d.ts +6 -1
- package/lib/types/client/panel/controller.d.ts +71 -4
- package/lib/types/engine/terminal-sessions.d.ts +67 -0
- package/lib/types/mount-once.d.ts +38 -7
- package/lib/types/protocol.d.ts +12 -1
- package/lib/types/routes.d.ts +2 -0
- package/package.json +19 -16
- package/src/client/api.ts +137 -24
- package/src/client/index.ts +31 -9
- package/src/client/locales.ts +2 -0
- package/src/client/native-panel.tsx +141 -0
- package/src/client/panel/SshPanel.tsx +25 -28
- package/src/client/panel/TerminalTab.tsx +54 -14
- package/src/client/panel/controller.ts +126 -7
- package/src/client/panel/panel.module.css +8 -128
- package/src/engine/terminal-sessions.ts +280 -0
- package/src/index.ts +4 -1
- package/src/mount-once.ts +107 -13
- package/src/protocol.ts +6 -1
- package/src/routes.ts +26 -107
- package/lib/types/client/body-mutations.d.ts +0 -45
- package/lib/types/client/mount.d.ts +0 -27
- package/lib/types/client/panel-mount-core.d.ts +0 -56
- package/lib/types/client/sidebar-entry-core.d.ts +0 -55
- package/lib/types/client/sidebar-entry.d.ts +0 -26
- package/src/client/body-mutations.ts +0 -133
- package/src/client/mount.tsx +0 -47
- package/src/client/panel-mount-core.ts +0 -155
- package/src/client/sidebar-entry-core.ts +0 -237
- package/src/client/sidebar-entry.ts +0 -51
package/lib/client.js
CHANGED
|
@@ -9,7 +9,6 @@ window.__ModuleLoader__.load({
|
|
|
9
9
|
//#endregion
|
|
10
10
|
let react = require("react");
|
|
11
11
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
12
|
-
let react_dom_client = require("react-dom/client");
|
|
13
12
|
//#region src/protocol.ts
|
|
14
13
|
const SSH_API = {
|
|
15
14
|
hosts: "/api/dsh-ssh/hosts",
|
|
@@ -116,6 +115,7 @@ window.__ModuleLoader__.load({
|
|
|
116
115
|
"terminal.ready": "终端已连接({alias})",
|
|
117
116
|
"terminal.exited": "终端已退出({alias})",
|
|
118
117
|
"terminal.error": "终端错误:{error}",
|
|
118
|
+
"terminal.noWebSocket": "当前页面不支持 WebSocket(应用外壳只转发 HTTP),请改用浏览器打开的 Web 界面使用终端。",
|
|
119
119
|
"terminal.auth.title": "二次身份验证 (2FA / 交互式认证)",
|
|
120
120
|
"terminal.auth.submit": "提交验证码",
|
|
121
121
|
"terminal.auth.cancel": "取消",
|
|
@@ -271,6 +271,7 @@ window.__ModuleLoader__.load({
|
|
|
271
271
|
"terminal.ready": "Terminal connected ({alias})",
|
|
272
272
|
"terminal.exited": "Terminal exited ({alias})",
|
|
273
273
|
"terminal.error": "Terminal error: {error}",
|
|
274
|
+
"terminal.noWebSocket": "This page cannot carry a WebSocket (the application shell forwards HTTP only). Open the Web UI in a browser to use the terminal.",
|
|
274
275
|
"terminal.auth.title": "Two-Factor Authentication (2FA)",
|
|
275
276
|
"terminal.auth.submit": "Submit Code",
|
|
276
277
|
"terminal.auth.cancel": "Cancel",
|
|
@@ -448,6 +449,63 @@ window.__ModuleLoader__.load({
|
|
|
448
449
|
const text = search.toString();
|
|
449
450
|
return text === "" ? "" : "?" + text;
|
|
450
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* The WebSocket URL the terminal route is reached at, or undefined when this
|
|
454
|
+
* page cannot carry one.
|
|
455
|
+
*
|
|
456
|
+
* A page delivered by an application on this machine (the official DSH
|
|
457
|
+
* Desktop shell serves its Web GUI from `dsh-app://app/`) has no WebSocket
|
|
458
|
+
* transport: its scheme handler forwards HTTP requests to the local host but
|
|
459
|
+
* cannot upgrade a socket, so `ws://app/...` never connects and the terminal
|
|
460
|
+
* reports "connection error" (issue #1744). Every other page is a web page and
|
|
461
|
+
* resolves the socket against its own origin, exactly as before.
|
|
462
|
+
*
|
|
463
|
+
* A blank authority is treated the same as an application scheme: there is
|
|
464
|
+
* nothing to dial, and the caller gets the actionable reason instead of a
|
|
465
|
+
* socket that can only fail.
|
|
466
|
+
*
|
|
467
|
+
* @param location - the page location to read.
|
|
468
|
+
* @param search - the query string carrying `alias` or `session`.
|
|
469
|
+
* @returns the absolute `ws:`/`wss:` URL, or undefined when the page cannot carry one.
|
|
470
|
+
*/
|
|
471
|
+
function terminalSocketUrl(location, search) {
|
|
472
|
+
if (WEB_PAGE_PROTOCOLS.includes(location.protocol)) return (location.protocol === "https:" ? "wss" : "ws") + "://" + location.host + SSH_API.terminal + search;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Schemes a network page can be delivered with. Every other scheme belongs to
|
|
476
|
+
* an application on this machine, which is the same classification the remote
|
|
477
|
+
* channel and the update seat use (`isWebPageProtocol` / `isApplicationDeliveredPage`).
|
|
478
|
+
*/
|
|
479
|
+
const WEB_PAGE_PROTOCOLS = [
|
|
480
|
+
"http:",
|
|
481
|
+
"https:",
|
|
482
|
+
"blob:",
|
|
483
|
+
"data:",
|
|
484
|
+
"about:",
|
|
485
|
+
"filesystem:"
|
|
486
|
+
];
|
|
487
|
+
/**
|
|
488
|
+
* A terminal connection that never opened: it reports one failure once the
|
|
489
|
+
* view has attached its `onExit`, so the tab shows why instead of sitting on a
|
|
490
|
+
* spinner, and every later frame is a no-op.
|
|
491
|
+
*/
|
|
492
|
+
function failedTerminal(reason) {
|
|
493
|
+
const connection = {
|
|
494
|
+
onReady: void 0,
|
|
495
|
+
onOutput: void 0,
|
|
496
|
+
onExit: void 0,
|
|
497
|
+
onAuthPrompt: void 0,
|
|
498
|
+
send: () => void 0,
|
|
499
|
+
resize: () => void 0,
|
|
500
|
+
sendAuthResponse: () => void 0,
|
|
501
|
+
detach: () => void 0,
|
|
502
|
+
close: () => void 0
|
|
503
|
+
};
|
|
504
|
+
queueMicrotask(() => {
|
|
505
|
+
connection.onExit?.(null, reason);
|
|
506
|
+
});
|
|
507
|
+
return connection;
|
|
508
|
+
}
|
|
451
509
|
/** The browser half's only data entry point. */
|
|
452
510
|
var SshApi = class {
|
|
453
511
|
async listHosts(queryText) {
|
|
@@ -644,39 +702,83 @@ window.__ModuleLoader__.load({
|
|
|
644
702
|
bytes: received
|
|
645
703
|
};
|
|
646
704
|
}
|
|
647
|
-
/**
|
|
705
|
+
/**
|
|
706
|
+
* Open a new host terminal session for the alias.
|
|
707
|
+
* @param alias - the configured host alias.
|
|
708
|
+
* @param cols - initial PTY width.
|
|
709
|
+
* @param rows - initial PTY height.
|
|
710
|
+
* @returns the live connection; its ready callback carries the session id
|
|
711
|
+
* {@link attachTerminal} uses after a view detach.
|
|
712
|
+
*/
|
|
648
713
|
openTerminal(alias, cols, rows) {
|
|
649
|
-
|
|
714
|
+
return this.terminalSocket(query({
|
|
650
715
|
alias,
|
|
651
716
|
cols,
|
|
652
717
|
rows
|
|
653
|
-
});
|
|
654
|
-
|
|
718
|
+
}));
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Reattach to a host-side session that outlived its view (a panel switch, a
|
|
722
|
+
* page navigation). The host replays its scrollback before the ready frame.
|
|
723
|
+
* @param sessionId - the id the previous connection reported on ready.
|
|
724
|
+
* @param cols - the view's current width.
|
|
725
|
+
* @param rows - the view's current height.
|
|
726
|
+
*/
|
|
727
|
+
attachTerminal(sessionId, cols, rows) {
|
|
728
|
+
return this.terminalSocket(query({
|
|
729
|
+
session: sessionId,
|
|
730
|
+
cols,
|
|
731
|
+
rows
|
|
732
|
+
}));
|
|
733
|
+
}
|
|
734
|
+
/** One terminal socket over either an alias (open) or a session id (attach). */
|
|
735
|
+
terminalSocket(search) {
|
|
736
|
+
const target = terminalSocketUrl(window.location, search);
|
|
737
|
+
if (target === void 0) return failedTerminal(tt("terminal.noWebSocket"));
|
|
738
|
+
let socket;
|
|
739
|
+
try {
|
|
740
|
+
socket = new WebSocket(target);
|
|
741
|
+
} catch (error) {
|
|
742
|
+
return failedTerminal(error instanceof Error ? error.message : String(error));
|
|
743
|
+
}
|
|
744
|
+
let leaving = false;
|
|
745
|
+
const sendFrame = (frame) => {
|
|
746
|
+
if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify(frame));
|
|
747
|
+
};
|
|
655
748
|
const connection = {
|
|
656
749
|
onReady: void 0,
|
|
657
750
|
onOutput: void 0,
|
|
658
751
|
onExit: void 0,
|
|
659
752
|
onAuthPrompt: void 0,
|
|
660
753
|
send: (data) => {
|
|
661
|
-
|
|
754
|
+
sendFrame({
|
|
662
755
|
type: "input",
|
|
663
756
|
data
|
|
664
|
-
})
|
|
757
|
+
});
|
|
665
758
|
},
|
|
666
759
|
resize: (cols, rows) => {
|
|
667
|
-
|
|
760
|
+
sendFrame({
|
|
668
761
|
type: "resize",
|
|
669
762
|
cols,
|
|
670
763
|
rows
|
|
671
|
-
})
|
|
764
|
+
});
|
|
672
765
|
},
|
|
673
766
|
sendAuthResponse: (responses) => {
|
|
674
|
-
|
|
767
|
+
sendFrame({
|
|
675
768
|
type: "auth_response",
|
|
676
769
|
responses
|
|
677
|
-
})
|
|
770
|
+
});
|
|
771
|
+
},
|
|
772
|
+
detach: () => {
|
|
773
|
+
leaving = true;
|
|
774
|
+
sendFrame({ type: "detach" });
|
|
775
|
+
try {
|
|
776
|
+
socket.close();
|
|
777
|
+
} catch {}
|
|
678
778
|
},
|
|
679
779
|
close: () => {
|
|
780
|
+
leaving = true;
|
|
781
|
+
sendFrame({ type: "close" });
|
|
680
782
|
try {
|
|
681
783
|
socket.close();
|
|
682
784
|
} catch {}
|
|
@@ -689,23 +791,127 @@ window.__ModuleLoader__.load({
|
|
|
689
791
|
} catch {
|
|
690
792
|
return;
|
|
691
793
|
}
|
|
692
|
-
if (frame.type === "ready") connection.onReady?.();
|
|
794
|
+
if (frame.type === "ready") connection.onReady?.(frame.sessionId, frame.alias);
|
|
693
795
|
else if (frame.type === "output") connection.onOutput?.(frame.data);
|
|
694
796
|
else if (frame.type === "exit") connection.onExit?.(frame.code, frame.error);
|
|
695
797
|
else if (frame.type === "auth_prompt") connection.onAuthPrompt?.(frame.name, frame.instructions, frame.prompts);
|
|
696
798
|
};
|
|
697
799
|
socket.onclose = () => {
|
|
698
|
-
connection.onExit?.(null, "connection closed");
|
|
800
|
+
if (!leaving) connection.onExit?.(null, "connection closed");
|
|
699
801
|
};
|
|
700
802
|
socket.onerror = () => {
|
|
701
|
-
connection.onExit?.(null, "connection error");
|
|
803
|
+
if (!leaving) connection.onExit?.(null, "connection error");
|
|
702
804
|
};
|
|
703
805
|
return connection;
|
|
704
806
|
}
|
|
705
807
|
};
|
|
808
|
+
/** The panel state owner the sidebar row and the view both read. */
|
|
809
|
+
var PanelController = class {
|
|
810
|
+
deps;
|
|
811
|
+
panelOpen = false;
|
|
812
|
+
activeTab = "hosts";
|
|
813
|
+
connectRequest = null;
|
|
814
|
+
terminalSessionId;
|
|
815
|
+
listeners = /* @__PURE__ */ new Set();
|
|
816
|
+
/** Cached so getSnapshot is referentially stable between changes. */
|
|
817
|
+
snapshot = {
|
|
818
|
+
panelOpen: false,
|
|
819
|
+
activeTab: "hosts",
|
|
820
|
+
connectRequest: null,
|
|
821
|
+
terminalSessionId: void 0
|
|
822
|
+
};
|
|
823
|
+
constructor(deps = {}) {
|
|
824
|
+
this.deps = deps;
|
|
825
|
+
}
|
|
826
|
+
getSnapshot() {
|
|
827
|
+
return this.snapshot;
|
|
828
|
+
}
|
|
829
|
+
subscribe(fn) {
|
|
830
|
+
this.listeners.add(fn);
|
|
831
|
+
return () => {
|
|
832
|
+
this.listeners.delete(fn);
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Show the panel. The layout owns which panel the column renders, so the
|
|
837
|
+
* state flip and the selection travel together; the selection is requested
|
|
838
|
+
* after the snapshot flips, so a subscriber never observes "open" while the
|
|
839
|
+
* shell still shows the conversation.
|
|
840
|
+
*/
|
|
841
|
+
open() {
|
|
842
|
+
if (this.panelOpen) return;
|
|
843
|
+
this.panelOpen = true;
|
|
844
|
+
this.commit();
|
|
845
|
+
this.selectPanel("ssh");
|
|
846
|
+
}
|
|
847
|
+
/** Return the column to the conversation, asking the layout explicitly. */
|
|
848
|
+
close() {
|
|
849
|
+
if (!this.panelOpen) return;
|
|
850
|
+
this.panelOpen = false;
|
|
851
|
+
this.commit();
|
|
852
|
+
this.selectPanel(null);
|
|
853
|
+
}
|
|
854
|
+
toggle() {
|
|
855
|
+
if (this.panelOpen) this.close();
|
|
856
|
+
else this.open();
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Reflect a selection made outside this controller (the user clicked another
|
|
860
|
+
* sidebar row, or the layout dropped the panel id).
|
|
861
|
+
* @param panelId - the layout's current panel id, or null for the conversation.
|
|
862
|
+
*/
|
|
863
|
+
syncPanelSelection(panelId) {
|
|
864
|
+
const open = panelId === "ssh";
|
|
865
|
+
if (open === this.panelOpen) return;
|
|
866
|
+
this.panelOpen = open;
|
|
867
|
+
this.commit();
|
|
868
|
+
}
|
|
869
|
+
/** Switch tabs, keeping every other piece of view state. */
|
|
870
|
+
setActiveTab(tab) {
|
|
871
|
+
if (this.activeTab === tab) return;
|
|
872
|
+
this.activeTab = tab;
|
|
873
|
+
this.commit();
|
|
874
|
+
}
|
|
875
|
+
/** Hand the terminal tab a host to connect to (hosts-tab "connect" action). */
|
|
876
|
+
requestConnect(alias) {
|
|
877
|
+
this.connectRequest = {
|
|
878
|
+
alias,
|
|
879
|
+
nonce: Date.now()
|
|
880
|
+
};
|
|
881
|
+
this.activeTab = "terminal";
|
|
882
|
+
this.commit();
|
|
883
|
+
}
|
|
884
|
+
/** Record the host-side session so a later page mount can reattach. */
|
|
885
|
+
setTerminalSession(sessionId) {
|
|
886
|
+
if (this.terminalSessionId === sessionId) return;
|
|
887
|
+
this.terminalSessionId = sessionId;
|
|
888
|
+
this.commit();
|
|
889
|
+
}
|
|
890
|
+
/** Forget the session (it exited, or the user disconnected). */
|
|
891
|
+
clearTerminalSession() {
|
|
892
|
+
if (this.terminalSessionId === void 0) return;
|
|
893
|
+
this.terminalSessionId = void 0;
|
|
894
|
+
this.commit();
|
|
895
|
+
}
|
|
896
|
+
commit() {
|
|
897
|
+
this.snapshot = {
|
|
898
|
+
panelOpen: this.panelOpen,
|
|
899
|
+
activeTab: this.activeTab,
|
|
900
|
+
connectRequest: this.connectRequest,
|
|
901
|
+
terminalSessionId: this.terminalSessionId
|
|
902
|
+
};
|
|
903
|
+
for (const fn of [...this.listeners]) fn();
|
|
904
|
+
}
|
|
905
|
+
/** Ask the layout to select a panel; a shell without the face is a no-op. */
|
|
906
|
+
selectPanel(panelId) {
|
|
907
|
+
try {
|
|
908
|
+
this.deps.panel?.select(panelId);
|
|
909
|
+
} catch {}
|
|
910
|
+
}
|
|
911
|
+
};
|
|
706
912
|
//#endregion
|
|
707
913
|
//#region \0dsh-css:packages/dsh-ssh/src/client/panel/panel.module.css.mjs
|
|
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;min-height:36px;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;text-align:left;white-space:nowrap;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;margin:0 2px;padding:7px 8px;font-size:14px;line-height:22px;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:16px;height:16px;display:inline-flex}.mL8Uca_entryIcon svg{width:16px;height:16px;display:block}.mL8Uca_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] .mL8Uca_entry,[data-sidebar-collapsed] .mL8Uca_entry{border-radius:12px;justify-content:center;width:36px;min-height:36px;margin:0 auto 12px;padding:0}[data-dsh-frame][data-sidebar-collapsed] .mL8Uca_entryIcon,[data-sidebar-collapsed] .mL8Uca_entryIcon,[data-dsh-frame][data-sidebar-collapsed] .mL8Uca_entryIcon svg,[data-sidebar-collapsed] .mL8Uca_entryIcon svg{width:18px;height:18px}[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}";
|
|
914
|
+
const css = ".mL8Uca_view{height:100%;min-height:0;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}";
|
|
709
915
|
const tagId = "@linxin666/dsh-ssh/packages/dsh-ssh/src/client/panel/panel.module.css";
|
|
710
916
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
711
917
|
const tag = document.createElement("style");
|
|
@@ -736,9 +942,6 @@ window.__ModuleLoader__.load({
|
|
|
736
942
|
"dirType": "mL8Uca_dirType",
|
|
737
943
|
"dshSshSpin": "mL8Uca_dshSshSpin",
|
|
738
944
|
"empty": "mL8Uca_empty",
|
|
739
|
-
"entry": "mL8Uca_entry",
|
|
740
|
-
"entryIcon": "mL8Uca_entryIcon",
|
|
741
|
-
"entryLabel": "mL8Uca_entryLabel",
|
|
742
945
|
"field": "mL8Uca_field",
|
|
743
946
|
"fieldLabel": "mL8Uca_fieldLabel",
|
|
744
947
|
"fillBody": "mL8Uca_fillBody",
|
|
@@ -14893,7 +15096,7 @@ window.__ModuleLoader__.load({
|
|
|
14893
15096
|
subscribe: () => () => void 0
|
|
14894
15097
|
};
|
|
14895
15098
|
/** The xterm terminal view. */
|
|
14896
|
-
function TerminalTab({ api, presetAlias, requestId, terminalFont }) {
|
|
15099
|
+
function TerminalTab({ api, controller, sessionId, presetAlias, requestId, terminalFont }) {
|
|
14897
15100
|
const [hosts, setHosts] = (0, react.useState)([]);
|
|
14898
15101
|
const [alias, setAlias] = (0, react.useState)(presetAlias ?? "");
|
|
14899
15102
|
const [status, setStatus] = (0, react.useState)({ kind: "idle" });
|
|
@@ -14938,7 +15141,12 @@ window.__ModuleLoader__.load({
|
|
|
14938
15141
|
(0, react.useEffect)(() => {
|
|
14939
15142
|
if (presetAlias !== void 0) setAlias(presetAlias);
|
|
14940
15143
|
}, [presetAlias, requestId]);
|
|
14941
|
-
|
|
15144
|
+
/**
|
|
15145
|
+
* Tear the view down.
|
|
15146
|
+
* @param leave - `detach` leaves the host session alive for a reattach
|
|
15147
|
+
* (panel switch, unmount); `close` ends it (the disconnect control).
|
|
15148
|
+
*/
|
|
15149
|
+
const teardown = (leave) => {
|
|
14942
15150
|
setAuthPrompt(void 0);
|
|
14943
15151
|
setAuthInputs([]);
|
|
14944
15152
|
const connection = connRef.current;
|
|
@@ -14948,7 +15156,8 @@ window.__ModuleLoader__.load({
|
|
|
14948
15156
|
connection.onOutput = void 0;
|
|
14949
15157
|
connection.onExit = void 0;
|
|
14950
15158
|
connection.onAuthPrompt = void 0;
|
|
14951
|
-
connection.
|
|
15159
|
+
if (leave === "detach") connection.detach();
|
|
15160
|
+
else connection.close();
|
|
14952
15161
|
}
|
|
14953
15162
|
dataSubRef.current?.dispose();
|
|
14954
15163
|
dataSubRef.current = null;
|
|
@@ -14957,7 +15166,7 @@ window.__ModuleLoader__.load({
|
|
|
14957
15166
|
fitRef.current = null;
|
|
14958
15167
|
};
|
|
14959
15168
|
(0, react.useEffect)(() => () => {
|
|
14960
|
-
teardown();
|
|
15169
|
+
teardown("detach");
|
|
14961
15170
|
}, []);
|
|
14962
15171
|
(0, react.useEffect)(() => {
|
|
14963
15172
|
let lastCols = -1;
|
|
@@ -14988,12 +15197,15 @@ window.__ModuleLoader__.load({
|
|
|
14988
15197
|
window.removeEventListener("resize", sync);
|
|
14989
15198
|
};
|
|
14990
15199
|
}, []);
|
|
14991
|
-
|
|
14992
|
-
|
|
15200
|
+
/**
|
|
15201
|
+
* Build the xterm view and bind one connection to it.
|
|
15202
|
+
* @param open - opens the transport once the terminal has a size.
|
|
15203
|
+
*/
|
|
15204
|
+
const startSession = (open) => {
|
|
14993
15205
|
const container = containerRef.current;
|
|
14994
|
-
if (
|
|
15206
|
+
if (container === null) return;
|
|
14995
15207
|
if (status.kind === "connecting" || status.kind === "connected") return;
|
|
14996
|
-
teardown();
|
|
15208
|
+
teardown("detach");
|
|
14997
15209
|
setStatus({ kind: "connecting" });
|
|
14998
15210
|
const term = new import_xterm.Terminal({
|
|
14999
15211
|
convertEol: false,
|
|
@@ -15010,11 +15222,12 @@ window.__ModuleLoader__.load({
|
|
|
15010
15222
|
term.loadAddon(fit);
|
|
15011
15223
|
term.open(container);
|
|
15012
15224
|
fit.fit();
|
|
15013
|
-
const connection =
|
|
15225
|
+
const connection = open(term.cols, term.rows);
|
|
15014
15226
|
termRef.current = term;
|
|
15015
15227
|
fitRef.current = fit;
|
|
15016
15228
|
connRef.current = connection;
|
|
15017
15229
|
let settled = false;
|
|
15230
|
+
let connectedAlias = "";
|
|
15018
15231
|
dataSubRef.current = term.onData((data) => {
|
|
15019
15232
|
connection.send(data);
|
|
15020
15233
|
});
|
|
@@ -15026,12 +15239,14 @@ window.__ModuleLoader__.load({
|
|
|
15026
15239
|
});
|
|
15027
15240
|
setAuthInputs(prompts.map(() => ""));
|
|
15028
15241
|
};
|
|
15029
|
-
connection.onReady = () => {
|
|
15242
|
+
connection.onReady = (id, readyAlias) => {
|
|
15243
|
+
connectedAlias = readyAlias;
|
|
15244
|
+
controller.setTerminalSession(id);
|
|
15030
15245
|
setAuthPrompt(void 0);
|
|
15031
15246
|
setAuthInputs([]);
|
|
15032
15247
|
setStatus({
|
|
15033
15248
|
kind: "connected",
|
|
15034
|
-
alias:
|
|
15249
|
+
alias: readyAlias
|
|
15035
15250
|
});
|
|
15036
15251
|
};
|
|
15037
15252
|
connection.onOutput = (data) => {
|
|
@@ -15046,15 +15261,29 @@ window.__ModuleLoader__.load({
|
|
|
15046
15261
|
dataSubRef.current = null;
|
|
15047
15262
|
term.options.disableStdin = true;
|
|
15048
15263
|
connRef.current = null;
|
|
15264
|
+
controller.clearTerminalSession();
|
|
15049
15265
|
setStatus({
|
|
15050
15266
|
kind: "exited",
|
|
15051
|
-
alias:
|
|
15267
|
+
alias: connectedAlias !== "" ? connectedAlias : alias,
|
|
15052
15268
|
detail: error
|
|
15053
15269
|
});
|
|
15054
15270
|
};
|
|
15055
15271
|
};
|
|
15272
|
+
const connect = () => {
|
|
15273
|
+
const target = alias;
|
|
15274
|
+
if (target === "") return;
|
|
15275
|
+
startSession((cols, rows) => api.openTerminal(target, cols, rows));
|
|
15276
|
+
};
|
|
15277
|
+
const reattachedRef = (0, react.useRef)(void 0);
|
|
15278
|
+
(0, react.useEffect)(() => {
|
|
15279
|
+
if (sessionId === void 0 || connRef.current !== null) return;
|
|
15280
|
+
if (reattachedRef.current === sessionId) return;
|
|
15281
|
+
reattachedRef.current = sessionId;
|
|
15282
|
+
startSession((cols, rows) => api.attachTerminal(sessionId, cols, rows));
|
|
15283
|
+
}, [sessionId]);
|
|
15056
15284
|
const disconnect = () => {
|
|
15057
|
-
teardown();
|
|
15285
|
+
teardown("close");
|
|
15286
|
+
controller.clearTerminalSession();
|
|
15058
15287
|
setStatus({ kind: "idle" });
|
|
15059
15288
|
};
|
|
15060
15289
|
const submitAuth = (e) => {
|
|
@@ -15830,10 +16059,14 @@ window.__ModuleLoader__.load({
|
|
|
15830
16059
|
//#region src/client/panel/SshPanel.tsx
|
|
15831
16060
|
/**
|
|
15832
16061
|
* The SSH operations panel shell: a header with a close control, a five-tab
|
|
15833
|
-
* bar, and the active tab's content.
|
|
15834
|
-
*
|
|
15835
|
-
*
|
|
15836
|
-
*
|
|
16062
|
+
* bar, and the active tab's content.
|
|
16063
|
+
*
|
|
16064
|
+
* The active tab, the pending connect request and the live terminal session id
|
|
16065
|
+
* live in the controller, not in component state: the layout mounts this page
|
|
16066
|
+
* only while the panel is selected, so local state would reset the tab on
|
|
16067
|
+
* every panel switch and lose the id the terminal tab needs to reattach its
|
|
16068
|
+
* host-side session. Inactive tabs unmount, so each tab fetches its own data
|
|
16069
|
+
* on activation.
|
|
15837
16070
|
*/
|
|
15838
16071
|
/** The tab bar definition (labels resolved at render time). */
|
|
15839
16072
|
const TABS = [
|
|
@@ -15860,16 +16093,7 @@ window.__ModuleLoader__.load({
|
|
|
15860
16093
|
];
|
|
15861
16094
|
/** The tabbed SSH panel. */
|
|
15862
16095
|
function SshPanel({ controller, api, terminalFont }) {
|
|
15863
|
-
const panelOpen = (0, react.useSyncExternalStore)((0, react.useCallback)((listener) => controller.subscribe(listener), [controller]), (0, react.useCallback)(() => controller.getSnapshot()
|
|
15864
|
-
const [activeTab, setActiveTab] = (0, react.useState)("hosts");
|
|
15865
|
-
const [connectRequest, setConnectRequest] = (0, react.useState)(null);
|
|
15866
|
-
const handleConnect = (alias) => {
|
|
15867
|
-
setActiveTab("terminal");
|
|
15868
|
-
setConnectRequest((prev) => ({
|
|
15869
|
-
alias,
|
|
15870
|
-
nonce: (prev?.nonce ?? 0) + 1
|
|
15871
|
-
}));
|
|
15872
|
-
};
|
|
16096
|
+
const { panelOpen, activeTab, connectRequest, terminalSessionId } = (0, react.useSyncExternalStore)((0, react.useCallback)((listener) => controller.subscribe(listener), [controller]), (0, react.useCallback)(() => controller.getSnapshot(), [controller]));
|
|
15873
16097
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
15874
16098
|
className: panel_module_css_default.panel,
|
|
15875
16099
|
"data-dsh-plugin": "ssh",
|
|
@@ -15905,7 +16129,7 @@ window.__ModuleLoader__.load({
|
|
|
15905
16129
|
"data-dsh-part": "tab",
|
|
15906
16130
|
className: panel_module_css_default.tab,
|
|
15907
16131
|
onClick: () => {
|
|
15908
|
-
setActiveTab(tab.id);
|
|
16132
|
+
controller.setActiveTab(tab.id);
|
|
15909
16133
|
},
|
|
15910
16134
|
children: tab.label()
|
|
15911
16135
|
}, tab.id))
|
|
@@ -15915,12 +16139,16 @@ window.__ModuleLoader__.load({
|
|
|
15915
16139
|
children: [
|
|
15916
16140
|
activeTab === "hosts" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HostsTab, {
|
|
15917
16141
|
api,
|
|
15918
|
-
onConnect:
|
|
16142
|
+
onConnect: (alias) => {
|
|
16143
|
+
controller.requestConnect(alias);
|
|
16144
|
+
}
|
|
15919
16145
|
}),
|
|
15920
16146
|
activeTab === "terminal" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TerminalTab, {
|
|
15921
16147
|
api,
|
|
16148
|
+
controller,
|
|
15922
16149
|
presetAlias: connectRequest?.alias,
|
|
15923
16150
|
requestId: connectRequest?.nonce,
|
|
16151
|
+
sessionId: terminalSessionId,
|
|
15924
16152
|
terminalFont
|
|
15925
16153
|
}),
|
|
15926
16154
|
activeTab === "transfer" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TransferTab, { api }),
|
|
@@ -15935,261 +16163,101 @@ window.__ModuleLoader__.load({
|
|
|
15935
16163
|
});
|
|
15936
16164
|
}
|
|
15937
16165
|
//#endregion
|
|
15938
|
-
//#region src/client/
|
|
15939
|
-
/**
|
|
15940
|
-
const
|
|
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
|
-
}
|
|
16166
|
+
//#region src/client/native-panel.tsx
|
|
16167
|
+
/** Row order among the shell's global panel rows (Plugins 0, Schedule 10, board 20, skill center 30). */
|
|
16168
|
+
const PANEL_ORDER = 40;
|
|
15958
16169
|
/**
|
|
15959
|
-
*
|
|
15960
|
-
*
|
|
15961
|
-
*
|
|
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
|
|
16026
|
-
//#region src/client/panel-mount-core.ts
|
|
16027
|
-
/**
|
|
16028
|
-
* Center-column panel takeover lifecycle.
|
|
16029
|
-
*
|
|
16030
|
-
* The `conversation` slot is single-occupant (ui-conversation) and external
|
|
16031
|
-
* plugins cannot declare slots, so a family panel takes over the center
|
|
16032
|
-
* column at the DOM level: a container is appended inside the center column
|
|
16033
|
-
* (`[class*="centerCol"]`, the 0.1.0-rc.6+ AppFrame layout; previously
|
|
16034
|
-
* `[data-pane="conversation"]` on older shells — the mount selector keeps
|
|
16035
|
-
* both, ssh #243 / task-board #107) as an extra trailing child React never
|
|
16036
|
-
* manages, and a stylesheet rule hides the conversation content while the
|
|
16037
|
-
* panel is active. Toggling is a data attribute on <html> — no React
|
|
16038
|
-
* involvement, so the conversation subtree underneath stays mounted and
|
|
16039
|
-
* stateful.
|
|
16170
|
+
* The sidebar row glyph the shell asks for at its own size and active state.
|
|
16171
|
+
* The shell owns the button, label, tooltip and rail geometry; this component
|
|
16172
|
+
* draws only the glyph, like every other panel row.
|
|
16040
16173
|
*
|
|
16041
|
-
*
|
|
16042
|
-
*
|
|
16043
|
-
*
|
|
16044
|
-
*
|
|
16045
|
-
*
|
|
16174
|
+
* The glyph carries `data-dsh-panel-entry` because it is the only DOM this
|
|
16175
|
+
* panel's own code owns inside that shell-owned row: the L2 contract (skins)
|
|
16176
|
+
* resolves which row belongs to which plugin through it, since the shell
|
|
16177
|
+
* stamps no per-entry hook of its own (see contracts/semantic-attrs-v1.md).
|
|
16178
|
+
* @param props - the shell's icon share: square edge and selection state.
|
|
16179
|
+
* @returns the decorative terminal glyph.
|
|
16046
16180
|
*/
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
|
|
16051
|
-
|
|
16052
|
-
|
|
16053
|
-
|
|
16181
|
+
function SshPanelIcon({ size }) {
|
|
16182
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
16183
|
+
"data-dsh-panel-entry": "ssh",
|
|
16184
|
+
viewBox: "0 0 16 16",
|
|
16185
|
+
width: size,
|
|
16186
|
+
height: size,
|
|
16187
|
+
fill: "none",
|
|
16188
|
+
stroke: "currentColor",
|
|
16189
|
+
strokeWidth: "1.5",
|
|
16190
|
+
strokeLinecap: "round",
|
|
16191
|
+
strokeLinejoin: "round",
|
|
16192
|
+
"aria-hidden": "true",
|
|
16193
|
+
children: [
|
|
16194
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
16195
|
+
x: "1.75",
|
|
16196
|
+
y: "2.25",
|
|
16197
|
+
width: "12.5",
|
|
16198
|
+
height: "11.5",
|
|
16199
|
+
rx: "1.75"
|
|
16200
|
+
}),
|
|
16201
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4.25 5.25l2.75 2.75-2.75 2.75" }),
|
|
16202
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8.5 10.75h3.25" })
|
|
16203
|
+
]
|
|
16204
|
+
});
|
|
16054
16205
|
}
|
|
16055
16206
|
/**
|
|
16056
|
-
*
|
|
16057
|
-
*
|
|
16058
|
-
*
|
|
16207
|
+
* The main-slot page. The layout mounts it only while this panel is selected;
|
|
16208
|
+
* the wrapper carries the pinned `data-dsh-ssh-view` semantic anchor (L2
|
|
16209
|
+
* contract, skins) the takeover container used to own.
|
|
16210
|
+
* @param props - the framework main-slot share plus this entry's injected face.
|
|
16211
|
+
* @returns the SSH panel page.
|
|
16059
16212
|
*/
|
|
16060
|
-
function
|
|
16061
|
-
|
|
16062
|
-
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
|
|
16066
|
-
|
|
16067
|
-
|
|
16068
|
-
|
|
16069
|
-
|
|
16070
|
-
if (container !== void 0 && !container.isConnected) {
|
|
16071
|
-
root?.unmount();
|
|
16072
|
-
root = void 0;
|
|
16073
|
-
container.remove();
|
|
16074
|
-
container = void 0;
|
|
16075
|
-
}
|
|
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;
|
|
16086
|
-
root = (0, react_dom_client.createRoot)(container);
|
|
16087
|
-
options.render(root);
|
|
16088
|
-
};
|
|
16089
|
-
const unsubscribeBody = subscribeBodyInvalidations(() => {
|
|
16090
|
-
ensure();
|
|
16213
|
+
function SshPanelPage({ controller, api, terminalFont }) {
|
|
16214
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
16215
|
+
className: panel_module_css_default.view,
|
|
16216
|
+
"data-dsh-ssh-view": "",
|
|
16217
|
+
"data-dsh-plugin": "ssh",
|
|
16218
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SshPanel, {
|
|
16219
|
+
controller,
|
|
16220
|
+
api,
|
|
16221
|
+
terminalFont
|
|
16222
|
+
})
|
|
16091
16223
|
});
|
|
16092
|
-
const applyActive = () => {
|
|
16093
|
-
if (options.isOpen()) {
|
|
16094
|
-
ensure();
|
|
16095
|
-
document.documentElement.removeAttribute(options.siblingActiveAttribute);
|
|
16096
|
-
document.documentElement.setAttribute(options.activeAttribute, "");
|
|
16097
|
-
document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: options.panelName }));
|
|
16098
|
-
} else document.documentElement.removeAttribute(options.activeAttribute);
|
|
16099
|
-
};
|
|
16100
|
-
const onOtherActivate = (event) => {
|
|
16101
|
-
if (event.detail === options.siblingPanelName && options.isOpen()) options.close();
|
|
16102
|
-
};
|
|
16103
|
-
const onClickSidebarRow = (event) => {
|
|
16104
|
-
if (!options.isOpen()) return;
|
|
16105
|
-
const target = event.target;
|
|
16106
|
-
if (target === null) return;
|
|
16107
|
-
if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) options.close();
|
|
16108
|
-
};
|
|
16109
|
-
document.addEventListener("click", onClickSidebarRow, true);
|
|
16110
|
-
document.addEventListener(ACTIVATE_EVENT, onOtherActivate);
|
|
16111
|
-
const unsubscribe = options.subscribe(applyActive);
|
|
16112
|
-
applyActive();
|
|
16113
|
-
ensure();
|
|
16114
|
-
return () => {
|
|
16115
|
-
document.removeEventListener("click", onClickSidebarRow, true);
|
|
16116
|
-
document.removeEventListener(ACTIVATE_EVENT, onOtherActivate);
|
|
16117
|
-
unsubscribeBody();
|
|
16118
|
-
unsubscribe();
|
|
16119
|
-
unsubscribeLocale?.();
|
|
16120
|
-
document.documentElement.removeAttribute(options.activeAttribute);
|
|
16121
|
-
root?.unmount();
|
|
16122
|
-
root = void 0;
|
|
16123
|
-
container?.remove();
|
|
16124
|
-
container = void 0;
|
|
16125
|
-
};
|
|
16126
16224
|
}
|
|
16127
|
-
//#endregion
|
|
16128
|
-
//#region src/client/mount.tsx
|
|
16129
16225
|
/**
|
|
16130
|
-
*
|
|
16131
|
-
*
|
|
16132
|
-
*
|
|
16133
|
-
*
|
|
16226
|
+
* Register the SSH panel's sidebar row and center-column page.
|
|
16227
|
+
*
|
|
16228
|
+
* Both seats are declared by shell plugins this package does not depend on at
|
|
16229
|
+
* runtime, so each registration is wrapped in `ctx.slots.inject`: the callback
|
|
16230
|
+
* runs only after the owning entry declares the seat, and a shell that never
|
|
16231
|
+
* declares it leaves the panel simply absent instead of failing boot.
|
|
16232
|
+
* @param ctx - client root context (services: slots).
|
|
16233
|
+
* @param controller - the controller the page and the row drive.
|
|
16234
|
+
* @param api - the SSH API client the page operates through.
|
|
16134
16235
|
* @param terminalFont - live terminal-font setting source (issue #577).
|
|
16135
|
-
* @
|
|
16136
|
-
* on a Language switch.
|
|
16137
|
-
* @returns disposer unmounting the tree and restoring the column.
|
|
16236
|
+
* @returns disposer releasing both registrations.
|
|
16138
16237
|
*/
|
|
16139
|
-
function
|
|
16140
|
-
|
|
16141
|
-
|
|
16238
|
+
function registerSshPanel(ctx, controller, api, terminalFont) {
|
|
16239
|
+
const slots = ctx.slots;
|
|
16240
|
+
const disposers = [];
|
|
16241
|
+
disposers.push(slots.inject("sidebar.panellist", () => slots.register({
|
|
16242
|
+
name: "sidebar.panellist",
|
|
16243
|
+
id: "ssh",
|
|
16244
|
+
order: PANEL_ORDER,
|
|
16245
|
+
label: () => tt("entry.label")
|
|
16246
|
+
}, SshPanelIcon)));
|
|
16247
|
+
disposers.push(slots.inject("main", () => slots.register({
|
|
16248
|
+
name: "main",
|
|
16249
|
+
key: "ssh",
|
|
16250
|
+
inject: () => ({
|
|
16142
16251
|
controller,
|
|
16143
16252
|
api,
|
|
16144
16253
|
terminalFont
|
|
16145
|
-
})
|
|
16146
|
-
|
|
16147
|
-
|
|
16148
|
-
|
|
16149
|
-
|
|
16150
|
-
siblingActiveAttribute: "data-dsh-taskboard-active",
|
|
16151
|
-
panelName: "ssh",
|
|
16152
|
-
siblingPanelName: "taskboard",
|
|
16153
|
-
isOpen: () => controller.getSnapshot().panelOpen,
|
|
16154
|
-
close: () => controller.close(),
|
|
16155
|
-
subscribe: (listener) => controller.subscribe(listener),
|
|
16156
|
-
locale
|
|
16157
|
-
});
|
|
16254
|
+
})
|
|
16255
|
+
}, SshPanelPage)));
|
|
16256
|
+
return () => {
|
|
16257
|
+
for (const dispose of disposers.splice(0)) dispose();
|
|
16258
|
+
};
|
|
16158
16259
|
}
|
|
16159
16260
|
//#endregion
|
|
16160
|
-
//#region src/client/panel/controller.ts
|
|
16161
|
-
/** The panel state owner the sidebar entry toggles and the view renders from. */
|
|
16162
|
-
var PanelController = class {
|
|
16163
|
-
panelOpen = false;
|
|
16164
|
-
listeners = /* @__PURE__ */ new Set();
|
|
16165
|
-
getSnapshot() {
|
|
16166
|
-
return { panelOpen: this.panelOpen };
|
|
16167
|
-
}
|
|
16168
|
-
subscribe(fn) {
|
|
16169
|
-
this.listeners.add(fn);
|
|
16170
|
-
return () => {
|
|
16171
|
-
this.listeners.delete(fn);
|
|
16172
|
-
};
|
|
16173
|
-
}
|
|
16174
|
-
open() {
|
|
16175
|
-
if (this.panelOpen) return;
|
|
16176
|
-
this.panelOpen = true;
|
|
16177
|
-
this.notify();
|
|
16178
|
-
}
|
|
16179
|
-
close() {
|
|
16180
|
-
if (!this.panelOpen) return;
|
|
16181
|
-
this.panelOpen = false;
|
|
16182
|
-
this.notify();
|
|
16183
|
-
}
|
|
16184
|
-
toggle() {
|
|
16185
|
-
if (this.panelOpen) this.close();
|
|
16186
|
-
else this.open();
|
|
16187
|
-
}
|
|
16188
|
-
notify() {
|
|
16189
|
-
for (const fn of [...this.listeners]) fn();
|
|
16190
|
-
}
|
|
16191
|
-
};
|
|
16192
|
-
//#endregion
|
|
16193
16261
|
//#region src/client/settings-binding.ts
|
|
16194
16262
|
/**
|
|
16195
16263
|
* The snapshot a reader reports before the Host has answered with this
|
|
@@ -16292,175 +16360,6 @@ window.__ModuleLoader__.load({
|
|
|
16292
16360
|
return new SharedFormsReader(ctx.configForms, field);
|
|
16293
16361
|
}
|
|
16294
16362
|
//#endregion
|
|
16295
|
-
//#region src/client/sidebar-entry-core.ts
|
|
16296
|
-
/**
|
|
16297
|
-
* Shared sidebar entry injection core.
|
|
16298
|
-
*
|
|
16299
|
-
* dsh's sidebar shell exposes no slot an external plugin can register into,
|
|
16300
|
-
* so the entry row is injected between the shell's New Session button and the
|
|
16301
|
-
* workspace browser. The injection self-heals: a MutationObserver watches the
|
|
16302
|
-
* sidebar root and re-inserts the row whenever a React re-render displaces it
|
|
16303
|
-
* (re-insertion happens in the same frame, before paint, so no flicker).
|
|
16304
|
-
*
|
|
16305
|
-
* The row is plain DOM (no React tree) so it can never disturb the shell's
|
|
16306
|
-
* reconciliation; the view it toggles is a separate root owned by the caller.
|
|
16307
|
-
*
|
|
16308
|
-
* Packages receive this file as a generated copy via scripts/sync-shared.mjs;
|
|
16309
|
-
* edit the shared source and re-run the sync instead of editing a copy.
|
|
16310
|
-
*/
|
|
16311
|
-
/** Find the sidebar shell root element, or undefined while not yet mounted. */
|
|
16312
|
-
function sidebarRoot() {
|
|
16313
|
-
const column = document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]");
|
|
16314
|
-
if (column === null) return void 0;
|
|
16315
|
-
return column.querySelector("[class*=\"logoRow\"]")?.parentElement ?? column.firstElementChild;
|
|
16316
|
-
}
|
|
16317
|
-
/** The New Session button: nested in the logo row on current shells, a direct child on legacy shells. */
|
|
16318
|
-
function newSessionButton(root) {
|
|
16319
|
-
const nested = root.querySelector("button[class*=\"newSession\"]");
|
|
16320
|
-
if (nested !== null) return nested;
|
|
16321
|
-
for (const child of root.children) if (child.tagName === "BUTTON") return child;
|
|
16322
|
-
}
|
|
16323
|
-
/** Build the entry row (detached; inserted once the shell is up). */
|
|
16324
|
-
function createEntry(options) {
|
|
16325
|
-
const entry = document.createElement("button");
|
|
16326
|
-
entry.type = "button";
|
|
16327
|
-
entry.setAttribute(options.rowAttribute, "");
|
|
16328
|
-
if (options.plugin !== void 0) {
|
|
16329
|
-
entry.setAttribute("data-dsh-plugin", options.plugin);
|
|
16330
|
-
entry.setAttribute("data-dsh-part", "sidebar-entry");
|
|
16331
|
-
}
|
|
16332
|
-
entry.className = options.css["entry"] ?? "";
|
|
16333
|
-
const labelSpan = document.createElement("span");
|
|
16334
|
-
labelSpan.className = options.css["entryLabel"] ?? "";
|
|
16335
|
-
const iconSpan = document.createElement("span");
|
|
16336
|
-
iconSpan.className = options.css["entryIcon"] ?? "";
|
|
16337
|
-
iconSpan.innerHTML = options.icon;
|
|
16338
|
-
entry.append(iconSpan, labelSpan);
|
|
16339
|
-
const applyLabel = () => {
|
|
16340
|
-
entry.setAttribute("aria-label", options.label());
|
|
16341
|
-
if (options.tooltip !== void 0) entry.setAttribute("title", options.tooltip());
|
|
16342
|
-
labelSpan.textContent = options.label();
|
|
16343
|
-
};
|
|
16344
|
-
applyLabel();
|
|
16345
|
-
entry.addEventListener("click", options.onToggle);
|
|
16346
|
-
return {
|
|
16347
|
-
entry,
|
|
16348
|
-
applyLabel
|
|
16349
|
-
};
|
|
16350
|
-
}
|
|
16351
|
-
/** Re-insert the entry after the New Session row (before the browser region). */
|
|
16352
|
-
function placeEntry(root, entry, options) {
|
|
16353
|
-
const button = newSessionButton(root);
|
|
16354
|
-
if (button === void 0) return false;
|
|
16355
|
-
if (entry.parentElement !== root) {
|
|
16356
|
-
const row = button.closest("[class*=\"logoRow\"]");
|
|
16357
|
-
const base = row !== null && row.parentElement === root ? row : button;
|
|
16358
|
-
const family = Array.from(root.children).filter((el) => el instanceof HTMLElement && el.matches(options.familySelectors.join(", ")));
|
|
16359
|
-
const anchor = options.position === "before" ? family.length > 0 ? family[0] : base.nextElementSibling : family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling;
|
|
16360
|
-
root.insertBefore(entry, anchor);
|
|
16361
|
-
}
|
|
16362
|
-
return true;
|
|
16363
|
-
}
|
|
16364
|
-
/**
|
|
16365
|
-
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
|
16366
|
-
* on later React re-renders.
|
|
16367
|
-
* @param options - the row's attribute/icon/copy/action/ordering configuration.
|
|
16368
|
-
* @returns disposer removing the entry and its observers.
|
|
16369
|
-
*/
|
|
16370
|
-
function mountSidebarEntry$1(options) {
|
|
16371
|
-
if (typeof document !== "undefined" && document.querySelector(options.rowSelector) !== null) return () => {};
|
|
16372
|
-
const { entry, applyLabel } = createEntry(options);
|
|
16373
|
-
let root;
|
|
16374
|
-
let placed = false;
|
|
16375
|
-
let unsubscribeRefresh;
|
|
16376
|
-
if (options.refresh !== void 0) try {
|
|
16377
|
-
unsubscribeRefresh = options.refresh.subscribe(applyLabel);
|
|
16378
|
-
} catch {}
|
|
16379
|
-
const tryPlace = () => {
|
|
16380
|
-
if (root !== void 0 && !root.isConnected) {
|
|
16381
|
-
rootObserver.disconnect();
|
|
16382
|
-
root = void 0;
|
|
16383
|
-
placed = false;
|
|
16384
|
-
}
|
|
16385
|
-
if (placed) {
|
|
16386
|
-
if (document.body.contains(entry)) return;
|
|
16387
|
-
rootObserver.disconnect();
|
|
16388
|
-
root = void 0;
|
|
16389
|
-
placed = false;
|
|
16390
|
-
}
|
|
16391
|
-
root ??= sidebarRoot();
|
|
16392
|
-
if (root === void 0) return;
|
|
16393
|
-
placed = placeEntry(root, entry, options);
|
|
16394
|
-
if (placed) rootObserver.observe(root, {
|
|
16395
|
-
childList: true,
|
|
16396
|
-
subtree: true
|
|
16397
|
-
});
|
|
16398
|
-
};
|
|
16399
|
-
const unsubscribeBody = subscribeBodyInvalidations(() => {
|
|
16400
|
-
tryPlace();
|
|
16401
|
-
});
|
|
16402
|
-
const rootObserver = new MutationObserver(() => {
|
|
16403
|
-
if (root === void 0 || !root.isConnected) {
|
|
16404
|
-
placed = false;
|
|
16405
|
-
tryPlace();
|
|
16406
|
-
return;
|
|
16407
|
-
}
|
|
16408
|
-
if (!root.contains(entry)) placed = placeEntry(root, entry, options);
|
|
16409
|
-
});
|
|
16410
|
-
const unsubscribeActive = options.active === void 0 ? void 0 : (() => {
|
|
16411
|
-
const syncActive = () => {
|
|
16412
|
-
if (options.active.isOpen()) entry.dataset.active = "true";
|
|
16413
|
-
else delete entry.dataset.active;
|
|
16414
|
-
};
|
|
16415
|
-
const unsubscribe = options.active.subscribe(syncActive);
|
|
16416
|
-
syncActive();
|
|
16417
|
-
return unsubscribe;
|
|
16418
|
-
})();
|
|
16419
|
-
tryPlace();
|
|
16420
|
-
return () => {
|
|
16421
|
-
unsubscribeBody();
|
|
16422
|
-
rootObserver.disconnect();
|
|
16423
|
-
unsubscribeRefresh?.();
|
|
16424
|
-
unsubscribeActive?.();
|
|
16425
|
-
entry.remove();
|
|
16426
|
-
};
|
|
16427
|
-
}
|
|
16428
|
-
//#endregion
|
|
16429
|
-
//#region src/client/sidebar-entry.ts
|
|
16430
|
-
/** Stable data attribute identifying the injected entry row. */
|
|
16431
|
-
const ENTRY_SELECTOR = "[data-dsh-ssh-entry]";
|
|
16432
|
-
/** Inline terminal glyph sized to the shell's panel-row navigation icons. */
|
|
16433
|
-
const ICON = "<svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"1.75\" y=\"2.25\" width=\"12.5\" height=\"11.5\" rx=\"1.75\"/><path d=\"M4.25 5.25l2.75 2.75-2.75 2.75\"/><path d=\"M8.5 10.75h3.25\"/></svg>";
|
|
16434
|
-
/**
|
|
16435
|
-
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
|
16436
|
-
* on later React re-renders.
|
|
16437
|
-
* @param controller - the panel controller the entry toggles.
|
|
16438
|
-
* @param locale - locale-change source; when given, re-applies the label on
|
|
16439
|
-
* a Language switch (the plain-DOM row otherwise keeps the mount-time copy).
|
|
16440
|
-
* @returns disposer removing the entry and its observers.
|
|
16441
|
-
*/
|
|
16442
|
-
function mountSidebarEntry(controller, locale) {
|
|
16443
|
-
return mountSidebarEntry$1({
|
|
16444
|
-
rowAttribute: "data-dsh-ssh-entry",
|
|
16445
|
-
rowSelector: ENTRY_SELECTOR,
|
|
16446
|
-
plugin: "ssh",
|
|
16447
|
-
icon: ICON,
|
|
16448
|
-
css: panel_module_css_default,
|
|
16449
|
-
label: () => tt("entry.label"),
|
|
16450
|
-
tooltip: () => tt("entry.tooltip"),
|
|
16451
|
-
refresh: locale === void 0 ? void 0 : { subscribe: (listener) => locale.subscribe(listener) },
|
|
16452
|
-
onToggle: () => {
|
|
16453
|
-
controller.toggle();
|
|
16454
|
-
},
|
|
16455
|
-
position: "after",
|
|
16456
|
-
familySelectors: ["[data-dsh-taskboard-entry]", "[data-dsh-ssh-entry]"],
|
|
16457
|
-
active: {
|
|
16458
|
-
subscribe: (listener) => controller.subscribe(listener),
|
|
16459
|
-
isOpen: () => controller.getSnapshot().panelOpen
|
|
16460
|
-
}
|
|
16461
|
-
});
|
|
16462
|
-
}
|
|
16463
|
-
//#endregion
|
|
16464
16363
|
//#region src/client/telemetry.ts
|
|
16465
16364
|
const VISITOR_KEY = "dsh-web-ui-telemetry-visitor";
|
|
16466
16365
|
const DAY_KEY_PREFIX = "dsh-web-ui-telemetry-day:";
|
|
@@ -16468,7 +16367,7 @@ window.__ModuleLoader__.load({
|
|
|
16468
16367
|
/** The building package's version, when the bundle carries it. */
|
|
16469
16368
|
function bakedVersion() {
|
|
16470
16369
|
try {
|
|
16471
|
-
return "0.4.
|
|
16370
|
+
return "0.4.4";
|
|
16472
16371
|
} catch {
|
|
16473
16372
|
return;
|
|
16474
16373
|
}
|
|
@@ -16545,7 +16444,8 @@ window.__ModuleLoader__.load({
|
|
|
16545
16444
|
const inject = [
|
|
16546
16445
|
"slots",
|
|
16547
16446
|
"locale",
|
|
16548
|
-
"configForms"
|
|
16447
|
+
"configForms",
|
|
16448
|
+
"layout"
|
|
16549
16449
|
];
|
|
16550
16450
|
/**
|
|
16551
16451
|
* Mount the SSH panel.
|
|
@@ -16566,7 +16466,9 @@ window.__ModuleLoader__.load({
|
|
|
16566
16466
|
try {
|
|
16567
16467
|
setRuntimeTranslate(ctx.locale.bind(NS));
|
|
16568
16468
|
} catch {}
|
|
16569
|
-
const controller = new PanelController()
|
|
16469
|
+
const controller = new PanelController({ panel: { select: (panelId) => {
|
|
16470
|
+
(ctx.get?.("layout"))?.selectPanel?.(panelId);
|
|
16471
|
+
} } });
|
|
16570
16472
|
const api = new SshApi();
|
|
16571
16473
|
const settings = bindSettingsReader(ctx, SETTINGS_NS, TERMINAL_FONT_FIELD);
|
|
16572
16474
|
const terminalFont = {
|
|
@@ -16581,10 +16483,18 @@ window.__ModuleLoader__.load({
|
|
|
16581
16483
|
}, "dsh-ssh: settings binding");
|
|
16582
16484
|
const disposers = [];
|
|
16583
16485
|
try {
|
|
16584
|
-
disposers.push(
|
|
16585
|
-
|
|
16486
|
+
disposers.push(registerSshPanel(ctx, controller, api, terminalFont));
|
|
16487
|
+
const layoutFace = ctx.get?.("layout");
|
|
16488
|
+
if (layoutFace?.panelInfo !== void 0) {
|
|
16489
|
+
const sync = () => {
|
|
16490
|
+
const active = layoutFace.panelInfo.getSnapshot().activePanelId;
|
|
16491
|
+
controller.syncPanelSelection(active === "ssh" ? "ssh" : null);
|
|
16492
|
+
};
|
|
16493
|
+
sync();
|
|
16494
|
+
disposers.push(layoutFace.panelInfo.subscribe(sync));
|
|
16495
|
+
}
|
|
16586
16496
|
} catch (error) {
|
|
16587
|
-
console.warn("[dsh-ssh]
|
|
16497
|
+
console.warn("[dsh-ssh] panel registration failed:", error);
|
|
16588
16498
|
}
|
|
16589
16499
|
ctx.effect(() => () => {
|
|
16590
16500
|
for (const dispose of disposers.splice(0)) dispose();
|