@linxin666/dsh-remote-web-ui 0.3.6 → 0.3.9
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/README.i18n.yaml +2 -2
- package/README.md +77 -468
- package/README.zh.md +83 -113
- package/lib/client.js +955 -203
- package/lib/client.js.map +1 -1
- package/lib/index.js +1136 -1466
- package/lib/types/client/FooterRemoteEntry.d.ts +6 -7
- package/lib/types/client/FooterRemoteEntry.d.ts.map +1 -1
- package/lib/types/client/FooterRemoteEntry.js +1 -1
- package/lib/types/client/RemoteEntry.d.ts +7 -4
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +7 -10
- package/lib/types/client/RemotePanel.d.ts +2 -2
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +2 -2
- package/lib/types/client/RemoteSettingsCard.d.ts +9 -5
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +56 -3
- package/lib/types/client/deep-link.d.ts +10 -13
- package/lib/types/client/deep-link.d.ts.map +1 -1
- package/lib/types/client/deep-link.js +14 -74
- package/lib/types/client/index.d.ts +2 -6
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +73 -39
- package/lib/types/client/locales.d.ts +26 -12
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +36 -22
- package/lib/types/client/mobile-adapt.d.ts +40 -0
- package/lib/types/client/mobile-adapt.d.ts.map +1 -0
- package/lib/types/client/mobile-adapt.js +967 -0
- package/lib/types/client/pair-api.d.ts +24 -6
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +9 -14
- package/lib/types/client/remote-channel.d.ts +3 -0
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +35 -3
- package/lib/types/client/settings-form.d.ts +35 -43
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +60 -53
- package/lib/types/firewall.d.ts +75 -0
- package/lib/types/firewall.d.ts.map +1 -0
- package/lib/types/firewall.js +184 -0
- package/lib/types/gate.d.ts +11 -5
- package/lib/types/gate.d.ts.map +1 -1
- package/lib/types/gate.js +11 -5
- package/lib/types/index.d.ts +39 -10
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +206 -58
- package/lib/types/inner-auth.d.ts +32 -0
- package/lib/types/inner-auth.d.ts.map +1 -0
- package/lib/types/inner-auth.js +72 -0
- package/lib/types/lan-bind-plan.d.ts +55 -0
- package/lib/types/lan-bind-plan.d.ts.map +1 -0
- package/lib/types/lan-bind-plan.js +60 -0
- package/lib/types/lan-bind.d.ts +69 -0
- package/lib/types/lan-bind.d.ts.map +1 -0
- package/lib/types/lan-bind.js +128 -0
- package/lib/types/loopback-proxy.d.ts +9 -2
- package/lib/types/loopback-proxy.d.ts.map +1 -1
- package/lib/types/loopback-proxy.js +24 -2
- package/lib/types/pairing.d.ts +15 -3
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +20 -6
- package/lib/types/remote-api.d.ts +30 -13
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +55 -42
- package/lib/types/remote-channel-boot.d.ts +21 -2
- package/lib/types/remote-channel-boot.d.ts.map +1 -1
- package/lib/types/remote-channel-boot.js +65 -3
- package/lib/types/remote-channel-rules.d.ts +8 -0
- package/lib/types/remote-channel-rules.d.ts.map +1 -1
- package/lib/types/remote-channel-rules.js +9 -2
- package/lib/types/remote-methods.d.ts +45 -11
- package/lib/types/remote-methods.d.ts.map +1 -1
- package/lib/types/remote-methods.js +56 -27
- package/lib/types/remote-presence-pet.d.ts +51 -0
- package/lib/types/remote-presence-pet.d.ts.map +1 -0
- package/lib/types/remote-presence-pet.js +101 -0
- package/lib/types/routes.d.ts +76 -4
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +336 -19
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +7 -1
- package/package.json +20 -18
- package/src/client/FooterRemoteEntry.tsx +9 -10
- package/src/client/RemoteEntry.tsx +13 -14
- package/src/client/RemotePanel.tsx +5 -16
- package/src/client/RemoteSettingsCard.tsx +72 -13
- package/src/client/deep-link.ts +14 -75
- package/src/client/index.ts +77 -39
- package/src/client/locales.ts +36 -22
- package/src/client/mobile-adapt.ts +913 -0
- package/src/client/pair-api.ts +26 -15
- package/src/client/remote-channel.ts +31 -3
- package/src/client/settings-form.ts +78 -92
- package/src/dsh-home.ts +1 -1
- package/src/firewall.ts +230 -0
- package/src/gate.ts +11 -5
- package/src/index.ts +235 -65
- package/src/inner-auth.ts +85 -0
- package/src/lan-bind-plan.ts +81 -0
- package/src/lan-bind.ts +145 -0
- package/src/loopback-proxy.ts +34 -0
- package/src/pairing.persist.test.ts +1 -1
- package/src/pairing.ts +20 -6
- package/src/remote-api.ts +67 -45
- package/src/remote-channel-boot.ts +70 -3
- package/src/remote-channel-rules.ts +17 -2
- package/src/remote-methods.ts +58 -27
- package/src/remote-presence-pet.ts +129 -0
- package/src/routes.ts +358 -19
- package/src/tunnel.ts +7 -1
- package/assets/apple-touch-icon.png +0 -0
- package/assets/mobile-icon-192.png +0 -0
- package/assets/mobile-icon-512.png +0 -0
- package/assets/mobile-service-worker.js +0 -63
- package/lib/mobile.js +0 -17658
- package/lib/mobile.js.map +0 -1
- package/lib/types/mobile/PairRequiredView.d.ts +0 -7
- package/lib/types/mobile/PairRequiredView.d.ts.map +0 -1
- package/lib/types/mobile/PairRequiredView.js +0 -28
- package/lib/types/mobile/PairRequiredView.test.d.ts +0 -2
- package/lib/types/mobile/PairRequiredView.test.d.ts.map +0 -1
- package/lib/types/mobile/PairRequiredView.test.js +0 -24
- package/lib/types/mobile/api.d.ts +0 -145
- package/lib/types/mobile/api.d.ts.map +0 -1
- package/lib/types/mobile/api.js +0 -103
- package/lib/types/mobile/index.d.ts +0 -10
- package/lib/types/mobile/index.d.ts.map +0 -1
- package/lib/types/mobile/index.js +0 -38
- package/lib/types/mobile/markdown.d.ts +0 -34
- package/lib/types/mobile/markdown.d.ts.map +0 -1
- package/lib/types/mobile/markdown.js +0 -285
- package/lib/types/mobile/markdown.test.d.ts +0 -2
- package/lib/types/mobile/markdown.test.d.ts.map +0 -1
- package/lib/types/mobile/markdown.test.js +0 -79
- package/lib/types/mobile/messages.d.ts +0 -149
- package/lib/types/mobile/messages.d.ts.map +0 -1
- package/lib/types/mobile/messages.js +0 -561
- package/lib/types/mobile/messages.test.d.ts +0 -2
- package/lib/types/mobile/messages.test.d.ts.map +0 -1
- package/lib/types/mobile/messages.test.js +0 -356
- package/lib/types/mobile/mobile-styles.d.ts +0 -6
- package/lib/types/mobile/mobile-styles.d.ts.map +0 -1
- package/lib/types/mobile/mobile-styles.js +0 -1438
- package/lib/types/mobile/mobile-theme.d.ts +0 -22
- package/lib/types/mobile/mobile-theme.d.ts.map +0 -1
- package/lib/types/mobile/mobile-theme.js +0 -70
- package/lib/types/mobile/mobile-theme.test.d.ts +0 -2
- package/lib/types/mobile/mobile-theme.test.d.ts.map +0 -1
- package/lib/types/mobile/mobile-theme.test.js +0 -71
- package/lib/types/mobile/mux.d.ts +0 -128
- package/lib/types/mobile/mux.d.ts.map +0 -1
- package/lib/types/mobile/mux.js +0 -305
- package/lib/types/mobile/mux.test.d.ts +0 -2
- package/lib/types/mobile/mux.test.d.ts.map +0 -1
- package/lib/types/mobile/mux.test.js +0 -267
- package/lib/types/mobile/pairing.d.ts +0 -31
- package/lib/types/mobile/pairing.d.ts.map +0 -1
- package/lib/types/mobile/pairing.js +0 -62
- package/lib/types/mobile/rpc.d.ts +0 -38
- package/lib/types/mobile/rpc.d.ts.map +0 -1
- package/lib/types/mobile/rpc.js +0 -83
- package/lib/types/mobile/theme-toggle.d.ts +0 -12
- package/lib/types/mobile/theme-toggle.d.ts.map +0 -1
- package/lib/types/mobile/theme-toggle.js +0 -26
- package/lib/types/mobile/views/App.d.ts +0 -57
- package/lib/types/mobile/views/App.d.ts.map +0 -1
- package/lib/types/mobile/views/App.js +0 -150
- package/lib/types/mobile/views/App.test.d.ts +0 -2
- package/lib/types/mobile/views/App.test.d.ts.map +0 -1
- package/lib/types/mobile/views/App.test.js +0 -52
- package/lib/types/mobile/views/ChatView.d.ts +0 -63
- package/lib/types/mobile/views/ChatView.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.js +0 -821
- package/lib/types/mobile/views/ChatView.stream.test.d.ts +0 -2
- package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.stream.test.js +0 -236
- package/lib/types/mobile/views/ChatView.test.d.ts +0 -2
- package/lib/types/mobile/views/ChatView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.test.js +0 -718
- package/lib/types/mobile/views/SessionListView.d.ts +0 -27
- package/lib/types/mobile/views/SessionListView.d.ts.map +0 -1
- package/lib/types/mobile/views/SessionListView.js +0 -139
- package/lib/types/mobile/views/SessionListView.test.d.ts +0 -2
- package/lib/types/mobile/views/SessionListView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/SessionListView.test.js +0 -167
- package/lib/types/mobile/views/WorkspaceView.d.ts +0 -21
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.js +0 -93
- package/lib/types/mobile/views/WorkspaceView.test.d.ts +0 -2
- package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.test.js +0 -183
- package/lib/types/mobile-api.d.ts +0 -65
- package/lib/types/mobile-api.d.ts.map +0 -1
- package/lib/types/mobile-api.js +0 -404
- package/lib/types/mobile-pending.d.ts +0 -38
- package/lib/types/mobile-pending.d.ts.map +0 -1
- package/lib/types/mobile-pending.js +0 -59
- package/lib/types/mobile-pwa.d.ts +0 -6
- package/lib/types/mobile-pwa.d.ts.map +0 -1
- package/lib/types/mobile-pwa.js +0 -17
- package/lib/types/mobile-routes.d.ts +0 -13
- package/lib/types/mobile-routes.d.ts.map +0 -1
- package/lib/types/mobile-routes.js +0 -217
- package/lib/types/paired-model-catalog.d.ts +0 -17
- package/lib/types/paired-model-catalog.d.ts.map +0 -1
- package/lib/types/paired-model-catalog.js +0 -311
- package/src/mobile/PairRequiredView.test.tsx +0 -28
- package/src/mobile/PairRequiredView.tsx +0 -58
- package/src/mobile/api.ts +0 -229
- package/src/mobile/index.tsx +0 -41
- package/src/mobile/markdown.test.ts +0 -91
- package/src/mobile/markdown.ts +0 -291
- package/src/mobile/messages.test.ts +0 -402
- package/src/mobile/messages.ts +0 -685
- package/src/mobile/mobile-styles.ts +0 -1439
- package/src/mobile/mobile-theme.test.ts +0 -84
- package/src/mobile/mobile-theme.ts +0 -76
- package/src/mobile/mux.test.ts +0 -314
- package/src/mobile/mux.ts +0 -336
- package/src/mobile/pairing.ts +0 -77
- package/src/mobile/rpc.ts +0 -90
- package/src/mobile/theme-toggle.tsx +0 -47
- package/src/mobile/views/App.test.tsx +0 -62
- package/src/mobile/views/App.tsx +0 -213
- package/src/mobile/views/ChatView.stream.test.tsx +0 -268
- package/src/mobile/views/ChatView.test.tsx +0 -816
- package/src/mobile/views/ChatView.tsx +0 -1342
- package/src/mobile/views/SessionListView.test.tsx +0 -207
- package/src/mobile/views/SessionListView.tsx +0 -244
- package/src/mobile/views/WorkspaceView.test.tsx +0 -227
- package/src/mobile/views/WorkspaceView.tsx +0 -245
- package/src/mobile-api.ts +0 -418
- package/src/mobile-pending.ts +0 -88
- package/src/mobile-pwa.ts +0 -19
- package/src/mobile-routes.ts +0 -231
- package/src/paired-model-catalog.ts +0 -272
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sidebar footer-seat wrapper for the remote-control entry.
|
|
3
3
|
*
|
|
4
|
-
* Current dsh web shells declare `sidebar.footer.action` (the seat beside
|
|
5
|
-
* settings trigger) instead of the legacy `sidebar.remote` seat this
|
|
6
|
-
* was written against. The footer seat supplies `{ wide }
|
|
7
|
-
*
|
|
8
|
-
* workspace-agnostic selector: pairing without a deep-linked workspace is
|
|
9
|
-
* fully supported by the host `/api/pair` routes.
|
|
4
|
+
* Current dsh web shells declare `sidebar.footer.action` (the seat beside
|
|
5
|
+
* the settings trigger) instead of the legacy `sidebar.remote` seat this
|
|
6
|
+
* plugin was written against. The footer seat supplies `{ wide }`; the
|
|
7
|
+
* pairing link is origin-agnostic, so no workspace source is involved.
|
|
10
8
|
*/
|
|
11
9
|
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
|
|
12
|
-
/** Entry props: the footer seat's column state
|
|
10
|
+
/** Entry props: the footer seat's column state and the standard locale seat. */
|
|
13
11
|
export type FooterRemoteEntryProps = PropsLocale<'remote'> & {
|
|
12
|
+
/** Whether the sidebar renders wide content (false = 56px rail). */
|
|
14
13
|
wide: boolean;
|
|
15
14
|
};
|
|
16
15
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FooterRemoteEntry.d.ts","sourceRoot":"","sources":["../../../src/client/FooterRemoteEntry.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"FooterRemoteEntry.d.ts","sourceRoot":"","sources":["../../../src/client/FooterRemoteEntry.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAGnE,gFAAgF;AAChF,MAAM,MAAM,sBAAsB,GAAG,WAAW,CAAC,QAAQ,CAAC,GAAG;IAC3D,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd,CAAA;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,+BAO9D"}
|
|
@@ -6,5 +6,5 @@ import { RemoteEntry } from "./RemoteEntry.js";
|
|
|
6
6
|
* @returns the entry element tree.
|
|
7
7
|
*/
|
|
8
8
|
export function FooterRemoteEntry(props) {
|
|
9
|
-
return (_jsx(RemoteEntry, { wide: props.wide,
|
|
9
|
+
return (_jsx(RemoteEntry, { wide: props.wide, t: props.t }));
|
|
10
10
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import type { PropsLocale
|
|
2
|
-
/** Entry props: the sidebar column state
|
|
3
|
-
export type RemoteEntryProps =
|
|
1
|
+
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
/** Entry props: the sidebar column state and the standard locale seat. */
|
|
3
|
+
export type RemoteEntryProps = PropsLocale<'remote'> & {
|
|
4
|
+
/** Whether the sidebar renders wide content (false = 56px rail). */
|
|
5
|
+
wide: boolean;
|
|
6
|
+
};
|
|
4
7
|
/**
|
|
5
8
|
* Render the remote-control trigger and panel.
|
|
6
9
|
* @param props - composed slot props (contract in this package).
|
|
7
10
|
* @returns the entry element tree.
|
|
8
11
|
*/
|
|
9
|
-
export declare function RemoteEntry({ wide,
|
|
12
|
+
export declare function RemoteEntry({ wide, t }: RemoteEntryProps): import("react").JSX.Element;
|
|
10
13
|
//# sourceMappingURL=RemoteEntry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemoteEntry.d.ts","sourceRoot":"","sources":["../../../src/client/RemoteEntry.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"RemoteEntry.d.ts","sourceRoot":"","sources":["../../../src/client/RemoteEntry.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAQnE,0EAA0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC,QAAQ,CAAC,GAAG;IACrD,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd,CAAA;AA0BD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,gBAAgB,+BA2LxD"}
|
|
@@ -44,7 +44,7 @@ function mergeFrame(state, frame) {
|
|
|
44
44
|
* @param props - composed slot props (contract in this package).
|
|
45
45
|
* @returns the entry element tree.
|
|
46
46
|
*/
|
|
47
|
-
export function RemoteEntry({ wide,
|
|
47
|
+
export function RemoteEntry({ wide, t }) {
|
|
48
48
|
const [open, setOpen] = useState(false);
|
|
49
49
|
const [state, setState] = useState({ kind: 'lan-required' });
|
|
50
50
|
// Latest-state mirror for the EventSource callback: transition detection
|
|
@@ -52,15 +52,12 @@ export function RemoteEntry({ wide, useWorkspaces, t }) {
|
|
|
52
52
|
// pure), so mint decisions read this ref instead.
|
|
53
53
|
const stateRef = useRef(state);
|
|
54
54
|
useEffect(() => { stateRef.current = state; }, [state]);
|
|
55
|
-
const [copied, setCopied] = useState(
|
|
55
|
+
const [copied, setCopied] = useState(false);
|
|
56
56
|
const eventSource = useRef(undefined);
|
|
57
57
|
// Generation counter for the open flow: closing (or re-opening) the panel
|
|
58
58
|
// bumps it, so an in-flight issue() that resolves after a close does not
|
|
59
59
|
// spawn a stray EventSource.
|
|
60
60
|
const openSeq = useRef(0);
|
|
61
|
-
// The current workspace (the recent-workspace projection the shell's New
|
|
62
|
-
// Session flow targets) — the deep-link target for the phone.
|
|
63
|
-
const workspaceId = useWorkspaces(s => s.recentWorkspaceId);
|
|
64
61
|
const closeEventSource = useCallback(() => {
|
|
65
62
|
eventSource.current?.close();
|
|
66
63
|
eventSource.current = undefined;
|
|
@@ -68,7 +65,7 @@ export function RemoteEntry({ wide, useWorkspaces, t }) {
|
|
|
68
65
|
const mint = useCallback(async (address) => {
|
|
69
66
|
let result;
|
|
70
67
|
try {
|
|
71
|
-
result = await issuePair(
|
|
68
|
+
result = await issuePair(address);
|
|
72
69
|
}
|
|
73
70
|
catch {
|
|
74
71
|
// Fetch/network failure: show an explicit state instead of silently
|
|
@@ -103,7 +100,7 @@ export function RemoteEntry({ wide, useWorkspaces, t }) {
|
|
|
103
100
|
address: address ?? result.lanAddresses[0] ?? '',
|
|
104
101
|
lanAddresses: result.lanAddresses,
|
|
105
102
|
};
|
|
106
|
-
}, [
|
|
103
|
+
}, []);
|
|
107
104
|
const openPanel = useCallback(async () => {
|
|
108
105
|
const seq = ++openSeq.current;
|
|
109
106
|
setOpen(true);
|
|
@@ -196,12 +193,12 @@ export function RemoteEntry({ wide, useWorkspaces, t }) {
|
|
|
196
193
|
const handlePickPublic = useCallback(() => {
|
|
197
194
|
void mint().then(setState);
|
|
198
195
|
}, [mint]);
|
|
199
|
-
const handleCopy = useCallback((
|
|
196
|
+
const handleCopy = useCallback((url) => {
|
|
200
197
|
void copyText(url).then((ok) => {
|
|
201
198
|
if (!ok)
|
|
202
199
|
return;
|
|
203
|
-
setCopied(
|
|
204
|
-
window.setTimeout(() => { setCopied(
|
|
200
|
+
setCopied(true);
|
|
201
|
+
window.setTimeout(() => { setCopied(false); }, 1500);
|
|
205
202
|
});
|
|
206
203
|
}, []);
|
|
207
204
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.entryRow, "data-rail": wide ? undefined : 'rail', children: [_jsx(UpdateEntry, { wide: wide, t: t }), _jsx(TooltipAnchor, { wide: wide, label: t('entry.label'), onClick: openPanel, expanded: open })] }), open && createPortal((_jsxs("div", { className: css.overlay, role: "presentation", children: [_jsx("div", { className: css.mask, "aria-hidden": "true", onClick: closePanel }), _jsx(RemotePanel, { t: t, state: state, copied: copied, onClose: closePanel, onStop: handleStop, onRefresh: handleRefresh, onCopy: handleCopy, onPickAddress: handlePickAddress, onPickPublic: handlePickPublic, onRevoke: handleRevoke })] })), document.body)] }));
|
|
@@ -36,11 +36,11 @@ export type PanelState = {
|
|
|
36
36
|
export interface RemotePanelProps {
|
|
37
37
|
t: TranslateNS<'remote'>;
|
|
38
38
|
state: PanelState;
|
|
39
|
-
copied:
|
|
39
|
+
copied: boolean;
|
|
40
40
|
onClose(): void;
|
|
41
41
|
onStop(): void;
|
|
42
42
|
onRefresh(): void;
|
|
43
|
-
onCopy(
|
|
43
|
+
onCopy(url: string): void;
|
|
44
44
|
/** Re-mint the QR against a different LAN address. */
|
|
45
45
|
onPickAddress(address: string): void;
|
|
46
46
|
/** Re-mint the QR against the configured public (tunneled) base. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemotePanel.d.ts","sourceRoot":"","sources":["../../../src/client/RemotePanel.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,
|
|
1
|
+
{"version":3,"file":"RemotePanel.d.ts","sourceRoot":"","sources":["../../../src/client/RemotePanel.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAA;AAItB,4DAA4D;AAC5D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,mBAAmB,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IACE,IAAI,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,YAAY,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,2DAA2D;IAC3D,OAAO,EAAE,WAAW,EAAE,CAAA;IACtB,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAA;IACf,yDAAyD;IACzD,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,yEAAyE;IACzE,MAAM,EAAE,OAAO,CAAA;IACf,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mEAAmE;IACnE,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB,CAAA;AAEL,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;IACxB,KAAK,EAAE,UAAU,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;IACf,OAAO,IAAI,IAAI,CAAA;IACf,MAAM,IAAI,IAAI,CAAA;IACd,SAAS,IAAI,IAAI,CAAA;IACjB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,sDAAsD;IACtD,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,oEAAoE;IACpE,YAAY,IAAI,IAAI,CAAA;IACpB,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAgBD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BA+J5I"}
|
|
@@ -8,7 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
8
8
|
import clsx from 'clsx';
|
|
9
9
|
import { QRCodeSVG } from 'qrcode.react';
|
|
10
10
|
import { IconCloseOutline16, IconCopyOutline16, IconRefreshOutline16, IconStopFill16, } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
11
|
-
import {
|
|
11
|
+
import { formatClock, formatLastSeen, } from "./pair-api.js";
|
|
12
12
|
import { deviceNameFromUserAgent } from "./device-name.js";
|
|
13
13
|
import css from './remote.module.css';
|
|
14
14
|
/** Badge text + tone per phase (ready states only). */
|
|
@@ -29,7 +29,7 @@ function statusOf(t, state) {
|
|
|
29
29
|
export function RemotePanel({ t, state, copied, onClose, onStop, onRefresh, onCopy, onPickAddress, onPickPublic, onRevoke }) {
|
|
30
30
|
return (_jsxs("div", { className: css.panel, role: "dialog", "aria-modal": "true", "aria-label": t('title'), children: [_jsxs("div", { className: css.header, children: [_jsxs("div", { className: css.heading, children: [_jsx("h2", { className: css.title, children: t('title') }), _jsx("p", { className: css.subtitle, children: t('subtitle') })] }), _jsx("button", { type: "button", className: css.close, "aria-label": t('close.label'), onClick: onClose, children: _jsx(IconCloseOutline16, { size: 14 }) })] }), state.kind === 'lan-required' ? (_jsxs("div", { className: css.banner, role: "alert", children: [_jsx("p", { className: css.bannerTitle, children: t('status.lanRequired') }), _jsx("p", { className: css.bannerHint, children: t('status.lanRequiredHint') })] })) : state.kind === 'loopback-required' ? (_jsxs("div", { className: css.banner, role: "alert", children: [_jsx("p", { className: css.bannerTitle, children: t('status.loopbackRequired') }), _jsx("p", { className: css.bannerHint, children: t('status.loopbackRequiredHint') })] })) : state.kind === 'unreachable' ? (_jsxs("div", { className: css.banner, role: "alert", children: [_jsx("p", { className: css.bannerTitle, children: t('status.unreachable') }), _jsx("p", { className: css.bannerHint, children: t('status.unreachableHint') })] })) : (_jsxs(_Fragment, { children: [state.posture !== undefined && state.posture.hosts.some(host => host.exposed) && (_jsxs("div", { className: css.banner, role: "alert", children: [_jsx("p", { className: css.bannerTitle, children: t('posture.exposed') }), _jsx("p", { className: css.bannerHint, children: t('posture.exposedHint', { hosts: state.posture.hosts.filter(host => host.exposed).map(host => host.host).join(', ') }) })] })), _jsxs("div", { className: css.card, children: [_jsxs("div", { className: css.cardHeader, children: [_jsx("span", { className: css.cardTitle, children: t('card.title') }), _jsxs("span", { className: css.badges, children: [state.public && _jsx("span", { className: clsx(css.badge, css.badgePublic), children: t('public.badge') }), _jsx("span", { className: clsx(css.badge, css[`badge-${statusOf(t, state).tone}`]), children: statusOf(t, state).text })] })] }), _jsx("div", { className: css.qrWrap, "data-testid": "remote-qr", children: _jsx(QRCodeSVG, { value: state.url, size: 184, level: "M", marginSize: 1, className: css.qr }) }), state.expired
|
|
31
31
|
? _jsx("p", { className: css.expired, children: t('pair.expired') })
|
|
32
|
-
: _jsx("p", { className: css.expiry, children: t('pair.expires', { time: formatClock(state.expiresAt) }) })] }), _jsx("p", { className: css.hint, children: state.public ? t('pair.publicHint') : t('pair.hint') }),
|
|
32
|
+
: _jsx("p", { className: css.expiry, children: t('pair.expires', { time: formatClock(state.expiresAt) }) })] }), _jsx("p", { className: css.hint, children: state.public ? t('pair.publicHint') : t('pair.hint') }), _jsx("div", { className: css.pairLinks, children: _jsxs("div", { className: css.pairLinkRow, children: [_jsxs("div", { className: css.pairLinkText, children: [_jsx("span", { className: css.pairLinkLabel, children: t('pair.linkLabel') }), _jsx("code", { className: css.link, title: state.url, children: state.url })] }), _jsxs("button", { type: "button", className: css.copyLink, onClick: () => onCopy(state.url), children: [_jsx(IconCopyOutline16, { size: 14 }), copied ? t('action.copied') : t('action.copyLink')] })] }) }), _jsx("p", { className: css.oneTimeHint, children: t('pair.oneTimeHint') }), state.phase === 'stopped' && _jsx("p", { className: css.stoppedHint, children: t('stopped.hint') }), state.tunnel !== undefined && state.tunnel.state !== 'running' && (_jsx("p", { className: state.tunnel.state === 'failed' ? css.tunnelFailed : css.tunnelNote, role: "status", children: state.tunnel.state === 'failed'
|
|
33
33
|
? t('tunnel.failed', { error: state.tunnel.error ?? t('tunnel.unknownError') })
|
|
34
34
|
: t('tunnel.starting') })), (state.publicBaseUrl !== undefined || state.lanAddresses.length > 1) && (_jsxs("fieldset", { className: css.addresses, children: [_jsx("legend", { children: t('address.label') }), state.publicBaseUrl !== undefined && (_jsxs("label", { className: css.address, children: [_jsx("input", { type: "radio", name: "lan-address", "aria-label": t('address.public'), checked: state.public, onChange: onPickPublic }), _jsx("span", { children: t('address.public') }), _jsx("code", { className: css.addressValue, children: state.publicBaseUrl })] }, "public")), state.lanAddresses.map(address => (_jsxs("label", { className: css.address, children: [_jsx("input", { type: "radio", name: "lan-address", "aria-label": address, checked: !state.public && address === state.address, onChange: () => onPickAddress(address) }), _jsx("span", { children: t('address.lan') }), _jsx("code", { className: css.addressValue, children: address })] }, address))), _jsx("p", { className: css.addressHint, children: t('address.hint') })] })), _jsxs("div", { className: css.actions, children: [_jsxs("button", { type: "button", className: css.action, onClick: onStop, children: [_jsx(IconStopFill16, { size: 14 }), t('action.stop')] }), _jsxs("button", { type: "button", className: css.action, onClick: onRefresh, children: [_jsx(IconRefreshOutline16, { size: 14 }), t('action.refresh')] })] }), _jsxs("section", { className: css.devices, "data-testid": "remote-devices", "aria-label": t('devices.title'), children: [_jsx("h3", { className: css.devicesTitle, children: t('devices.title') }), state.devices.length === 0 ? (_jsx("p", { className: css.devicesEmpty, children: t('devices.empty') })) : (_jsx("ul", { className: css.deviceList, children: state.devices.map(device => (_jsxs("li", { className: css.deviceRow, children: [_jsxs("div", { className: css.deviceMeta, children: [_jsx("span", { className: css.deviceName, children: deviceNameFromUserAgent(device.userAgent) ?? t('devices.unknown') }), _jsx("span", { className: clsx(css.devicePresence, device.online ? css.deviceOnline : css.deviceOffline), children: device.online ? t('devices.online') : t('devices.offline') }), _jsx("span", { className: css.deviceSeen, children: t('devices.lastSeen', { time: formatLastSeen(device.lastSeenAt) }) })] }), _jsx("button", { type: "button", className: css.deviceRevoke, "aria-label": t('devices.revoke.label'), onClick: () => { onRevoke(device.id); }, children: t('devices.revoke') })] }, device.id))) }))] })] }))] }));
|
|
35
35
|
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* renders, bound to the `remote-web-ui` settings namespace.
|
|
5
5
|
*/
|
|
6
6
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
7
|
-
import type { SettingsScope
|
|
7
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
8
|
+
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
8
9
|
import { type CardActions, type CardShell, type FieldState as CardFieldState } from './settings-form.ts';
|
|
9
10
|
/** The remote-control fields this card edits (the namespace's full schema). */
|
|
10
11
|
export interface RemoteSettings {
|
|
@@ -26,8 +27,11 @@ export interface RemoteSettings {
|
|
|
26
27
|
publicBaseUrl?: string;
|
|
27
28
|
/** When on, the plugin runs its own Cloudflare quick tunnel automatically. */
|
|
28
29
|
autoTunnel?: boolean;
|
|
29
|
-
/**
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* LAN bind toggle: once flipped, the plugin manages the profile patch's
|
|
32
|
+
* webserver block (0.0.0.0 / 127.0.0.1) and the host firewall rule.
|
|
33
|
+
*/
|
|
34
|
+
lanBind?: boolean;
|
|
31
35
|
}
|
|
32
36
|
/** What the remote-control card renders. */
|
|
33
37
|
export interface RemoteSettingsCardState extends CardShell {
|
|
@@ -49,8 +53,8 @@ export interface RemoteSettingsCardState extends CardShell {
|
|
|
49
53
|
publicBaseUrl: CardFieldState;
|
|
50
54
|
/** Auto public tunnel switch. */
|
|
51
55
|
autoTunnel: CardFieldState;
|
|
52
|
-
/**
|
|
53
|
-
|
|
56
|
+
/** LAN bind toggle. */
|
|
57
|
+
lanBind: CardFieldState;
|
|
54
58
|
}
|
|
55
59
|
/** The registration-side face the card's slot entry injects. */
|
|
56
60
|
export interface RemoteSettingsCardFace extends CardActions {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemoteSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/RemoteSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"RemoteSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/RemoteSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC7F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4CAA4C,CAAA;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAElE,OAAO,EAAkD,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAIxJ,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6FAA6F;IAC7F,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,4CAA4C;AAC5C,MAAM,WAAW,uBAAwB,SAAQ,SAAS;IACxD,qBAAqB;IACrB,OAAO,EAAE,cAAc,CAAA;IACvB,sBAAsB;IACtB,UAAU,EAAE,cAAc,CAAA;IAC1B,gCAAgC;IAChC,cAAc,EAAE,cAAc,CAAA;IAC9B,yBAAyB;IACzB,UAAU,EAAE,cAAc,CAAA;IAC1B,0BAA0B;IAC1B,YAAY,EAAE,cAAc,CAAA;IAC5B,0BAA0B;IAC1B,UAAU,EAAE,cAAc,CAAA;IAC1B,sBAAsB;IACtB,oBAAoB,EAAE,cAAc,CAAA;IACpC,kCAAkC;IAClC,aAAa,EAAE,cAAc,CAAA;IAC7B,iCAAiC;IACjC,UAAU,EAAE,cAAc,CAAA;IAC1B,uBAAuB;IACvB,OAAO,EAAE,cAAc,CAAA;CACxB;AAED,gEAAgE;AAChE,MAAM,WAAW,sBAAuB,SAAQ,WAAW;IACzD,KAAK,EAAE;QACL,oEAAoE;QACpE,kBAAkB,EAAE,aAAa,CAAC,uBAAuB,CAAC,CAAA;KAC3D,CAAA;CACF;AAED,qEAAqE;AACrE,qBAAa,4BAA4B;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0B;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwC;IAE9D,iFAAiF;gBACrE,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC;IAgBhD,OAAO,CAAC,UAAU;IAgBlB;;;OAGG;IACH,MAAM,IAAI,sBAAsB;IAIhC;;;OAGG;IACH,OAAO,IAAI,IAAI;CAGhB;AAED,4DAA4D;AAC5D,MAAM,MAAM,uBAAuB,GACjC,YAAY,CAAC,oBAAoB,CAAC,GAChC,WAAW,CAAC,QAAQ,CAAC,GACrB,UAAU,CAAC,sBAAsB,CAAC,CAAA;AAEtC;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,+BAkIhE"}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The remote-control settings card: pairing security and device limits.
|
|
4
|
+
* Registers into the `web-ui.plugin.item` child slot the Web UI plugin group
|
|
5
|
+
* renders, bound to the `remote-web-ui` settings namespace.
|
|
6
|
+
*/
|
|
7
|
+
import { useEffect, useState } from 'react';
|
|
2
8
|
import { PluginSettingsCard, ValueField, BooleanField } from "./PluginSettingsCard.js";
|
|
3
9
|
import { CardForm, booleanField, numberField, textField } from "./settings-form.js";
|
|
10
|
+
import { readLanBindStatus } from "./pair-api.js";
|
|
4
11
|
/** Bridges the `remote-web-ui` scope onto the card's staged form. */
|
|
5
12
|
export class RemoteSettingsCardController {
|
|
6
13
|
form;
|
|
@@ -17,7 +24,7 @@ export class RemoteSettingsCardController {
|
|
|
17
24
|
booleanField('requirePairingForLan'),
|
|
18
25
|
textField('publicBaseUrl'),
|
|
19
26
|
booleanField('autoTunnel'),
|
|
20
|
-
booleanField('
|
|
27
|
+
booleanField('lanBind'),
|
|
21
28
|
]);
|
|
22
29
|
this.store = this.form.bind(() => this.projection());
|
|
23
30
|
}
|
|
@@ -33,7 +40,7 @@ export class RemoteSettingsCardController {
|
|
|
33
40
|
requirePairingForLan: this.form.field('requirePairingForLan'),
|
|
34
41
|
publicBaseUrl: this.form.field('publicBaseUrl'),
|
|
35
42
|
autoTunnel: this.form.field('autoTunnel'),
|
|
36
|
-
|
|
43
|
+
lanBind: this.form.field('lanBind'),
|
|
37
44
|
};
|
|
38
45
|
}
|
|
39
46
|
/**
|
|
@@ -66,5 +73,51 @@ export function RemoteSettingsCard(props) {
|
|
|
66
73
|
invalidLabel: t('settings.invalidNumber'),
|
|
67
74
|
disabled,
|
|
68
75
|
};
|
|
69
|
-
return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", defaultOpen: false, state: state, onSave: props.save, onDiscard: props.discard, children: [_jsx(BooleanField, { id: "settings-remote-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), _jsx(ValueField, { id: "settings-remote-token-ttl", label: t('settings.tokenTtlMs'), hint: t('settings.tokenTtlMsHint'), numeric: true, ...fieldProps, ...state.tokenTtlMs, onEdit: (text) => { props.edit('tokenTtlMs', text); }, onReset: () => { props.resetField('tokenTtlMs'); } }), _jsx(ValueField, { id: "settings-remote-offline", label: t('settings.offlineAfterMs'), hint: t('settings.offlineAfterMsHint'), numeric: true, ...fieldProps, ...state.offlineAfterMs, onEdit: (text) => { props.edit('offlineAfterMs', text); }, onReset: () => { props.resetField('offlineAfterMs'); } }), _jsx(ValueField, { id: "settings-remote-max-devices", label: t('settings.maxDevices'), hint: t('settings.maxDevicesHint'), numeric: true, ...fieldProps, ...state.maxDevices, onEdit: (text) => { props.edit('maxDevices', text); }, onReset: () => { props.resetField('maxDevices'); } }), _jsx(ValueField, { id: "settings-remote-idle-expire", label: t('settings.idleExpireMs'), hint: t('settings.idleExpireMsHint'), numeric: true, ...fieldProps, ...state.idleExpireMs, onEdit: (text) => { props.edit('idleExpireMs', text); }, onReset: () => { props.resetField('idleExpireMs'); } }), _jsx(ValueField, { id: "settings-remote-cookie", label: t('settings.cookieName'), hint: t('settings.cookieNameHint'), ...fieldProps, ...state.cookieName, onEdit: (text) => { props.edit('cookieName', text); }, onReset: () => { props.resetField('cookieName'); } }), _jsx(BooleanField, { id: "settings-remote-fence", label: t('settings.requirePairingForLan'), hint: t('settings.requirePairingForLanHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.requirePairingForLan, onEdit: (text) => { props.edit('requirePairingForLan', text); }, onReset: () => { props.resetField('requirePairingForLan'); } }), _jsx(ValueField, { id: "settings-remote-public-base", label: t('settings.publicBaseUrl'), hint: t('settings.publicBaseUrlHint'), placeholder: "https://example.trycloudflare.com", ...fieldProps, ...state.publicBaseUrl, onEdit: (text) => { props.edit('publicBaseUrl', text); }, onReset: () => { props.resetField('publicBaseUrl'); } }), _jsx(BooleanField, { id: "settings-remote-auto-tunnel", label: t('settings.autoTunnel'), hint: t('settings.autoTunnelHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.autoTunnel, onEdit: (text) => { props.edit('autoTunnel', text); }, onReset: () => { props.resetField('autoTunnel'); } }), _jsx(BooleanField, { id: "settings-remote-
|
|
76
|
+
return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", defaultOpen: false, state: state, onSave: props.save, onDiscard: props.discard, children: [_jsx(BooleanField, { id: "settings-remote-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), _jsx(ValueField, { id: "settings-remote-token-ttl", label: t('settings.tokenTtlMs'), hint: t('settings.tokenTtlMsHint'), numeric: true, ...fieldProps, ...state.tokenTtlMs, onEdit: (text) => { props.edit('tokenTtlMs', text); }, onReset: () => { props.resetField('tokenTtlMs'); } }), _jsx(ValueField, { id: "settings-remote-offline", label: t('settings.offlineAfterMs'), hint: t('settings.offlineAfterMsHint'), numeric: true, ...fieldProps, ...state.offlineAfterMs, onEdit: (text) => { props.edit('offlineAfterMs', text); }, onReset: () => { props.resetField('offlineAfterMs'); } }), _jsx(ValueField, { id: "settings-remote-max-devices", label: t('settings.maxDevices'), hint: t('settings.maxDevicesHint'), numeric: true, ...fieldProps, ...state.maxDevices, onEdit: (text) => { props.edit('maxDevices', text); }, onReset: () => { props.resetField('maxDevices'); } }), _jsx(ValueField, { id: "settings-remote-idle-expire", label: t('settings.idleExpireMs'), hint: t('settings.idleExpireMsHint'), numeric: true, ...fieldProps, ...state.idleExpireMs, onEdit: (text) => { props.edit('idleExpireMs', text); }, onReset: () => { props.resetField('idleExpireMs'); } }), _jsx(ValueField, { id: "settings-remote-cookie", label: t('settings.cookieName'), hint: t('settings.cookieNameHint'), ...fieldProps, ...state.cookieName, onEdit: (text) => { props.edit('cookieName', text); }, onReset: () => { props.resetField('cookieName'); } }), _jsx(BooleanField, { id: "settings-remote-fence", label: t('settings.requirePairingForLan'), hint: t('settings.requirePairingForLanHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.requirePairingForLan, onEdit: (text) => { props.edit('requirePairingForLan', text); }, onReset: () => { props.resetField('requirePairingForLan'); } }), _jsx(ValueField, { id: "settings-remote-public-base", label: t('settings.publicBaseUrl'), hint: t('settings.publicBaseUrlHint'), placeholder: "https://example.trycloudflare.com", ...fieldProps, ...state.publicBaseUrl, onEdit: (text) => { props.edit('publicBaseUrl', text); }, onReset: () => { props.resetField('publicBaseUrl'); } }), _jsx(BooleanField, { id: "settings-remote-auto-tunnel", label: t('settings.autoTunnel'), hint: t('settings.autoTunnelHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.autoTunnel, onEdit: (text) => { props.edit('autoTunnel', text); }, onReset: () => { props.resetField('autoTunnel'); } }), _jsx(LanBindStatus, { t: t }), _jsx(BooleanField, { id: "settings-remote-lan-bind", label: t('settings.lanBind'), hint: t('settings.lanBindHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.lanBind, onEdit: (text) => { props.edit('lanBind', text); }, onReset: () => { props.resetField('lanBind'); } })] }));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Live LAN-bind facts above the toggle: the managed block's host, the live
|
|
80
|
+
* bind, the firewall summary, and the reachable LAN URLs. Polls the
|
|
81
|
+
* loopback-only status endpoint; a failure (non-loopback origin) renders
|
|
82
|
+
* nothing — the pairing panel carries the loopback banner instead.
|
|
83
|
+
*/
|
|
84
|
+
function LanBindStatus({ t }) {
|
|
85
|
+
const [frame, setFrame] = useState(undefined);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
let alive = true;
|
|
88
|
+
const read = () => {
|
|
89
|
+
void readLanBindStatus().then((value) => {
|
|
90
|
+
if (alive)
|
|
91
|
+
setFrame(value);
|
|
92
|
+
}).catch(() => { });
|
|
93
|
+
};
|
|
94
|
+
read();
|
|
95
|
+
const timer = window.setInterval(read, 10_000);
|
|
96
|
+
return () => {
|
|
97
|
+
alive = false;
|
|
98
|
+
window.clearInterval(timer);
|
|
99
|
+
};
|
|
100
|
+
}, []);
|
|
101
|
+
if (frame === undefined)
|
|
102
|
+
return null;
|
|
103
|
+
const lanOn = frame.blockHost === '0.0.0.0';
|
|
104
|
+
const firewallText = frame.firewall.managed
|
|
105
|
+
? t(frame.firewall.ok ? 'lan.firewall.ok' : 'lan.firewall.bad')
|
|
106
|
+
: t('lan.firewall.unmanaged');
|
|
107
|
+
const lines = [
|
|
108
|
+
t('lan.bind', { host: frame.bindHost, port: String(frame.port) }) + ' · ' + firewallText,
|
|
109
|
+
];
|
|
110
|
+
if (lanOn && frame.lanUrls.length > 0) {
|
|
111
|
+
lines.push(t('lan.urls', { urls: frame.lanUrls.join(' ') }));
|
|
112
|
+
}
|
|
113
|
+
if (!lanOn) {
|
|
114
|
+
lines.push(t('lan.off'));
|
|
115
|
+
}
|
|
116
|
+
if (frame.setting === null) {
|
|
117
|
+
lines.push(t('lan.untouched'));
|
|
118
|
+
}
|
|
119
|
+
if (frame.pendingRestart === true) {
|
|
120
|
+
lines.push(t('lan.pendingRestart'));
|
|
121
|
+
}
|
|
122
|
+
return (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 2, fontSize: 12, lineHeight: '18px', opacity: 0.85 }, children: lines.map((line, index) => (_jsx("div", { style: { wordBreak: 'break-all' }, children: line }, index))) }));
|
|
70
123
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Failure of accept leaves a sessionStorage marker the entry renders as a
|
|
10
|
-
* one-time notice.
|
|
2
|
+
* Pair boot flow: the QR link's `pair` parameter. Runs from the client
|
|
3
|
+
* apply on every page load, on any device: `pair` present → accept the
|
|
4
|
+
* token, then reload so the whole SPA boots with the device cookie (the
|
|
5
|
+
* accept endpoint is exempt from the pairing gate; every other /api request
|
|
6
|
+
* needs the cookie). Every device — phone or PC — lands on the same official
|
|
7
|
+
* Web GUI; phones get the injected portrait adaptation. Failure of accept
|
|
8
|
+
* leaves a sessionStorage marker the entry renders as a one-time notice.
|
|
11
9
|
*/
|
|
12
10
|
import type { Context } from '@deepseek-ai/cordis';
|
|
13
11
|
/** sessionStorage key for the failed-pair notice. */
|
|
@@ -28,11 +26,10 @@ export interface PageSurface {
|
|
|
28
26
|
}
|
|
29
27
|
/** The browser implementation of {@link PageSurface}. */
|
|
30
28
|
export declare const browserPage: PageSurface;
|
|
31
|
-
/** Whether this browser looks like a phone/tablet (the simplified mobile surface). */
|
|
32
|
-
export declare function isMobileSurface(): boolean;
|
|
33
29
|
/**
|
|
34
|
-
* Run the pair
|
|
35
|
-
* @param ctx - client root context (
|
|
30
|
+
* Run the pair boot flow for this page load.
|
|
31
|
+
* @param ctx - client root context (unused since the deep-link retirement;
|
|
32
|
+
* kept for call-site stability).
|
|
36
33
|
* @param search - the current location.search.
|
|
37
34
|
* @param page - the page surface (defaults to the browser).
|
|
38
35
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deep-link.d.ts","sourceRoot":"","sources":["../../../src/client/deep-link.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"deep-link.d.ts","sourceRoot":"","sources":["../../../src/client/deep-link.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,qDAAqD;AACrD,eAAO,MAAM,kBAAkB,2BAA2B,CAAA;AAE1D;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ,yCAAyC;IACzC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,6CAA6C;IAC7C,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,uBAAuB;IACvB,MAAM,IAAI,IAAI,CAAA;CACf;AAED,yDAAyD;AACzD,eAAO,MAAM,WAAW,EAAE,WAazB,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,WAAyB,GAAG,IAAI,CAMnG"}
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Failure of accept leaves a sessionStorage marker the entry renders as a
|
|
10
|
-
* one-time notice.
|
|
2
|
+
* Pair boot flow: the QR link's `pair` parameter. Runs from the client
|
|
3
|
+
* apply on every page load, on any device: `pair` present → accept the
|
|
4
|
+
* token, then reload so the whole SPA boots with the device cookie (the
|
|
5
|
+
* accept endpoint is exempt from the pairing gate; every other /api request
|
|
6
|
+
* needs the cookie). Every device — phone or PC — lands on the same official
|
|
7
|
+
* Web GUI; phones get the injected portrait adaptation. Failure of accept
|
|
8
|
+
* leaves a sessionStorage marker the entry renders as a one-time notice.
|
|
11
9
|
*/
|
|
12
10
|
import { acceptPair, readPairParams } from "./pair-api.js";
|
|
13
11
|
/** sessionStorage key for the failed-pair notice. */
|
|
14
12
|
export const PAIR_FAILED_MARKER = 'dsh-remote-pair-failed';
|
|
15
|
-
/** Poll budget for the runtime services (activation order is unconstrained). */
|
|
16
|
-
const SERVICE_WAIT_MS = 10_000;
|
|
17
13
|
/** The browser implementation of {@link PageSurface}. */
|
|
18
14
|
export const browserPage = {
|
|
19
15
|
get href() {
|
|
@@ -29,29 +25,21 @@ export const browserPage = {
|
|
|
29
25
|
window.location.reload();
|
|
30
26
|
},
|
|
31
27
|
};
|
|
32
|
-
/** Whether this browser looks like a phone/tablet (the simplified mobile surface). */
|
|
33
|
-
export function isMobileSurface() {
|
|
34
|
-
if (typeof navigator === 'undefined')
|
|
35
|
-
return false;
|
|
36
|
-
return /Android|iPhone|iPad|iPod|Mobile|mobile/i.test(navigator.userAgent);
|
|
37
|
-
}
|
|
38
28
|
/**
|
|
39
|
-
* Run the pair
|
|
40
|
-
* @param ctx - client root context (
|
|
29
|
+
* Run the pair boot flow for this page load.
|
|
30
|
+
* @param ctx - client root context (unused since the deep-link retirement;
|
|
31
|
+
* kept for call-site stability).
|
|
41
32
|
* @param search - the current location.search.
|
|
42
33
|
* @param page - the page surface (defaults to the browser).
|
|
43
34
|
*/
|
|
44
35
|
export function runPairBootFlow(ctx, search, page = browserPage) {
|
|
36
|
+
void ctx;
|
|
45
37
|
const params = readPairParams(search);
|
|
46
38
|
if (params.pair !== undefined) {
|
|
47
39
|
void runAccept(params.pair, page);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (params.workspace !== undefined) {
|
|
51
|
-
void runDeepLink(ctx, params.workspace, page);
|
|
52
40
|
}
|
|
53
41
|
}
|
|
54
|
-
/** Accept the token, then
|
|
42
|
+
/** Accept the token, then reload into the paired official UI. */
|
|
55
43
|
async function runAccept(token, page) {
|
|
56
44
|
let ok = false;
|
|
57
45
|
try {
|
|
@@ -69,55 +57,7 @@ async function runAccept(token, page) {
|
|
|
69
57
|
url.searchParams.delete('pair');
|
|
70
58
|
page.replaceState(`${url.pathname}${url.search}${url.hash}`);
|
|
71
59
|
if (ok) {
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
// mobile surface can open the intended workspace instead of losing the
|
|
75
|
-
// QR context at this navigation boundary.
|
|
76
|
-
if (isMobileSurface()) {
|
|
77
|
-
url.pathname = '/m/';
|
|
78
|
-
page.navigate(`${url.pathname}${url.search}${url.hash}`);
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
page.reload();
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Connect the deep-linked workspace and open its session. Waits for the
|
|
87
|
-
* runtime services AND for the target workspace to appear in the workspace
|
|
88
|
-
* list (both are asynchronous after boot), then connects and opens; gives
|
|
89
|
-
* up silently within the budget — the workspace param is stripped either
|
|
90
|
-
* way, so a late failure cannot loop.
|
|
91
|
-
* @param ctx - client root context.
|
|
92
|
-
* @param workspaceId - the target workspace.
|
|
93
|
-
* @param page - the page surface.
|
|
94
|
-
*/
|
|
95
|
-
async function runDeepLink(ctx, workspaceId, page) {
|
|
96
|
-
const target = workspaceId;
|
|
97
|
-
const deadline = Date.now() + SERVICE_WAIT_MS;
|
|
98
|
-
while (Date.now() < deadline) {
|
|
99
|
-
const workspaces = ctx.get('workspaces');
|
|
100
|
-
const sessions = ctx.get('sessions');
|
|
101
|
-
if (workspaces !== undefined && sessions !== undefined) {
|
|
102
|
-
const items = workspaces.list.getSnapshot().items;
|
|
103
|
-
if (items.some(item => item.workspaceId === target)) {
|
|
104
|
-
try {
|
|
105
|
-
// Open unconditionally: a host-side "current" session may already
|
|
106
|
-
// exist (multi-client mirroring), but the QR's workspace target is
|
|
107
|
-
// explicit user intent and must win.
|
|
108
|
-
const sessionId = await workspaces.connectWorkspace(target);
|
|
109
|
-
sessions.open(sessionId);
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
// Unknown workspace or a failed connect: fall through to the
|
|
113
|
-
// runtime's own default initial selection.
|
|
114
|
-
}
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
|
60
|
+
// Every device reloads into the same official Web GUI at this origin.
|
|
61
|
+
page.reload();
|
|
119
62
|
}
|
|
120
|
-
const url = new URL(page.href);
|
|
121
|
-
url.searchParams.delete('workspace');
|
|
122
|
-
page.replaceState(`${url.pathname}${url.search}${url.hash}`);
|
|
123
63
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
2
3
|
import { type RemoteKey } from './locales.ts';
|
|
3
4
|
export type { RemoteEntryProps } from './RemoteEntry.tsx';
|
|
4
5
|
export type { PanelState, RemotePanelProps } from './RemotePanel.tsx';
|
|
@@ -18,11 +19,6 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
18
19
|
* sidebar shell on deployments that carry the feature seat; the shell
|
|
19
20
|
* passes only its column display state.
|
|
20
21
|
*/
|
|
21
|
-
'sidebar.remote': {
|
|
22
|
-
kind: 'single';
|
|
23
|
-
scope: 'root';
|
|
24
|
-
owner: SidebarRemoteOwnerProps;
|
|
25
|
-
};
|
|
26
22
|
/**
|
|
27
23
|
* The child slot the Web UI plugin group declares; this card registers
|
|
28
24
|
* into the group instead of the top-level `settings.plugin.item` list.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAelG,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAqBrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAErE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,0CAA0C;QAC1C,MAAM,EAAE,SAAS,CAAA;KAClB;IAED,UAAU,OAAO;QACf;;;;WAIG;QACH;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,4GAA4G;AAC5G,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAYD,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAoQ9C"}
|