@linxin666/dsh-remote-web-ui 0.3.24 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +13 -10
- package/README.zh.md +13 -10
- package/icon.svg +3 -0
- package/lib/client.js +263 -129
- package/lib/client.js.map +1 -1
- package/lib/index.js +565 -256
- package/lib/types/client/FenceNotice.d.ts.map +1 -1
- package/lib/types/client/FenceNotice.js +0 -3
- package/lib/types/client/PluginSettingsCard.d.ts +4 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +3 -2
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +14 -9
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +13 -4
- package/lib/types/client/RemoteSettingsCard.d.ts +6 -6
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +14 -8
- package/lib/types/client/UpdateEntry.js +2 -2
- package/lib/types/client/UpdatePanel.js +2 -2
- package/lib/types/client/deep-link.js +2 -2
- package/lib/types/client/index.d.ts +16 -8
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +47 -18
- package/lib/types/client/locales.d.ts +0 -2
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +6 -8
- package/lib/types/client/mobile-adapt.d.ts.map +1 -1
- package/lib/types/client/mobile-adapt.js +103 -22
- package/lib/types/client/pair-api.d.ts +32 -3
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +44 -6
- package/lib/types/client/plugin-card-seat.d.ts +23 -16
- package/lib/types/client/plugin-card-seat.d.ts.map +1 -1
- package/lib/types/client/plugin-card-seat.js +16 -14
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +18 -5
- package/lib/types/client/settings-form.d.ts +17 -17
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +30 -27
- package/lib/types/http.d.ts +18 -1
- package/lib/types/http.d.ts.map +1 -1
- package/lib/types/http.js +22 -1
- package/lib/types/index.d.ts +19 -11
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +91 -76
- package/lib/types/inner-auth.d.ts +3 -2
- package/lib/types/inner-auth.d.ts.map +1 -1
- package/lib/types/inner-auth.js +3 -2
- package/lib/types/pair-grant.d.ts +66 -0
- package/lib/types/pair-grant.d.ts.map +1 -0
- package/lib/types/pair-grant.js +95 -0
- package/lib/types/pairing.d.ts +44 -12
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +69 -20
- package/lib/types/pairing.persist.test.js +26 -0
- package/lib/types/posture.d.ts +11 -3
- package/lib/types/posture.d.ts.map +1 -1
- package/lib/types/posture.js +8 -3
- package/lib/types/remote-api.d.ts +10 -13
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +34 -34
- package/lib/types/remote-channel-boot.d.ts +6 -1
- package/lib/types/remote-channel-boot.d.ts.map +1 -1
- package/lib/types/remote-channel-boot.js +16 -8
- package/lib/types/remote-channel-rules.d.ts +14 -0
- package/lib/types/remote-channel-rules.d.ts.map +1 -1
- package/lib/types/remote-channel-rules.js +8 -0
- package/lib/types/remote-methods.d.ts +1 -1
- package/lib/types/remote-methods.d.ts.map +1 -1
- package/lib/types/remote-methods.js +1 -0
- package/lib/types/routes.d.ts +47 -5
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +160 -58
- package/lib/types/tunnel.d.ts +21 -6
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +58 -11
- package/lib/types/update.d.ts +4 -0
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +74 -26
- package/package.json +23 -20
- package/src/client/FenceNotice.tsx +0 -2
- package/src/client/PluginSettingsCard.tsx +7 -2
- package/src/client/RemoteEntry.tsx +14 -9
- package/src/client/RemotePanel.tsx +17 -7
- package/src/client/RemoteSettingsCard.tsx +16 -9
- package/src/client/UpdateEntry.tsx +2 -2
- package/src/client/UpdatePanel.tsx +3 -3
- package/src/client/deep-link.ts +2 -2
- package/src/client/index.ts +61 -27
- package/src/client/locales.ts +6 -8
- package/src/client/mobile-adapt.ts +97 -20
- package/src/client/pair-api.ts +47 -7
- package/src/client/plugin-card-seat.ts +24 -17
- package/src/client/remote-channel.ts +19 -4
- package/src/client/remote.module.css +38 -0
- package/src/client/settings-form.ts +34 -31
- package/src/http.ts +23 -1
- package/src/index.ts +102 -79
- package/src/inner-auth.ts +3 -2
- package/src/pair-grant.ts +131 -0
- package/src/pairing.persist.test.ts +36 -0
- package/src/pairing.ts +75 -24
- package/src/posture.ts +12 -4
- package/src/remote-api.ts +34 -41
- package/src/remote-channel-boot.ts +16 -8
- package/src/remote-channel-rules.ts +17 -0
- package/src/remote-methods.ts +1 -0
- package/src/routes.ts +165 -57
- package/src/tunnel.ts +56 -11
- package/src/update.ts +74 -24
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FenceNotice.d.ts","sourceRoot":"","sources":["../../../src/client/FenceNotice.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAInE,oCAAoC;AACpC,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;IACxB,8EAA8E;IAC9E,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,WAAW,CAAA;KAAE,CAAC,CAAA;CAC5G;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWlE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,QAAqB,EAAE,EAAE,gBAAgB,+
|
|
1
|
+
{"version":3,"file":"FenceNotice.d.ts","sourceRoot":"","sources":["../../../src/client/FenceNotice.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAInE,oCAAoC;AACpC,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;IACxB,8EAA8E;IAC9E,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,WAAW,CAAA;KAAE,CAAC,CAAA;CAC5G;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWlE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,QAAqB,EAAE,EAAE,gBAAgB,+BA8ElF"}
|
|
@@ -53,9 +53,6 @@ export function FenceNotice({ t, onRetry, onAccept = acceptPair }) {
|
|
|
53
53
|
if (result.code === 'invalid') {
|
|
54
54
|
setErrorMsg(t('fence.unpaired.tokenInvalid'));
|
|
55
55
|
}
|
|
56
|
-
else if (result.code === 'used') {
|
|
57
|
-
setErrorMsg(t('fence.unpaired.tokenUsed'));
|
|
58
|
-
}
|
|
59
56
|
else {
|
|
60
57
|
setErrorMsg(t('fence.unpaired.tokenFailed'));
|
|
61
58
|
}
|
|
@@ -47,6 +47,10 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
|
|
|
47
47
|
* edits, so the footer would sit there permanently disabled.
|
|
48
48
|
*/
|
|
49
49
|
hideFooter?: boolean;
|
|
50
|
+
/** Render children even when the settings namespace is not exposed by the host. */
|
|
51
|
+
renderChildrenWhenNotExposed?: boolean;
|
|
52
|
+
/** Hide the 'not exposed' warning banner when the settings namespace is not exposed. */
|
|
53
|
+
hideNotExposedNotice?: boolean;
|
|
50
54
|
/** The plugin's controls. */
|
|
51
55
|
children: ReactNode;
|
|
52
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,EAAiF,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AACrH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAGnD,oGAAoG;AACpG,eAAO,MAAM,cAAc,kMAUjB,CAAA;AAEV,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzD,oEAAoE;AACpE,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,GAAG,WAAW,CAAA;AAE9E,wDAAwD;AACxD,MAAM,WAAW,uBAAuB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACnE,0CAA0C;IAC1C,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAA;IACnF,uCAAuC;IACvC,QAAQ,EAAE,IAAI,CAAA;IACd,4EAA4E;IAC5E,cAAc,EAAE,IAAI,CAAA;IACpB,mIAAmI;IACnI,eAAe,CAAC,EAAE,SAAS,CAAA;IAC3B,kFAAkF;IAClF,KAAK,EAAE,SAAS,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,uBAAuB,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"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"}
|
|
@@ -46,9 +46,10 @@ export function PluginSettingsCard(props) {
|
|
|
46
46
|
// official settings allowlist omits third-party namespaces): show a card
|
|
47
47
|
// that explains the gap instead of vanishing, so a missing card never
|
|
48
48
|
// reads as a missing plugin.
|
|
49
|
-
if (!state.exposed) {
|
|
49
|
+
if (!state.exposed && props.renderChildrenWhenNotExposed !== true) {
|
|
50
|
+
const showNotice = props.hideNotExposedNotice !== true;
|
|
50
51
|
return (_jsxs("li", { className: cardClass, children: [header, expanded
|
|
51
|
-
? (_jsx("div", { className: css.body, children: _jsx("p", { className: css.notExposed, role: "status", children: props.t('settings.notExposed') }) }))
|
|
52
|
+
? (_jsx("div", { className: css.body, children: showNotice ? _jsx("p", { className: css.notExposed, role: "status", children: props.t('settings.notExposed') }) : null }))
|
|
52
53
|
: null] }));
|
|
53
54
|
}
|
|
54
55
|
return (_jsxs("li", { className: cardClass, children: [header, expanded
|
|
@@ -1 +1 @@
|
|
|
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;AA2BD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,gBAAgB,+
|
|
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;AA2BD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,gBAAgB,+BA4MxD"}
|
|
@@ -173,17 +173,22 @@ export function RemoteEntry({ wide, t }) {
|
|
|
173
173
|
// Unmount safety: never leave the stream open.
|
|
174
174
|
useEffect(() => closeEventSource, [closeEventSource]);
|
|
175
175
|
const handleStop = useCallback(() => {
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
//
|
|
180
|
-
|
|
176
|
+
// The phase flip lands only once the server accepted the stop. A refused
|
|
177
|
+
// stop changes nothing server-side and the service emits no state change
|
|
178
|
+
// (notify dedupes identical snapshots), so an optimistic flip would stick
|
|
179
|
+
// and claim a revocation that never happened.
|
|
180
|
+
void stopPair().then(() => {
|
|
181
|
+
setState(previous => previous.kind === 'ready' ? { ...previous, phase: 'stopped', devices: [] } : previous);
|
|
182
|
+
}).catch(() => { });
|
|
181
183
|
}, []);
|
|
182
184
|
const handleRevoke = useCallback((deviceId) => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
// Same rule as stop: a refused unpair must leave the row, or the panel
|
|
186
|
+
// claims a still-live session is gone.
|
|
187
|
+
void revokePair(deviceId).then(() => {
|
|
188
|
+
setState(previous => previous.kind === 'ready'
|
|
189
|
+
? { ...previous, devices: previous.devices.filter(device => device.id !== deviceId) }
|
|
190
|
+
: previous);
|
|
191
|
+
}).catch(() => { });
|
|
187
192
|
}, []);
|
|
188
193
|
const handleRefresh = useCallback(() => {
|
|
189
194
|
void mint().then(setState);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemotePanel.d.ts","sourceRoot":"","sources":["../../../src/client/RemotePanel.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RemotePanel.d.ts","sourceRoot":"","sources":["../../../src/client/RemotePanel.tsx"],"names":[],"mappings":"AAYA,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,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAA;AAatB,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,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,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,uEAAuE;IACvE,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,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,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,OAAO,IAAI,IAAI,CAAA;IACf,MAAM,IAAI,IAAI,CAAA;IACd,SAAS,IAAI,IAAI,CAAA;IACjB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,WAAW,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,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,EAC1B,CAAC,EACD,KAAK,EACL,MAAM,EACN,WAAW,EACX,OAAO,EACP,MAAM,EACN,SAAS,EACT,MAAM,EACN,WAAW,EACX,aAAa,EACb,YAAY,EACZ,QAAQ,GACT,EAAE,gBAAgB,+BAyLlB"}
|
|
@@ -6,11 +6,20 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
6
6
|
* actions arrive through props from the entry's behavior component.
|
|
7
7
|
*/
|
|
8
8
|
import clsx from 'clsx';
|
|
9
|
+
import { memo } from 'react';
|
|
9
10
|
import { QRCodeSVG } from 'qrcode.react';
|
|
10
|
-
import {
|
|
11
|
+
import { IconCloseOutlineRegular, IconCopyOutlineRegular, IconRefreshOutlineRegular, IconStopFillRegular, } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
11
12
|
import { formatClock, formatLastSeen, } from "./pair-api.js";
|
|
12
13
|
import { deviceNameFromUserAgent } from "./device-name.js";
|
|
13
14
|
import css from './remote.module.css';
|
|
15
|
+
/**
|
|
16
|
+
* The QR symbol, memoized on the link: the panel re-renders on every SSE state
|
|
17
|
+
* frame (a paired phone heartbeats every 10s), and qrcode.react rebuilds the
|
|
18
|
+
* SVG path and element tree on each render.
|
|
19
|
+
*/
|
|
20
|
+
const PairQrCode = memo(function PairQrCode({ url, className }) {
|
|
21
|
+
return _jsx(QRCodeSVG, { value: url, size: 184, level: "M", marginSize: 1, className: className });
|
|
22
|
+
});
|
|
14
23
|
/** Badge text + tone per phase (ready states only). */
|
|
15
24
|
function statusOf(t, state) {
|
|
16
25
|
switch (state.phase) {
|
|
@@ -27,9 +36,9 @@ function statusOf(t, state) {
|
|
|
27
36
|
* @returns the panel element tree.
|
|
28
37
|
*/
|
|
29
38
|
export function RemotePanel({ t, state, copied, copiedToken, onClose, onStop, onRefresh, onCopy, onCopyToken, onPickAddress, onPickPublic, onRevoke, }) {
|
|
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(
|
|
39
|
+
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(IconCloseOutlineRegular, { 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(PairQrCode, { url: state.url, className: css.qr }) }), state.expired
|
|
31
40
|
? _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') }), _jsxs("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(
|
|
41
|
+
: _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') }), _jsxs("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(IconCopyOutlineRegular, { size: 14 }), copied ? t('action.copied') : t('action.copyLink')] })] }), state.token !== undefined && state.token !== '' && (_jsxs("div", { className: css.pairLinkRow, children: [_jsxs("div", { className: css.pairLinkText, children: [_jsx("span", { className: css.pairLinkLabel, children: t('pair.tokenLabel') }), _jsx("code", { className: css.link, title: state.token, children: state.token })] }), _jsxs("button", { type: "button", className: css.copyLink, onClick: () => onCopyToken ? onCopyToken(state.token) : onCopy(state.token), children: [_jsx(IconCopyOutlineRegular, { size: 14 }), copiedToken ? t('action.copiedToken') : t('action.copyToken')] })] }))] }), _jsxs("p", { className: css.oneTimeHint, children: [t('pair.oneTimeHint'), " ", t('pair.dockerHint')] }), 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
42
|
? t('tunnel.failed', { error: state.tunnel.error ?? t('tunnel.unknownError') })
|
|
34
|
-
: t('tunnel.starting') })), state.relay?.state === 'registering' && (_jsx("p", { className: css.tunnelNote, role: "status", children: t('relay.registering') })), state.relay?.state === 'failed' && (_jsx("p", { className: css.tunnelFailed, role: "status", children: t('relay.failed', { error: state.relay.error ?? t('tunnel.unknownError') }) })), (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(
|
|
43
|
+
: t('tunnel.starting') })), state.relay?.state === 'registering' && (_jsx("p", { className: css.tunnelNote, role: "status", children: t('relay.registering') })), state.relay?.state === 'failed' && (_jsx("p", { className: css.tunnelFailed, role: "status", children: t('relay.failed', { error: state.relay.error ?? t('tunnel.unknownError') }) })), (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(IconStopFillRegular, { size: 14 }), t('action.stop')] }), _jsxs("button", { type: "button", className: css.action, onClick: onRefresh, children: [_jsx(IconRefreshOutlineRegular, { 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
44
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The remote-control settings card: pairing security and device limits.
|
|
3
3
|
* Registers into the `web-ui.plugin.item` child slot the Web UI plugin group
|
|
4
|
-
* renders, bound to the `remote-web-ui`
|
|
4
|
+
* renders, bound to the `remote-web-ui` profile entry's configuration form.
|
|
5
5
|
*/
|
|
6
6
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
7
|
-
import type {
|
|
7
|
+
import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
8
8
|
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
9
9
|
import { type CardActions, type CardShell, type FieldState as CardFieldState } from './settings-form.ts';
|
|
10
10
|
/** The remote-control fields this card edits (the namespace's full schema). */
|
|
@@ -79,12 +79,12 @@ export interface RemoteSettingsCardFace extends CardActions {
|
|
|
79
79
|
remoteSettingsCard: SnapshotStore<RemoteSettingsCardState>;
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
-
/** Bridges the `remote-web-ui`
|
|
82
|
+
/** Bridges the `remote-web-ui` form onto the card's staged form. */
|
|
83
83
|
export declare class RemoteSettingsCardController {
|
|
84
84
|
private readonly form;
|
|
85
85
|
private readonly store;
|
|
86
|
-
/** @param scope - the
|
|
87
|
-
constructor(scope:
|
|
86
|
+
/** @param scope - the configuration form for the `remote-web-ui` entry. */
|
|
87
|
+
constructor(scope: ConfigForm<RemoteSettings>);
|
|
88
88
|
private projection;
|
|
89
89
|
/**
|
|
90
90
|
* Build the face the card's slot registration injects.
|
|
@@ -92,7 +92,7 @@ export declare class RemoteSettingsCardController {
|
|
|
92
92
|
*/
|
|
93
93
|
inject(): RemoteSettingsCardFace;
|
|
94
94
|
/**
|
|
95
|
-
* Release the card's
|
|
95
|
+
* Release the card's form subscription and bound stores; the slot
|
|
96
96
|
* disposer calls this on teardown.
|
|
97
97
|
*/
|
|
98
98
|
dispose(): void;
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,UAAU,EAAE,MAAM,4CAA4C,CAAA;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAElE,OAAO,EAA+D,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAIrK,+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;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;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,wDAAwD;IACxD,WAAW,EAAE,cAAc,CAAA;IAC3B,kCAAkC;IAClC,KAAK,EAAE,cAAc,CAAA;IACrB,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,oEAAoE;AACpE,qBAAa,4BAA4B;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0B;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwC;IAE9D,2EAA2E;gBAC/D,KAAK,EAAE,UAAU,CAAC,cAAc,CAAC;IAkB7C,OAAO,CAAC,UAAU;IAkBlB;;;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,+BA0JhE"}
|
|
@@ -2,17 +2,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* The remote-control settings card: pairing security and device limits.
|
|
4
4
|
* Registers into the `web-ui.plugin.item` child slot the Web UI plugin group
|
|
5
|
-
* renders, bound to the `remote-web-ui`
|
|
5
|
+
* renders, bound to the `remote-web-ui` profile entry's configuration form.
|
|
6
6
|
*/
|
|
7
7
|
import { useEffect, useState } from 'react';
|
|
8
8
|
import { PluginSettingsCard, ValueField, BooleanField } from "./PluginSettingsCard.js";
|
|
9
9
|
import { CardForm, booleanField, numberField, secretField, textField } from "./settings-form.js";
|
|
10
|
-
import { readLanBindStatus } from "./pair-api.js";
|
|
11
|
-
/** Bridges the `remote-web-ui`
|
|
10
|
+
import { LanBindStatusError, readLanBindStatus, shouldStopLanBindPoll } from "./pair-api.js";
|
|
11
|
+
/** Bridges the `remote-web-ui` form onto the card's staged form. */
|
|
12
12
|
export class RemoteSettingsCardController {
|
|
13
13
|
form;
|
|
14
14
|
store;
|
|
15
|
-
/** @param scope - the
|
|
15
|
+
/** @param scope - the configuration form for the `remote-web-ui` entry. */
|
|
16
16
|
constructor(scope) {
|
|
17
17
|
this.form = new CardForm(scope, [
|
|
18
18
|
booleanField('enabled'),
|
|
@@ -55,7 +55,7 @@ export class RemoteSettingsCardController {
|
|
|
55
55
|
return { hooks: { remoteSettingsCard: this.store }, ...this.form.actions() };
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
|
-
* Release the card's
|
|
58
|
+
* Release the card's form subscription and bound stores; the slot
|
|
59
59
|
* disposer calls this on teardown.
|
|
60
60
|
*/
|
|
61
61
|
dispose() {
|
|
@@ -77,7 +77,7 @@ export function RemoteSettingsCard(props) {
|
|
|
77
77
|
invalidLabel: t('settings.invalidNumber'),
|
|
78
78
|
disabled,
|
|
79
79
|
};
|
|
80
|
-
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(ValueField, { id: "settings-remote-tunnel-token", label: t('settings.tunnelToken'), hint: t('settings.tunnelTokenHint'), placeholder: "eyJ...", ...fieldProps, ...state.tunnelToken, onEdit: (text) => { props.edit('tunnelToken', text); }, onReset: () => { props.resetField('tunnelToken'); } }), _jsx(BooleanField, { id: "settings-remote-relay", label: t('settings.relay'), hint: t('settings.relayHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.relay, onEdit: (text) => { props.edit('relay', text); }, onReset: () => { props.resetField('relay'); } }), _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'); } })] }));
|
|
80
|
+
return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", defaultOpen: false, state: state, renderChildrenWhenNotExposed: true, hideNotExposedNotice: true, 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(ValueField, { id: "settings-remote-tunnel-token", label: t('settings.tunnelToken'), hint: t('settings.tunnelTokenHint'), placeholder: "eyJ...", ...fieldProps, ...state.tunnelToken, onEdit: (text) => { props.edit('tunnelToken', text); }, onReset: () => { props.resetField('tunnelToken'); } }), _jsx(BooleanField, { id: "settings-remote-relay", label: t('settings.relay'), hint: t('settings.relayHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.relay, onEdit: (text) => { props.edit('relay', text); }, onReset: () => { props.resetField('relay'); } }), _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'); } })] }));
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
83
|
* Live LAN-bind facts above the toggle: the managed block's host, the live
|
|
@@ -93,10 +93,16 @@ function LanBindStatus({ t }) {
|
|
|
93
93
|
void readLanBindStatus().then((value) => {
|
|
94
94
|
if (alive)
|
|
95
95
|
setFrame(value);
|
|
96
|
-
}).catch(() => {
|
|
96
|
+
}).catch((error) => {
|
|
97
|
+
// The endpoint is loopback-only: a 401/403 means this origin (a paired
|
|
98
|
+
// phone) can never read it, so the 10s poll stops instead of retrying a
|
|
99
|
+
// known refusal for as long as the card stays expanded.
|
|
100
|
+
if (error instanceof LanBindStatusError && shouldStopLanBindPoll(error.status))
|
|
101
|
+
window.clearInterval(timer);
|
|
102
|
+
});
|
|
97
103
|
};
|
|
98
|
-
read();
|
|
99
104
|
const timer = window.setInterval(read, 10_000);
|
|
105
|
+
read();
|
|
100
106
|
return () => {
|
|
101
107
|
alive = false;
|
|
102
108
|
window.clearInterval(timer);
|
|
@@ -9,7 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
9
9
|
*/
|
|
10
10
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
11
11
|
import { createPortal } from "react-dom";
|
|
12
|
-
import {
|
|
12
|
+
import { IconDownloadOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
13
13
|
import { fetchUpdateStatus, runUpdate, UpdateStatusError } from "./update-api.js";
|
|
14
14
|
import { UpdatePanel } from "./UpdatePanel.js";
|
|
15
15
|
import css from "./remote.module.css";
|
|
@@ -108,5 +108,5 @@ export function UpdateEntry({ wide, t }) {
|
|
|
108
108
|
};
|
|
109
109
|
}, [probeAvailability]);
|
|
110
110
|
const updateLabel = updateAvailable ? t("update.availableLabel") : t("update.label");
|
|
111
|
-
return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: css.trigger, "data-wide": wide ? (updateAvailable ? "wide" : undefined) : "rail", "data-update-available": updateAvailable ? "true" : undefined, "aria-label": updateLabel, "aria-expanded": open, title: updateLabel, onClick: openPanel, children: [_jsx(
|
|
111
|
+
return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: css.trigger, "data-wide": wide ? (updateAvailable ? "wide" : undefined) : "rail", "data-update-available": updateAvailable ? "true" : undefined, "aria-label": updateLabel, "aria-expanded": open, title: updateLabel, onClick: openPanel, children: [_jsx(IconDownloadOutlineRegular, { size: wide ? 16 : 18 }), updateAvailable && wide && (_jsx("span", { className: css.updateBadgeText, children: t("update.badge") }))] }), open && createPortal((_jsxs("div", { className: css.overlay, role: "presentation", children: [_jsx("div", { className: css.mask, "aria-hidden": "true", onClick: closePanel }), _jsx(UpdatePanel, { t: t, view: view, onClose: closePanel, onRecheck: () => { void check(); }, onStartUpdate: (status) => { void startUpdate(status); } })] })), document.body)] }));
|
|
112
112
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { IconCloseOutlineRegular, IconRefreshOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
3
|
import css from "./remote.module.css";
|
|
4
4
|
/** The anchor package name (aggregate first) for copy purposes. */
|
|
5
5
|
function anchorName(status) {
|
|
@@ -18,7 +18,7 @@ export function UpdatePanel({ t, view, onClose, onRecheck, onStartUpdate }) {
|
|
|
18
18
|
const status = view.kind === "result" || view.kind === "updating" ? view.status : undefined;
|
|
19
19
|
const title = view.kind === "done" && view.result.ok ? t("update.done") : t("update.title");
|
|
20
20
|
const subtitle = subtitleOf(t, view);
|
|
21
|
-
return (_jsxs("div", { className: css.panel, role: "dialog", "aria-modal": "true", "aria-label": title, children: [_jsxs("div", { className: css.header, children: [_jsxs("div", { className: css.heading, children: [_jsx("h2", { className: css.title, children: title }), subtitle !== undefined && _jsx("p", { className: css.subtitle, children: subtitle })] }), _jsx("button", { type: "button", className: css.close, "aria-label": t("update.close"), onClick: onClose, children: _jsx(
|
|
21
|
+
return (_jsxs("div", { className: css.panel, role: "dialog", "aria-modal": "true", "aria-label": title, children: [_jsxs("div", { className: css.header, children: [_jsxs("div", { className: css.heading, children: [_jsx("h2", { className: css.title, children: title }), subtitle !== undefined && _jsx("p", { className: css.subtitle, children: subtitle })] }), _jsx("button", { type: "button", className: css.close, "aria-label": t("update.close"), onClick: onClose, children: _jsx(IconCloseOutlineRegular, {}) })] }), view.kind === "checking" && _jsx("p", { className: css.updateStatus, children: t("update.checking") }), view.kind === "result" && status !== undefined && _jsx(ResultBody, { t: t, status: status }), view.kind === "result" && status !== undefined && status.mode === "npm" && status.outdated && status.error === undefined && (_jsx("div", { className: css.updateActions, children: _jsx("button", { type: "button", className: css.updateRetry, onClick: () => onStartUpdate(status), children: t("update.start") }) })), view.kind === "updating" && status !== undefined && (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.updating", { name: anchorName(status) ?? "", version: anchorLatest(status) ?? "" }) }), _jsx("p", { className: css.updateDetail, children: t("update.cooldownNotice") }), _jsx(PackageSummary, { t: t, status: status })] })), view.kind === "done" && _jsx(DoneBody, { t: t, result: view.result }), view.kind === "error" && (_jsxs("div", { children: [_jsx("p", { className: css.updateError, children: view.message }), view.detail !== undefined && _jsx("pre", { className: css.updateOutput, children: view.detail })] })), (view.kind === "done" || view.kind === "error") && (_jsx("div", { className: css.updateActions, children: _jsxs("button", { type: "button", className: css.updateRetry, onClick: onRecheck, children: [_jsx(IconRefreshOutlineRegular, {}), " ", t("update.retry")] }) }))] }));
|
|
22
22
|
}
|
|
23
23
|
/** The subtitle copy per view state (absent on plain results). */
|
|
24
24
|
function subtitleOf(t, view) {
|
|
@@ -51,8 +51,8 @@ async function runAccept(token, page) {
|
|
|
51
51
|
catch {
|
|
52
52
|
sessionStorage.setItem(PAIR_FAILED_MARKER, 'failed');
|
|
53
53
|
}
|
|
54
|
-
// Drop the token from the URL either way:
|
|
55
|
-
//
|
|
54
|
+
// Drop the token from the URL either way: it is spent for this tab (the
|
|
55
|
+
// session is established), and a failed one must not loop.
|
|
56
56
|
const url = new URL(page.href);
|
|
57
57
|
url.searchParams.delete('pair');
|
|
58
58
|
page.replaceState(`${url.pathname}${url.search}${url.hash}`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
3
3
|
import { type RemoteKey } from './locales.ts';
|
|
4
4
|
export type { RemoteEntryProps } from './RemoteEntry.tsx';
|
|
5
5
|
export type { PanelState, RemotePanelProps } from './RemotePanel.tsx';
|
|
@@ -21,9 +21,9 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
21
21
|
*/
|
|
22
22
|
/**
|
|
23
23
|
* The child slot the Web UI plugin group declares; this card registers
|
|
24
|
-
* into the group
|
|
25
|
-
* Spelled here with the same shape so this
|
|
26
|
-
* depending on the sibling UI package.
|
|
24
|
+
* into the group's list seat rather than the official
|
|
25
|
+
* bundle-configuration seat. Spelled here with the same shape so this
|
|
26
|
+
* package can register without depending on the sibling UI package.
|
|
27
27
|
*/
|
|
28
28
|
'web-ui.plugin.item': {
|
|
29
29
|
kind: 'list';
|
|
@@ -45,12 +45,20 @@ export interface SettingsPluginItemOwnerProps {
|
|
|
45
45
|
declare module '@deepseek-ai/cordis' {
|
|
46
46
|
interface Context {
|
|
47
47
|
/**
|
|
48
|
-
* Optional
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* Optional family settings binder provided by dsh-web-settings; absent
|
|
49
|
+
* when that group plugin is not installed, so callers fall back to the
|
|
50
|
+
* official `ctx.configForms` service.
|
|
51
|
+
*
|
|
52
|
+
* The 0.1.7 client addresses one form per active profile entry id and
|
|
53
|
+
* carries no package identity, so the family binder resolves a family
|
|
54
|
+
* settings namespace to the entry that owns it and hands back that entry's
|
|
55
|
+
* shared form; the official service is addressed by the entry id directly.
|
|
51
56
|
*/
|
|
52
57
|
webUiSettings?: {
|
|
53
|
-
bind<S>(spec:
|
|
58
|
+
bind<S>(spec: {
|
|
59
|
+
namespace: string;
|
|
60
|
+
decode?: (section: unknown) => S | undefined;
|
|
61
|
+
}): ConfigForm<S>;
|
|
54
62
|
};
|
|
55
63
|
}
|
|
56
64
|
}
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,UAAU,EAAe,MAAM,4CAA4C,CAAA;AAezF,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAoBrD,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;;;;;;;;;WASG;QACH,aAAa,CAAC,EAAE;YACd,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE;gBAAE,SAAS,EAAE,MAAM,CAAC;gBAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,CAAC,GAAG,SAAS,CAAA;aAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;SAClG,CAAA;KACF;CACF;AAyBD,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAA6D,CAAA;AAEhF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CA0R9C"}
|
|
@@ -14,7 +14,7 @@ import { PairFailedNotice } from "./PairFailedNotice.js";
|
|
|
14
14
|
import { RemoteSettingsCard, RemoteSettingsCardController } from "./RemoteSettingsCard.js";
|
|
15
15
|
import { en, zh } from "./locales.js";
|
|
16
16
|
import { PAIR_FAILED_MARKER, runPairBootFlow } from "./deep-link.js";
|
|
17
|
-
import { readPairGatePolicy, sendHeartbeat } from "./pair-api.js";
|
|
17
|
+
import { readPairGatePolicy, sendHeartbeat, shouldStopHeartbeat } from "./pair-api.js";
|
|
18
18
|
import { channelTransition, installRemoteChannel, isLoopbackHostname, remoteChannelRequired, REMOTE_CHANNEL_BOOT_GLOBAL, } from "./remote-channel.js";
|
|
19
19
|
import { FenceNotice } from "./FenceNotice.js";
|
|
20
20
|
import { reportDailyHeartbeat } from "./telemetry.js";
|
|
@@ -24,10 +24,24 @@ import { installPluginCard } from "./plugin-card-seat.js";
|
|
|
24
24
|
const NS = 'remote';
|
|
25
25
|
/** Settings namespace the remote-control card edits (the Host plugin registers it). */
|
|
26
26
|
const REMOTE_WEB_UI_NS = 'remote-web-ui';
|
|
27
|
+
const AGGREGATE_ENTRY_ID = 'web-ui-remote-web-ui';
|
|
28
|
+
const REMOTE_WEB_UI_ENTRY_IDS = [AGGREGATE_ENTRY_ID, 'ui-remote-web-ui', REMOTE_WEB_UI_NS];
|
|
29
|
+
function servedEntryId(forms) {
|
|
30
|
+
let served;
|
|
31
|
+
try {
|
|
32
|
+
served = forms.describe().getSnapshot().view?.namespaces.map(view => view.ns);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
served = undefined;
|
|
36
|
+
}
|
|
37
|
+
if (!served || served.length === 0)
|
|
38
|
+
return REMOTE_WEB_UI_NS;
|
|
39
|
+
return REMOTE_WEB_UI_ENTRY_IDS.find(id => served.includes(id)) ?? REMOTE_WEB_UI_NS;
|
|
40
|
+
}
|
|
27
41
|
/** Heartbeat cadence from a paired phone (presence + revocation liveness). */
|
|
28
42
|
const HEARTBEAT_INTERVAL_MS = 10_000;
|
|
29
43
|
/** Services required by this plugin. */
|
|
30
|
-
export const inject = ['slots', 'locale', 'connection', '
|
|
44
|
+
export const inject = ['slots', 'locale', 'connection', 'configForms', 'remote'];
|
|
31
45
|
/**
|
|
32
46
|
* Register the remote-control surface.
|
|
33
47
|
* @param ctx - client root context.
|
|
@@ -95,10 +109,17 @@ export function apply(ctx) {
|
|
|
95
109
|
// re-render them in the active locale.
|
|
96
110
|
if (adapt !== undefined)
|
|
97
111
|
adapt.translate = t;
|
|
98
|
-
|
|
99
|
-
|
|
112
|
+
// The settings form this card edits. The family group's binder resolves a
|
|
113
|
+
// family namespace to the profile entry that owns it and binds that entry's
|
|
114
|
+
// shared form (natively, or over its loopback bridge); without that group the
|
|
115
|
+
// namespace IS the owning entry id, so the official service is addressed
|
|
116
|
+
// directly.
|
|
117
|
+
const family = ctx.get('webUiSettings');
|
|
118
|
+
const settingsForm = family !== undefined && typeof family.bind === 'function'
|
|
119
|
+
? family.bind({ namespace: REMOTE_WEB_UI_NS })
|
|
120
|
+
: ctx.configForms.get(servedEntryId(ctx.configForms));
|
|
100
121
|
const enabled = () => {
|
|
101
|
-
const snapshot =
|
|
122
|
+
const snapshot = settingsForm.getSnapshot();
|
|
102
123
|
return snapshot.status === 'ready'
|
|
103
124
|
? snapshot.value?.enabled ?? true
|
|
104
125
|
: snapshot.status === 'unavailable';
|
|
@@ -113,7 +134,7 @@ export function apply(ctx) {
|
|
|
113
134
|
;
|
|
114
135
|
window.__dshRemoteAdapt?.setEnabled?.(enabled());
|
|
115
136
|
};
|
|
116
|
-
|
|
137
|
+
settingsForm.subscribe(syncAdaptEnabled);
|
|
117
138
|
syncAdaptEnabled();
|
|
118
139
|
// The replay closes a details panel restored before the wiring; the
|
|
119
140
|
// layout face throws while the root entry has not mounted yet (a real
|
|
@@ -143,7 +164,7 @@ export function apply(ctx) {
|
|
|
143
164
|
disposeEntry = undefined;
|
|
144
165
|
}
|
|
145
166
|
};
|
|
146
|
-
const unsubscribe =
|
|
167
|
+
const unsubscribe = settingsForm.subscribe(syncEntry);
|
|
147
168
|
syncEntry();
|
|
148
169
|
return () => {
|
|
149
170
|
unsubscribe();
|
|
@@ -152,12 +173,12 @@ export function apply(ctx) {
|
|
|
152
173
|
});
|
|
153
174
|
// Plugin configuration card: one staged form over the `remote-web-ui`
|
|
154
175
|
// settings namespace, contributed to whichever plugin-card seat the running
|
|
155
|
-
// host declares (the family group's list seat, or the official
|
|
156
|
-
// the plugin
|
|
157
|
-
//
|
|
158
|
-
const remoteSettings = new RemoteSettingsCardController(
|
|
176
|
+
// host declares (the family group's list seat, or the official
|
|
177
|
+
// bundle-configuration seat on the plugin manager page when dsh-web-settings
|
|
178
|
+
// is not installed).
|
|
179
|
+
const remoteSettings = new RemoteSettingsCardController(settingsForm);
|
|
159
180
|
installPluginCard(ctx, {
|
|
160
|
-
|
|
181
|
+
bundle: '@linxin666/dsh-remote-web-ui',
|
|
161
182
|
id: 'remote-web-ui',
|
|
162
183
|
order: 90,
|
|
163
184
|
locale: NS,
|
|
@@ -177,7 +198,15 @@ export function apply(ctx) {
|
|
|
177
198
|
runPairBootFlow(ctx, window.location.search);
|
|
178
199
|
if (loopback)
|
|
179
200
|
return () => { };
|
|
180
|
-
const timer = window.setInterval(() => {
|
|
201
|
+
const timer = window.setInterval(() => {
|
|
202
|
+
void sendHeartbeat().then((status) => {
|
|
203
|
+
// A revoked (401) or fenced-off (403) page can never be accepted
|
|
204
|
+
// again without re-pairing, so the 10 s wake source stops instead of
|
|
205
|
+
// polling a refusal for the lifetime of the tab.
|
|
206
|
+
if (shouldStopHeartbeat(status))
|
|
207
|
+
window.clearInterval(timer);
|
|
208
|
+
}).catch(() => { });
|
|
209
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
181
210
|
return () => { window.clearInterval(timer); };
|
|
182
211
|
}, 'remote-web-ui: pair flow + heartbeats');
|
|
183
212
|
}
|
|
@@ -186,7 +215,7 @@ export function apply(ctx) {
|
|
|
186
215
|
disposeRuntime = undefined;
|
|
187
216
|
}
|
|
188
217
|
};
|
|
189
|
-
|
|
218
|
+
settingsForm.subscribe(syncRuntime);
|
|
190
219
|
syncRuntime();
|
|
191
220
|
// Remote desktop channel: on a non-loopback origin (LAN address or public
|
|
192
221
|
// tunnel) the connection plugin's /api fence refuses this desktop Web GUI,
|
|
@@ -211,13 +240,13 @@ export function apply(ctx) {
|
|
|
211
240
|
fenceNotice = undefined;
|
|
212
241
|
};
|
|
213
242
|
const handleUnpaired = () => {
|
|
214
|
-
if (
|
|
243
|
+
if (settingsForm.getSnapshot().status !== 'ready' && hostPairingPolicy === undefined) {
|
|
215
244
|
unpairedWhilePolicyPending = true;
|
|
216
245
|
return;
|
|
217
246
|
}
|
|
218
247
|
showFenceNotice();
|
|
219
248
|
};
|
|
220
|
-
const channelActive = () => remoteChannelRequired(window.location.hostname,
|
|
249
|
+
const channelActive = () => remoteChannelRequired(window.location.hostname, settingsForm.getSnapshot(), hostPairingPolicy);
|
|
221
250
|
// The parse-time boot patch (issue #987), when the served index carried
|
|
222
251
|
// it: already installed before any boot entry ran, so adopting its seat
|
|
223
252
|
// beats patching a second time (which would double-rewrite onto
|
|
@@ -268,9 +297,9 @@ export function apply(ctx) {
|
|
|
268
297
|
bootSeat()?.restore();
|
|
269
298
|
}
|
|
270
299
|
};
|
|
271
|
-
|
|
300
|
+
settingsForm.subscribe(syncChannel);
|
|
272
301
|
syncChannel();
|
|
273
|
-
if (!isLoopbackHostname(window.location.hostname) &&
|
|
302
|
+
if (!isLoopbackHostname(window.location.hostname) && settingsForm.getSnapshot().status !== 'ready') {
|
|
274
303
|
void readPairGatePolicy().then((policy) => {
|
|
275
304
|
hostPairingPolicy = policy.requirePairingForLan;
|
|
276
305
|
syncChannel();
|
|
@@ -44,7 +44,6 @@ export declare const zh: {
|
|
|
44
44
|
'fence.unpaired.pairAction': string;
|
|
45
45
|
'fence.unpaired.pairing': string;
|
|
46
46
|
'fence.unpaired.tokenInvalid': string;
|
|
47
|
-
'fence.unpaired.tokenUsed': string;
|
|
48
47
|
'fence.unpaired.tokenFailed': string;
|
|
49
48
|
'fence.unpaired.footnote': string;
|
|
50
49
|
'posture.exposed': string;
|
|
@@ -213,7 +212,6 @@ export declare const en: {
|
|
|
213
212
|
'fence.unpaired.pairAction': string;
|
|
214
213
|
'fence.unpaired.pairing': string;
|
|
215
214
|
'fence.unpaired.tokenInvalid': string;
|
|
216
|
-
'fence.unpaired.tokenUsed': string;
|
|
217
215
|
'fence.unpaired.tokenFailed': string;
|
|
218
216
|
'fence.unpaired.footnote': string;
|
|
219
217
|
'posture.exposed': string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,mEAAmE;AACnE,eAAO,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoKmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoKsB,CAAA"}
|