dsh-zen-remote 2.3.10 → 2.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/CHANGELOG.md +44 -72
- package/README.md +94 -84
- package/README.zh-CN.md +90 -81
- package/bin/zen-remote.mjs +5 -0
- package/docs/interface.md +6 -6
- package/docs/remote-access.en.md +6 -4
- package/docs/remote-access.md +6 -4
- package/dsh-push.mjs +96 -10
- package/lib/adapters/dsh-better-sidebar/index.js +7 -3
- package/lib/adapters/dsh-better-sidebar/index.js.map +1 -1
- package/lib/admin-routes.js +10 -2
- package/lib/admin-routes.js.map +1 -1
- package/lib/cli/args.js +251 -0
- package/lib/cli/args.js.map +1 -0
- package/lib/cli/bootstrap.js +449 -0
- package/lib/cli/bootstrap.js.map +1 -0
- package/lib/cli/command-setup.js +484 -0
- package/lib/cli/command-setup.js.map +1 -0
- package/lib/cli/commands-client.js +359 -0
- package/lib/cli/commands-client.js.map +1 -0
- package/lib/cli/commands-host.js +1108 -0
- package/lib/cli/commands-host.js.map +1 -0
- package/lib/cli/commands-status.js +279 -0
- package/lib/cli/commands-status.js.map +1 -0
- package/lib/cli/config-writer.js +334 -0
- package/lib/cli/config-writer.js.map +1 -0
- package/lib/cli/context.js +10 -0
- package/lib/cli/context.js.map +1 -0
- package/lib/cli/i18n.js +620 -0
- package/lib/cli/i18n.js.map +1 -0
- package/lib/cli/ipc-client.js +160 -0
- package/lib/cli/ipc-client.js.map +1 -0
- package/lib/cli/ipc-server.js +464 -0
- package/lib/cli/ipc-server.js.map +1 -0
- package/lib/cli/main.js +245 -0
- package/lib/cli/main.js.map +1 -0
- package/lib/cli/qr-terminal.js +48 -0
- package/lib/cli/qr-terminal.js.map +1 -0
- package/lib/client-data/qr.js +286 -0
- package/lib/client-data/qr.js.map +1 -0
- package/lib/client-routes.js +308 -25
- package/lib/client-routes.js.map +1 -1
- package/lib/client.js +1013 -229
- package/lib/config.js +50 -12
- package/lib/config.js.map +1 -1
- package/lib/debug-report.js +28 -1
- package/lib/debug-report.js.map +1 -1
- package/lib/fetch-route-intercept.js +334 -26
- package/lib/fetch-route-intercept.js.map +1 -1
- package/lib/index.js +573 -366
- package/lib/index.js.map +1 -1
- package/lib/intercept.js +1833 -280
- package/lib/intercept.js.map +1 -1
- package/lib/lan-gate-server.cjs +36 -10
- package/lib/merge-streams.js +103 -11
- package/lib/merge-streams.js.map +1 -1
- package/lib/plugin-route-intercept.js +93 -63
- package/lib/plugin-route-intercept.js.map +1 -1
- package/lib/relay-access.js +8 -1
- package/lib/relay-access.js.map +1 -1
- package/lib/relay-client.js +133 -4
- package/lib/relay-client.js.map +1 -1
- package/lib/relay-pool.js +275 -0
- package/lib/relay-pool.js.map +1 -0
- package/lib/relay-server.js +269 -17
- package/lib/relay-server.js.map +1 -1
- package/lib/restart-fields.js +6 -4
- package/lib/restart-fields.js.map +1 -1
- package/lib/share-store.js +65 -27
- package/lib/share-store.js.map +1 -1
- package/lib/state-store.js +400 -0
- package/lib/state-store.js.map +1 -0
- package/lib/types/adapters/dsh-better-sidebar/index.d.ts +3 -2
- package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -1
- package/lib/types/admin-routes.d.ts.map +1 -1
- package/lib/types/cli/args.d.ts +78 -0
- package/lib/types/cli/args.d.ts.map +1 -0
- package/lib/types/cli/bootstrap.d.ts +170 -0
- package/lib/types/cli/bootstrap.d.ts.map +1 -0
- package/lib/types/cli/command-setup.d.ts +72 -0
- package/lib/types/cli/command-setup.d.ts.map +1 -0
- package/lib/types/cli/commands-client.d.ts +36 -0
- package/lib/types/cli/commands-client.d.ts.map +1 -0
- package/lib/types/cli/commands-host.d.ts +82 -0
- package/lib/types/cli/commands-host.d.ts.map +1 -0
- package/lib/types/cli/commands-status.d.ts +46 -0
- package/lib/types/cli/commands-status.d.ts.map +1 -0
- package/lib/types/cli/config-writer.d.ts +92 -0
- package/lib/types/cli/config-writer.d.ts.map +1 -0
- package/lib/types/cli/context.d.ts +39 -0
- package/lib/types/cli/context.d.ts.map +1 -0
- package/lib/types/cli/i18n.d.ts +32 -0
- package/lib/types/cli/i18n.d.ts.map +1 -0
- package/lib/types/cli/ipc-client.d.ts +83 -0
- package/lib/types/cli/ipc-client.d.ts.map +1 -0
- package/lib/types/cli/ipc-server.d.ts +113 -0
- package/lib/types/cli/ipc-server.d.ts.map +1 -0
- package/lib/types/cli/main.d.ts +57 -0
- package/lib/types/cli/main.d.ts.map +1 -0
- package/lib/types/cli/qr-terminal.d.ts +17 -0
- package/lib/types/cli/qr-terminal.d.ts.map +1 -0
- package/lib/types/client/RemoteStatusIcon.d.ts.map +1 -1
- package/lib/types/client/current-session-report.d.ts +4 -0
- package/lib/types/client/current-session-report.d.ts.map +1 -1
- package/lib/types/client/effects/model-group-side.d.ts.map +1 -1
- package/lib/types/client/effects/remote-session.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +47 -3
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/settings/SettingsSection.d.ts.map +1 -1
- package/lib/types/client/settings/settings-css.d.ts +1 -1
- package/lib/types/client/settings/settings-css.d.ts.map +1 -1
- package/lib/types/client/styles/remote-session.css.d.ts +1 -1
- package/lib/types/client/styles/remote-session.css.d.ts.map +1 -1
- package/lib/types/client/wizard/GatewayWizard.d.ts.map +1 -1
- package/lib/types/client/wizard/cards/ProxyCard.d.ts.map +1 -1
- package/lib/types/client/wizard/wizard-css.d.ts +1 -1
- package/lib/types/client/wizard/wizard-css.d.ts.map +1 -1
- package/lib/types/client-data/model-group-side.d.ts +23 -2
- package/lib/types/client-data/model-group-side.d.ts.map +1 -1
- package/lib/types/client-data/remote-status.d.ts +47 -1
- package/lib/types/client-data/remote-status.d.ts.map +1 -1
- package/lib/types/client-data/settings-form.d.ts +85 -1
- package/lib/types/client-data/settings-form.d.ts.map +1 -1
- package/lib/types/client-data/wizard.d.ts +56 -1
- package/lib/types/client-data/wizard.d.ts.map +1 -1
- package/lib/types/client-routes.d.ts +25 -1
- package/lib/types/client-routes.d.ts.map +1 -1
- package/lib/types/config.d.ts +40 -14
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/debug-report.d.ts.map +1 -1
- package/lib/types/fetch-route-intercept.d.ts +66 -8
- package/lib/types/fetch-route-intercept.d.ts.map +1 -1
- package/lib/types/index.d.ts +9 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/intercept.d.ts +72 -10
- package/lib/types/intercept.d.ts.map +1 -1
- package/lib/types/merge-streams.d.ts +79 -1
- package/lib/types/merge-streams.d.ts.map +1 -1
- package/lib/types/plugin-route-intercept.d.ts +25 -9
- package/lib/types/plugin-route-intercept.d.ts.map +1 -1
- package/lib/types/relay-access.d.ts +3 -1
- package/lib/types/relay-access.d.ts.map +1 -1
- package/lib/types/relay-client.d.ts +23 -2
- package/lib/types/relay-client.d.ts.map +1 -1
- package/lib/types/relay-pool.d.ts +148 -0
- package/lib/types/relay-pool.d.ts.map +1 -0
- package/lib/types/relay-server.d.ts +30 -3
- package/lib/types/relay-server.d.ts.map +1 -1
- package/lib/types/restart-fields.d.ts +5 -5
- package/lib/types/restart-fields.d.ts.map +1 -1
- package/lib/types/share-store.d.ts.map +1 -1
- package/lib/types/state-store.d.ts +179 -0
- package/lib/types/state-store.d.ts.map +1 -0
- package/lib/types/upload-limits.d.ts +15 -0
- package/lib/types/upload-limits.d.ts.map +1 -1
- package/lib/types/virtual-id.d.ts +21 -0
- package/lib/types/virtual-id.d.ts.map +1 -1
- package/lib/types/wizard/probe.d.ts.map +1 -1
- package/lib/types/wizard/proxy-snippets.d.ts +3 -0
- package/lib/types/wizard/proxy-snippets.d.ts.map +1 -1
- package/lib/types/wizard/routes.d.ts +1 -0
- package/lib/types/wizard/routes.d.ts.map +1 -1
- package/lib/types/wizard/status.d.ts +9 -0
- package/lib/types/wizard/status.d.ts.map +1 -1
- package/lib/types/wizard/verify.d.ts +3 -0
- package/lib/types/wizard/verify.d.ts.map +1 -1
- package/lib/upload-limits.js +15 -0
- package/lib/upload-limits.js.map +1 -1
- package/lib/virtual-id.js +60 -0
- package/lib/virtual-id.js.map +1 -1
- package/lib/wizard/probe.js +46 -1
- package/lib/wizard/probe.js.map +1 -1
- package/lib/wizard/proxy-snippets.js +14 -1
- package/lib/wizard/proxy-snippets.js.map +1 -1
- package/lib/wizard/routes.js +23 -4
- package/lib/wizard/routes.js.map +1 -1
- package/lib/wizard/status.js +21 -0
- package/lib/wizard/status.js.map +1 -1
- package/lib/wizard/verify.js +31 -8
- package/lib/wizard/verify.js.map +1 -1
- package/package.json +8 -4
- package/pwa/icons/badge-96.png +0 -0
- package/pwa/icons/badge.svg +1 -0
- package/pwa/manifest.json +2 -1
- package/pwa/sw.js +3 -1
- package/scripts/check-debug-report.mjs +41 -1
- package/scripts/check-settings-form.mjs +14 -3
- package/scripts/check-settings-view.mjs +247 -0
- package/scripts/check-wizard-ui.mjs +37 -0
- package/src/adapters/dsh-better-sidebar/index.ts +7 -3
- package/src/admin-routes.ts +10 -2
- package/src/cli/args.ts +289 -0
- package/src/cli/bootstrap.ts +569 -0
- package/src/cli/command-setup.ts +540 -0
- package/src/cli/commands-client.ts +369 -0
- package/src/cli/commands-host.ts +1127 -0
- package/src/cli/commands-status.ts +296 -0
- package/src/cli/config-writer.ts +354 -0
- package/src/cli/context.ts +38 -0
- package/src/cli/i18n.ts +645 -0
- package/src/cli/ipc-client.ts +216 -0
- package/src/cli/ipc-server.ts +531 -0
- package/src/cli/main.ts +285 -0
- package/src/cli/qr-terminal.ts +49 -0
- package/src/client/RemoteStatusIcon.tsx +4 -1
- package/src/client/current-session-report.ts +28 -4
- package/src/client/effects/model-group-side.ts +19 -5
- package/src/client/effects/remote-session.ts +26 -6
- package/src/client/locales.ts +96 -6
- package/src/client/settings/SettingsSection.tsx +287 -92
- package/src/client/settings/settings-css.ts +18 -0
- package/src/client/styles/remote-session.css.ts +15 -1
- package/src/client/wizard/GatewayWizard.tsx +470 -109
- package/src/client/wizard/cards/ProxyCard.tsx +2 -1
- package/src/client/wizard/wizard-css.ts +23 -0
- package/src/client-data/model-group-side.ts +37 -10
- package/src/client-data/remote-status.ts +112 -17
- package/src/client-data/settings-form.ts +164 -8
- package/src/client-data/wizard.ts +112 -2
- package/src/client-routes.ts +334 -26
- package/src/config.ts +62 -16
- package/src/debug-report.ts +27 -1
- package/src/fetch-route-intercept.ts +381 -32
- package/src/index.ts +575 -370
- package/src/intercept.ts +1866 -286
- package/src/merge-streams.ts +155 -10
- package/src/plugin-route-intercept.ts +120 -71
- package/src/relay-access.ts +14 -3
- package/src/relay-client.ts +163 -5
- package/src/relay-pool.ts +387 -0
- package/src/relay-server.ts +312 -16
- package/src/restart-fields.ts +6 -4
- package/src/share-store.ts +59 -26
- package/src/state-store.ts +428 -0
- package/src/upload-limits.ts +16 -0
- package/src/virtual-id.ts +57 -0
- package/src/wizard/probe.ts +54 -2
- package/src/wizard/proxy-snippets.ts +17 -2
- package/src/wizard/routes.ts +24 -5
- package/src/wizard/status.ts +28 -0
- package/src/wizard/verify.ts +33 -11
- package/AGENTS.md +0 -185
- package/docs/adr/0001-plugin-bridge-forwards-plugin-routes.md +0 -15
- package/docs/adr/0002-relay-emulates-websocket.md +0 -8
package/lib/client.js
CHANGED
|
@@ -1947,10 +1947,10 @@ exports.RESTART_FIELDS = void 0;
|
|
|
1947
1947
|
* effect (T17): `role` chooses which halves load; `port` / `host` /
|
|
1948
1948
|
* `targetPort` / `rateLimit` / `trustedProxies` become the gateway child's
|
|
1949
1949
|
* runtime shape; `vapidSubject` / `lang` / `pushEvents` / `pushDebounceMs` /
|
|
1950
|
-
* `pushSummary` / `pushTurnEnd` / `pushTool`
|
|
1951
|
-
* values. Everything else
|
|
1952
|
-
* serverUrl, deviceToken, and
|
|
1953
|
-
* and needs no reload.
|
|
1950
|
+
* `pushSummary` / `pushTurnEnd` / `pushTool` / `pushEnabled` /
|
|
1951
|
+
* `pushSessionName` are the push leg's startup values. Everything else
|
|
1952
|
+
* (serverName, idleHours, autoShareNewSessions, serverUrl, deviceToken, and
|
|
1953
|
+
* the interface-half knobs) is read live per use and needs no reload.
|
|
1954
1954
|
*
|
|
1955
1955
|
* Single source shared by both halves: the host's restart watcher
|
|
1956
1956
|
* (src/restart-watcher.ts) fingerprints exactly these fields, and the
|
|
@@ -1974,6 +1974,8 @@ exports.RESTART_FIELDS = [
|
|
|
1974
1974
|
'pushSummary',
|
|
1975
1975
|
'pushTurnEnd',
|
|
1976
1976
|
'pushTool',
|
|
1977
|
+
'pushEnabled',
|
|
1978
|
+
'pushSessionName',
|
|
1977
1979
|
];
|
|
1978
1980
|
};
|
|
1979
1981
|
__modules["client-data/plugin-bridge.js"] = function (require, module, exports) {
|
|
@@ -2122,10 +2124,11 @@ __modules["client-data/settings-form.js"] = function (require, module, exports)
|
|
|
2122
2124
|
* (devices / pairing) plus the T14 envelope (ok / gateway / config / viaGateway).
|
|
2123
2125
|
*/
|
|
2124
2126
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2125
|
-
exports.ZenRemoteSettingsForm = exports.SETTINGS_FIELDS = exports.IDLE_HOURS_MAX = exports.SERVER_NAME_MAX = exports.RESTART_FIELDS = exports.PAIRING_CODE_LENGTH = exports.PAIRING_CODE_ALPHABET = exports.DEVICE_TOKEN_FIELD = exports.CLIENT_CONFIG_ROUTE = exports.CLIENT_DEBUG_REPORT_ROUTE = exports.CLIENT_RECONNECT_ROUTE = exports.CLIENT_STATUS_ROUTE = exports.CLIENT_CLAIM_ROUTE = exports.ADMIN_PLUGIN_BRIDGE_ROUTE = exports.ADMIN_PUSH_TEST_ROUTE = exports.ADMIN_ACTION_ROUTE = exports.ADMIN_PAIR_ROUTE = exports.ADMIN_STATUS_ROUTE = void 0;
|
|
2127
|
+
exports.ZenRemoteSettingsForm = exports.SETTINGS_FIELDS = exports.IDLE_HOURS_MAX = exports.SERVER_NAME_MAX = exports.RESTART_FIELDS = exports.PAIRING_CODE_LENGTH = exports.PAIRING_CODE_ALPHABET = exports.DEVICE_TOKEN_FIELD = exports.CLIENT_CONFIG_ROUTE = exports.MAX_PAIRED_SERVERS = exports.CLIENT_DEBUG_REPORT_ROUTE = exports.CLIENT_UNPAIR_ROUTE = exports.CLIENT_RECONNECT_ROUTE = exports.CLIENT_STATUS_ROUTE = exports.CLIENT_CLAIM_ROUTE = exports.ADMIN_PLUGIN_BRIDGE_ROUTE = exports.ADMIN_PUSH_TEST_ROUTE = exports.ADMIN_ACTION_ROUTE = exports.ADMIN_PAIR_ROUTE = exports.ADMIN_STATUS_ROUTE = void 0;
|
|
2126
2128
|
exports.deriveClientStatusView = deriveClientStatusView;
|
|
2127
2129
|
exports.clientStatusLineOf = clientStatusLineOf;
|
|
2128
2130
|
exports.normalizePairingCode = normalizePairingCode;
|
|
2131
|
+
exports.canSubmitClientPairing = canSubmitClientPairing;
|
|
2129
2132
|
exports.shouldFollowServerDeviceName = shouldFollowServerDeviceName;
|
|
2130
2133
|
exports.shouldReloadAfterClientActivation = shouldReloadAfterClientActivation;
|
|
2131
2134
|
exports.awaitClientStatusReady = awaitClientStatusReady;
|
|
@@ -2157,9 +2160,20 @@ exports.CLIENT_CLAIM_ROUTE = '/_dsh/zen-remote/client/claim';
|
|
|
2157
2160
|
exports.CLIENT_STATUS_ROUTE = '/_dsh/zen-remote/client/status';
|
|
2158
2161
|
/** T43: one immediate reconnect — answered 409 unless the client is offline. */
|
|
2159
2162
|
exports.CLIENT_RECONNECT_ROUTE = '/_dsh/zen-remote/client/reconnect';
|
|
2163
|
+
/** MS-6: POST `{ entryId }` (or `{ all: true }`) removes one / all paired
|
|
2164
|
+
* servers from the pool (host half: src/client-routes.ts, mirrored here
|
|
2165
|
+
* because the browser half cannot import the host module). */
|
|
2166
|
+
exports.CLIENT_UNPAIR_ROUTE = '/_dsh/zen-remote/client/unpair';
|
|
2160
2167
|
/** CU-4: the redacted diagnostic report (GET, both roles, machine-local only
|
|
2161
2168
|
* — a gateway-forwarded fetch is refused server-side). */
|
|
2162
2169
|
exports.CLIENT_DEBUG_REPORT_ROUTE = '/_dsh/zen-remote/client/debug-report';
|
|
2170
|
+
/**
|
|
2171
|
+
* MS-6: the paired-server pool's cap — the browser mirror of
|
|
2172
|
+
* src/state-store.ts's constant of the same name (the host module cannot be
|
|
2173
|
+
* imported here). The settings page and the wizard read it for the
|
|
2174
|
+
* `已配对 N / 8 台` counters and the limit gating.
|
|
2175
|
+
*/
|
|
2176
|
+
exports.MAX_PAIRED_SERVERS = 8;
|
|
2163
2177
|
/** The lightweight client-facing config route (host half, both roles): the
|
|
2164
2178
|
* settings page's FALLBACK role probe (T17) — it registers wherever a
|
|
2165
2179
|
* webServer exists, and since T17 its body carries the effective `role`
|
|
@@ -2177,6 +2191,40 @@ const WIRE_STATE_ALIASES = {
|
|
|
2177
2191
|
online: 'connected',
|
|
2178
2192
|
offline: 'unreachable',
|
|
2179
2193
|
};
|
|
2194
|
+
/** The view `state` for one raw wire word: the vocabulary verbatim, the T55
|
|
2195
|
+
* aliases mapped, anything else the conservative `unpaired`. Shared by the
|
|
2196
|
+
* top level and every `servers[]` row. */
|
|
2197
|
+
function clientStateOf(raw) {
|
|
2198
|
+
const word = typeof raw === 'string' ? raw : '';
|
|
2199
|
+
return CLIENT_STATES.includes(word)
|
|
2200
|
+
? word
|
|
2201
|
+
: WIRE_STATE_ALIASES[word] ?? 'unpaired';
|
|
2202
|
+
}
|
|
2203
|
+
/** The finite-number gate every epoch-ms wire field passes. */
|
|
2204
|
+
function finiteOrUndefined(value) {
|
|
2205
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
2206
|
+
}
|
|
2207
|
+
/** MS-6: one `servers[]` row into its view. Tolerant like the top level —
|
|
2208
|
+
* a junk field degrades, a row without the route's stable `entryId` key
|
|
2209
|
+
* drops whole. */
|
|
2210
|
+
function deriveClientServerView(value) {
|
|
2211
|
+
const record = asRecord(value);
|
|
2212
|
+
const entryId = asStringSet(record.entryId);
|
|
2213
|
+
if (entryId === '')
|
|
2214
|
+
return undefined;
|
|
2215
|
+
return {
|
|
2216
|
+
entryId,
|
|
2217
|
+
serverId: asStringSet(record.serverId),
|
|
2218
|
+
serverUrl: asStringSet(record.serverUrl),
|
|
2219
|
+
serverName: asStringSet(record.serverName),
|
|
2220
|
+
displayName: asStringSet(record.displayName),
|
|
2221
|
+
state: clientStateOf(record.state),
|
|
2222
|
+
nextRetryAt: finiteOrUndefined(record.nextRetryAt),
|
|
2223
|
+
lastError: asStringSet(record.lastError),
|
|
2224
|
+
compat: deriveCompatView(record.compat),
|
|
2225
|
+
pluginBridge: (0, plugin_bridge_ts_1.derivePluginBridgeStatuses)(record.pluginBridge),
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2180
2228
|
/** At most `cap` strings out of an array-shaped value; anything else is none. */
|
|
2181
2229
|
function stringListOf(value, cap) {
|
|
2182
2230
|
if (!Array.isArray(value))
|
|
@@ -2264,18 +2312,20 @@ function deriveClientStatusView(body) {
|
|
|
2264
2312
|
// diagnostics below it showed live relay calls. `offline` rides the same
|
|
2265
2313
|
// mapping to the probe word for it; anything else unknown still degrades
|
|
2266
2314
|
// to `unpaired`.
|
|
2267
|
-
const
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
const
|
|
2272
|
-
|
|
2273
|
-
|
|
2315
|
+
const state = clientStateOf(safe.state);
|
|
2316
|
+
// MS-6: the pool list rides as `servers[]` (one row per paired entry); a
|
|
2317
|
+
// legacy single-server body answers the empty list and the pre-MS-6 page
|
|
2318
|
+
// shape otherwise.
|
|
2319
|
+
const servers = (Array.isArray(safe.servers) ? safe.servers : [])
|
|
2320
|
+
.map(deriveClientServerView)
|
|
2321
|
+
.filter((row) => row !== undefined);
|
|
2322
|
+
const maxServersRaw = finiteOrUndefined(safe.maxServers);
|
|
2323
|
+
const maxServers = maxServersRaw !== undefined && maxServersRaw >= 1 ? maxServersRaw : exports.MAX_PAIRED_SERVERS;
|
|
2274
2324
|
return {
|
|
2275
2325
|
state,
|
|
2276
2326
|
serverUrl: typeof safe.serverUrl === 'string' ? safe.serverUrl : '',
|
|
2277
2327
|
serverName: typeof safe.serverName === 'string' ? safe.serverName : '',
|
|
2278
|
-
nextRetryAt,
|
|
2328
|
+
nextRetryAt: finiteOrUndefined(safe.nextRetryAt),
|
|
2279
2329
|
lastError: typeof safe.lastError === 'string' ? safe.lastError : '',
|
|
2280
2330
|
deviceName: typeof safe.deviceName === 'string' ? safe.deviceName : '',
|
|
2281
2331
|
intercept: deriveInterceptView(safe.intercept),
|
|
@@ -2285,6 +2335,9 @@ function deriveClientStatusView(body) {
|
|
|
2285
2335
|
uploadProbe: safe.serviceProbe !== null && typeof safe.serviceProbe === 'object'
|
|
2286
2336
|
? Object.freeze({ ...safe.serviceProbe })
|
|
2287
2337
|
: undefined,
|
|
2338
|
+
servers,
|
|
2339
|
+
maxServers,
|
|
2340
|
+
limitReached: servers.length >= maxServers,
|
|
2288
2341
|
};
|
|
2289
2342
|
}
|
|
2290
2343
|
function clientStatusLineOf(view, now) {
|
|
@@ -2319,6 +2372,39 @@ function normalizePairingCode(input) {
|
|
|
2319
2372
|
.replace(/[^ABCDEFGHJKMNPQRSTUVWXYZ23456789]/g, '')
|
|
2320
2373
|
.slice(0, exports.PAIRING_CODE_LENGTH);
|
|
2321
2374
|
}
|
|
2375
|
+
/**
|
|
2376
|
+
* The address comparison the overwrite judgment makes (MS-6): trim padding,
|
|
2377
|
+
* drop trailing slashes. Light on purpose — the backend re-normalizes the
|
|
2378
|
+
* address authoritatively on claim; here it only decides whether the typed
|
|
2379
|
+
* address NAMES an already-paired server, and `http://x.local:3088/` must
|
|
2380
|
+
* count as that server while `http://x.local:3089` must not.
|
|
2381
|
+
*/
|
|
2382
|
+
function normalizedPairAddress(url) {
|
|
2383
|
+
return url.trim().replace(/\/+$/u, '');
|
|
2384
|
+
}
|
|
2385
|
+
/**
|
|
2386
|
+
* The 配对 button's whole gate, as one pure truth table (MS-6): the claim
|
|
2387
|
+
* needs an address and a COMPLETE 8-character pairing code (the input box
|
|
2388
|
+
* normalizes as the user types; this judge is the same bar the route's code
|
|
2389
|
+
* grammar enforces), a quiet page (`busy`), and a valid device-name draft
|
|
2390
|
+
* (`deviceNameInvalid`, T60 — a red-boxed name would silently pair under the
|
|
2391
|
+
* fallback copy). The pool cap blocks only NEW addresses: a full pool still
|
|
2392
|
+
* accepts re-pairing an existing one in place, which is how a user whose
|
|
2393
|
+
* token was revoked at 8/8 gets back in.
|
|
2394
|
+
*/
|
|
2395
|
+
function canSubmitClientPairing(input) {
|
|
2396
|
+
const maxServers = input.maxServers ?? exports.MAX_PAIRED_SERVERS;
|
|
2397
|
+
const address = normalizedPairAddress(input.pairUrl);
|
|
2398
|
+
const isOverwrite = address !== ''
|
|
2399
|
+
&& input.servers.some((server) => normalizedPairAddress(server.serverUrl) === address);
|
|
2400
|
+
const blockedByLimit = !isOverwrite && input.servers.length >= maxServers;
|
|
2401
|
+
const allowed = address !== ''
|
|
2402
|
+
&& normalizePairingCode(input.pairCode).length === exports.PAIRING_CODE_LENGTH
|
|
2403
|
+
&& !blockedByLimit
|
|
2404
|
+
&& input.busy !== true
|
|
2405
|
+
&& input.deviceNameInvalid !== true;
|
|
2406
|
+
return { allowed, isOverwrite, blockedByLimit };
|
|
2407
|
+
}
|
|
2322
2408
|
/**
|
|
2323
2409
|
* Whether the settings page may follow the SERVER's record of this device's
|
|
2324
2410
|
* name into the row (T59): the body carried a name, it differs from the row
|
|
@@ -2580,6 +2666,8 @@ exports.SETTINGS_FIELDS = [
|
|
|
2580
2666
|
textField('trustedProxies'),
|
|
2581
2667
|
intField('rateLimit', 1, Number.MAX_SAFE_INTEGER),
|
|
2582
2668
|
textField('vapidSubject'),
|
|
2669
|
+
boolField('pushEnabled'),
|
|
2670
|
+
boolField('pushSessionName'),
|
|
2583
2671
|
boolField('pushSummary'),
|
|
2584
2672
|
boolField('pushTurnEnd'),
|
|
2585
2673
|
boolField('pushTool'),
|
|
@@ -3251,6 +3339,10 @@ exports.zh = {
|
|
|
3251
3339
|
'settings.invalidPort': '需要 1–65535 的整数',
|
|
3252
3340
|
'settings.invalidPositiveInt': '需要正整数',
|
|
3253
3341
|
'settings.pushTitle': '推送',
|
|
3342
|
+
'settings.fieldPushEnabled': '启用会话通知',
|
|
3343
|
+
'settings.fieldPushEnabledHint': '总开关:关闭后停止所有自动推送,且不注册 push_notify 工具与系统提示词。',
|
|
3344
|
+
'settings.fieldPushSessionName': '推送来源会话名',
|
|
3345
|
+
'settings.fieldPushSessionNameHint': '在推送正文第一行显示「来源会话:<会话名>」。',
|
|
3254
3346
|
'settings.fieldPushSummary': '推送内容摘要',
|
|
3255
3347
|
'settings.fieldPushSummaryHint': '在推送正文中包含回合的最终文本或待回答的问题。',
|
|
3256
3348
|
'settings.fieldPushTurnEnd': '回合结束推送',
|
|
@@ -3328,7 +3420,7 @@ exports.zh = {
|
|
|
3328
3420
|
'settings.refreshFailed': '状态刷新失败,当前显示的是上一次成功的数据',
|
|
3329
3421
|
'settings.resetPreview': '保存后恢复为下一层的值',
|
|
3330
3422
|
// --- 插件设置页(T16),子客户端区块 ---
|
|
3331
|
-
'settings.client.connectTitle': '
|
|
3423
|
+
'settings.client.connectTitle': '连接主服务端',
|
|
3332
3424
|
'settings.client.serverUrl': '服务端地址',
|
|
3333
3425
|
'settings.client.serverUrlHint': '服务端网关地址,例如 http://192.168.1.10:3088 或 https://dsh.example.com。',
|
|
3334
3426
|
// The label above is the shared 设备名称 field in the role card (T57); this
|
|
@@ -3348,7 +3440,27 @@ exports.zh = {
|
|
|
3348
3440
|
'settings.client.failUnreachable': '无法连接服务端',
|
|
3349
3441
|
'settings.client.failRoleMismatch': '该配对码不适用于桌面应用端',
|
|
3350
3442
|
'settings.client.failUnexpected': '服务端回应无法识别,请确认服务端已安装 dsh-zen-remote 2.0.0',
|
|
3351
|
-
'settings.client.
|
|
3443
|
+
'settings.client.failLimitReached': '已达主服务端数量上限:请先移除旧服务端,或输入已配对服务端的地址覆盖重配',
|
|
3444
|
+
// --- MS-7 多服务端:卡片头计数、8 台上限红字、已配对主服务端列表 ---
|
|
3445
|
+
'settings.client.poolCount': '已配对 {count} / {max} 台',
|
|
3446
|
+
'settings.client.poolCapNote': '最多可同时连接 {max} 台主服务端。',
|
|
3447
|
+
'settings.client.limitReached': '已配满 {count} / {max} 台主服务端(已达上限)。如需添加新服务端,请先移除旧服务端;输入已配对服务端的地址仍可覆盖重配。',
|
|
3448
|
+
'settings.client.serversTitle': '已配对主服务端',
|
|
3449
|
+
'settings.client.serversEmpty': '尚未配对任何主服务端(最多可同时连接 {max} 台)',
|
|
3450
|
+
'settings.client.repair': '重新配对',
|
|
3451
|
+
'settings.client.serverRemove': '移除',
|
|
3452
|
+
'settings.client.unpairServerConfirm': '将从本机移除该主服务端的配对(服务端上的数据与设备记录不受影响)。确定移除?',
|
|
3453
|
+
'settings.client.unpairAllServers': '全部移除',
|
|
3454
|
+
'settings.client.unpairAllServersConfirm': '将从本机移除全部已配对的主服务端(服务端上的数据与设备记录不受影响)。确定移除?',
|
|
3455
|
+
'settings.client.serverStateConnected': '已连接',
|
|
3456
|
+
'settings.client.serverStateRetry': '离线,{seconds} 秒后重试',
|
|
3457
|
+
'settings.client.serverStateRetrySoon': '离线,即将重试',
|
|
3458
|
+
'settings.client.serverStateOffline': '离线',
|
|
3459
|
+
'settings.client.serverStateRevoked': '令牌已吊销',
|
|
3460
|
+
'settings.client.serverStateIncompatible': '不兼容',
|
|
3461
|
+
'settings.client.serverStateInvalidUrl': '地址无效',
|
|
3462
|
+
'settings.client.serverStateUnpaired': '未连接',
|
|
3463
|
+
'settings.client.serverDiagTitle': '各主服务端诊断',
|
|
3352
3464
|
'settings.client.statusTitle': '连接状态',
|
|
3353
3465
|
'settings.client.statusUnpaired': '未配对',
|
|
3354
3466
|
'settings.client.statusConnected': '已连接到 {serverUrl}',
|
|
@@ -3527,6 +3639,7 @@ exports.zh = {
|
|
|
3527
3639
|
'wizard.desktop.generate': '生成桌面客户端配对码',
|
|
3528
3640
|
'wizard.desktop.regenerate': '重新生成配对码',
|
|
3529
3641
|
'wizard.desktop.copyInfo': '复制地址与配对码',
|
|
3642
|
+
'wizard.desktop.copiedText': '服务端地址:{address}\n桌面客户端配对码:{code}',
|
|
3530
3643
|
'wizard.desktop.guide': '在另一台电脑打开本插件详情页,第 1 步选「作为桌面客户端」,第 2 步填入上方服务端地址与 8 位配对码即可完成连接。',
|
|
3531
3644
|
'wizard.desktop.pairedTitle': '已连接的桌面客户端({count})',
|
|
3532
3645
|
'wizard.desktop.pairedEmpty': '暂未配对桌面客户端',
|
|
@@ -3641,7 +3754,7 @@ exports.zh = {
|
|
|
3641
3754
|
'wizard.overview.devicesSummary': '手机/网页 {web} 台 · 桌面客户端 {desktop} 台',
|
|
3642
3755
|
'wizard.overview.devicesNone': '暂未配对设备',
|
|
3643
3756
|
'wizard.overview.clientCredLabel': '本地配对凭证',
|
|
3644
|
-
'wizard.overview.clientConnLabel': '
|
|
3757
|
+
'wizard.overview.clientConnLabel': '连接状态',
|
|
3645
3758
|
'wizard.overview.clientServerLabel': '远端服务端地址',
|
|
3646
3759
|
'wizard.overview.startSetup': '开始配置向导',
|
|
3647
3760
|
'wizard.overview.reconfigure': '修改接入配置',
|
|
@@ -3663,6 +3776,26 @@ exports.zh = {
|
|
|
3663
3776
|
'wizard.devicesModal.title': '已配对设备管理',
|
|
3664
3777
|
'wizard.devicesModal.subtitle': '查看与管理已配对的手机/网页设备及桌面客户端,支持改名、切换布局或吊销凭证',
|
|
3665
3778
|
'wizard.devicesModal.retest': '重新测试连通性',
|
|
3779
|
+
'wizard.overview.clientSyncLabel': '会话与插件同步',
|
|
3780
|
+
'wizard.overview.syncOk': '正常',
|
|
3781
|
+
'wizard.overview.syncBad': '异常',
|
|
3782
|
+
'wizard.overview.syncPending': '未连接',
|
|
3783
|
+
'wizard.overview.clientOnlineCount': '在线 {online} / {total} 台',
|
|
3784
|
+
'wizard.overview.clientOfflineCount': '离线 {count} 台',
|
|
3785
|
+
'wizard.overview.clientRevokedCount': '已吊销 {count} 台',
|
|
3786
|
+
'wizard.overview.clientAllOnline': '全部在线',
|
|
3787
|
+
'wizard.overview.clientServersLabel': '已配对服务端',
|
|
3788
|
+
'wizard.overview.clientPairedFull': '已配满 {count} / {max} 台(已达上限)',
|
|
3789
|
+
'wizard.overview.clientPairedNames': '{names}(上限 {max} 台)',
|
|
3790
|
+
'wizard.overview.limitNote': '已达 {max} 台上限,添加前请先移除旧服务端',
|
|
3791
|
+
'wizard.overview.manageServers': '管理服务端',
|
|
3792
|
+
'wizard.overview.addServer': '+ 添加服务端',
|
|
3793
|
+
'wizard.serversModal.title': '已配对主服务端管理',
|
|
3794
|
+
'wizard.serversModal.subtitle': '最多同时连接 {max} 台主服务端(当前 {count} / {max} 台)',
|
|
3795
|
+
'wizard.serversModal.viewStatus': '查看状态',
|
|
3796
|
+
'wizard.client.poolCapNote': '最多同时连接 {max} 台主服务端(当前已配对 {count} / {max} 台)',
|
|
3797
|
+
'wizard.client.continueAdd': '+ 继续添加另一台',
|
|
3798
|
+
'wizard.client.serverPicker': '选择要查看的服务端',
|
|
3666
3799
|
};
|
|
3667
3800
|
/** English dictionary, key-identical to the Chinese source of truth. */
|
|
3668
3801
|
exports.en = {
|
|
@@ -3774,6 +3907,10 @@ exports.en = {
|
|
|
3774
3907
|
'settings.invalidPort': 'Enter an integer from 1 to 65535',
|
|
3775
3908
|
'settings.invalidPositiveInt': 'Enter a positive integer',
|
|
3776
3909
|
'settings.pushTitle': 'Push',
|
|
3910
|
+
'settings.fieldPushEnabled': 'Enable session notifications',
|
|
3911
|
+
'settings.fieldPushEnabledHint': 'Master switch: when off, stops all automatic pushes and does not register the push_notify tool or system prompt.',
|
|
3912
|
+
'settings.fieldPushSessionName': 'Include session name in push',
|
|
3913
|
+
'settings.fieldPushSessionNameHint': 'Show "Session: <name>" on the first line of the push notification body.',
|
|
3777
3914
|
'settings.fieldPushSummary': 'Push body summary',
|
|
3778
3915
|
'settings.fieldPushSummaryHint': 'Include the turn\u2019s final text or pending question in push bodies.',
|
|
3779
3916
|
'settings.fieldPushTurnEnd': 'Turn-end push',
|
|
@@ -3850,7 +3987,7 @@ exports.en = {
|
|
|
3850
3987
|
'settings.statusUnknown': 'Could not read the plugin status',
|
|
3851
3988
|
'settings.refreshFailed': 'The status refresh failed — showing the last successful data',
|
|
3852
3989
|
'settings.resetPreview': 'Saving reverts to the next layer\u2019s value',
|
|
3853
|
-
'settings.client.connectTitle': 'Connect to
|
|
3990
|
+
'settings.client.connectTitle': 'Connect to main servers',
|
|
3854
3991
|
'settings.client.serverUrl': 'Server address',
|
|
3855
3992
|
'settings.client.serverUrlHint': 'The server\u2019s gateway address, e.g. http://192.168.1.10:3088 or https://dsh.example.com.',
|
|
3856
3993
|
// The label above is the shared 设备名称 field in the role card (T57); this
|
|
@@ -3870,7 +4007,27 @@ exports.en = {
|
|
|
3870
4007
|
'settings.client.failUnreachable': 'Could not reach the server',
|
|
3871
4008
|
'settings.client.failRoleMismatch': 'This pairing code is not valid for desktop clients',
|
|
3872
4009
|
'settings.client.failUnexpected': 'The server gave an unrecognized answer — make sure it runs dsh-zen-remote 2.0.0',
|
|
3873
|
-
'settings.client.
|
|
4010
|
+
'settings.client.failLimitReached': 'The main-server limit is reached: remove an old server first, or enter an already-paired address to re-pair it in place',
|
|
4011
|
+
// --- MS-7 multi-server: card-head count, the 8-server red alert, the paired list ---
|
|
4012
|
+
'settings.client.poolCount': '{count} / {max} paired',
|
|
4013
|
+
'settings.client.poolCapNote': 'Up to {max} main servers can be connected at the same time.',
|
|
4014
|
+
'settings.client.limitReached': 'All {count} / {max} main servers are paired (the limit is reached). To add a new server, remove an old one first; entering an already-paired address still re-pairs it in place.',
|
|
4015
|
+
'settings.client.serversTitle': 'Paired main servers',
|
|
4016
|
+
'settings.client.serversEmpty': 'No main servers paired yet (up to {max} at the same time)',
|
|
4017
|
+
'settings.client.repair': 'Re-pair',
|
|
4018
|
+
'settings.client.serverRemove': 'Remove',
|
|
4019
|
+
'settings.client.unpairServerConfirm': 'This machine will forget this main server\u2019s pairing (data and the device record on the server are untouched). Remove it?',
|
|
4020
|
+
'settings.client.unpairAllServers': 'Remove all',
|
|
4021
|
+
'settings.client.unpairAllServersConfirm': 'This machine will forget every paired main server (data and device records on the servers are untouched). Remove all?',
|
|
4022
|
+
'settings.client.serverStateConnected': 'Connected',
|
|
4023
|
+
'settings.client.serverStateRetry': 'Offline, retrying in {seconds} s',
|
|
4024
|
+
'settings.client.serverStateRetrySoon': 'Offline, retrying now',
|
|
4025
|
+
'settings.client.serverStateOffline': 'Offline',
|
|
4026
|
+
'settings.client.serverStateRevoked': 'Token revoked',
|
|
4027
|
+
'settings.client.serverStateIncompatible': 'Incompatible',
|
|
4028
|
+
'settings.client.serverStateInvalidUrl': 'Invalid address',
|
|
4029
|
+
'settings.client.serverStateUnpaired': 'Not connected',
|
|
4030
|
+
'settings.client.serverDiagTitle': 'Per-server diagnostics',
|
|
3874
4031
|
'settings.client.statusTitle': 'Connection',
|
|
3875
4032
|
'settings.client.statusUnpaired': 'Not paired',
|
|
3876
4033
|
'settings.client.statusConnected': 'Connected to {serverUrl}',
|
|
@@ -4047,6 +4204,7 @@ exports.en = {
|
|
|
4047
4204
|
'wizard.desktop.generate': 'Generate Desktop Pairing Code',
|
|
4048
4205
|
'wizard.desktop.regenerate': 'Regenerate Code',
|
|
4049
4206
|
'wizard.desktop.copyInfo': 'Copy Address & Code',
|
|
4207
|
+
'wizard.desktop.copiedText': 'Server address: {address}\nDesktop client pairing code: {code}',
|
|
4050
4208
|
'wizard.desktop.guide': 'On the other computer, open this plugin page, choose "Desktop Client" in Step 1, and enter the server address and 8-character code above in Step 2.',
|
|
4051
4209
|
'wizard.desktop.pairedTitle': 'Paired Desktop Clients ({count})',
|
|
4052
4210
|
'wizard.desktop.pairedEmpty': 'No desktop clients paired yet',
|
|
@@ -4161,7 +4319,7 @@ exports.en = {
|
|
|
4161
4319
|
'wizard.overview.devicesSummary': 'Phone/Web {web} · Desktop {desktop}',
|
|
4162
4320
|
'wizard.overview.devicesNone': 'No paired devices yet',
|
|
4163
4321
|
'wizard.overview.clientCredLabel': 'Local Credential',
|
|
4164
|
-
'wizard.overview.clientConnLabel': '
|
|
4322
|
+
'wizard.overview.clientConnLabel': 'Connection',
|
|
4165
4323
|
'wizard.overview.clientServerLabel': 'Remote Server URL',
|
|
4166
4324
|
'wizard.overview.startSetup': 'Start Setup Wizard',
|
|
4167
4325
|
'wizard.overview.reconfigure': 'Reconfigure Wizard',
|
|
@@ -4183,6 +4341,26 @@ exports.en = {
|
|
|
4183
4341
|
'wizard.devicesModal.title': 'Paired Devices',
|
|
4184
4342
|
'wizard.devicesModal.subtitle': 'View and manage paired phone/web and desktop client devices — rename, switch layout, or revoke access',
|
|
4185
4343
|
'wizard.devicesModal.retest': 'Retest Connectivity',
|
|
4344
|
+
'wizard.overview.clientSyncLabel': 'Session & Plugin Sync',
|
|
4345
|
+
'wizard.overview.syncOk': 'Normal',
|
|
4346
|
+
'wizard.overview.syncBad': 'Abnormal',
|
|
4347
|
+
'wizard.overview.syncPending': 'Not connected',
|
|
4348
|
+
'wizard.overview.clientOnlineCount': '{online} / {total} online',
|
|
4349
|
+
'wizard.overview.clientOfflineCount': '{count} offline',
|
|
4350
|
+
'wizard.overview.clientRevokedCount': '{count} revoked',
|
|
4351
|
+
'wizard.overview.clientAllOnline': 'All online',
|
|
4352
|
+
'wizard.overview.clientServersLabel': 'Paired Servers',
|
|
4353
|
+
'wizard.overview.clientPairedFull': 'All {count} / {max} paired (limit reached)',
|
|
4354
|
+
'wizard.overview.clientPairedNames': '{names} (up to {max})',
|
|
4355
|
+
'wizard.overview.limitNote': 'The {max}-server limit is reached — remove an old server before adding another',
|
|
4356
|
+
'wizard.overview.manageServers': 'Manage Servers',
|
|
4357
|
+
'wizard.overview.addServer': '+ Add Server',
|
|
4358
|
+
'wizard.serversModal.title': 'Paired Main Servers',
|
|
4359
|
+
'wizard.serversModal.subtitle': 'Up to {max} main servers at the same time (currently {count} / {max})',
|
|
4360
|
+
'wizard.serversModal.viewStatus': 'View Status',
|
|
4361
|
+
'wizard.client.poolCapNote': 'Up to {max} main servers at the same time ({count} / {max} currently paired)',
|
|
4362
|
+
'wizard.client.continueAdd': '+ Add Another Server',
|
|
4363
|
+
'wizard.client.serverPicker': 'Choose the server to view',
|
|
4186
4364
|
};
|
|
4187
4365
|
};
|
|
4188
4366
|
__modules["client-data/shares.js"] = function (require, module, exports) {
|
|
@@ -16738,6 +16916,8 @@ __modules["virtual-id.js"] = function (require, module, exports) {
|
|
|
16738
16916
|
*/
|
|
16739
16917
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16740
16918
|
exports.VIRTUAL_GROUP_NAME_MARK = exports.VIRTUAL_ID_PREFIX = void 0;
|
|
16919
|
+
exports.encodeServerIdZeroWidth = encodeServerIdZeroWidth;
|
|
16920
|
+
exports.decodeServerIdZeroWidth = decodeServerIdZeroWidth;
|
|
16741
16921
|
exports.toVirtual = toVirtual;
|
|
16742
16922
|
exports.fromVirtual = fromVirtual;
|
|
16743
16923
|
exports.isVirtual = isVirtual;
|
|
@@ -16757,9 +16937,69 @@ exports.VIRTUAL_ID_PREFIX = 'zr~';
|
|
|
16757
16937
|
* text.
|
|
16758
16938
|
*/
|
|
16759
16939
|
exports.VIRTUAL_GROUP_NAME_MARK = '\u2060';
|
|
16940
|
+
/** MS-4: the two zero-width code points the serverId rides on inside a
|
|
16941
|
+
* virtual group NAME — ZERO WIDTH SPACE encodes bit 0, ZERO WIDTH
|
|
16942
|
+
* NON-JOINER bit 1. Invisible everywhere, and neither can appear in a
|
|
16943
|
+
* server id's own visible text, so the window between the two marks is
|
|
16944
|
+
* unambiguous. */
|
|
16945
|
+
const ZERO_BIT = '\u200b';
|
|
16946
|
+
const ONE_BIT = '\u200c';
|
|
16947
|
+
const SERVER_ID_BITS = 32;
|
|
16760
16948
|
/** The server id half: exactly the 8 lowercase hex chars relay-server.ts
|
|
16761
16949
|
* mints (`randomBytes(4).toString('hex')`). */
|
|
16762
16950
|
const SERVER_ID_PATTERN = /^[0-9a-f]{8}$/;
|
|
16951
|
+
/**
|
|
16952
|
+
* MS-4: the invisible prefix a REMOTE model-menu group heading carries
|
|
16953
|
+
* before its server name (merge-streams.ts mergeModelCatalogs). The full
|
|
16954
|
+
* form is `${MARK}${32 zero-width bits}${MARK}` — the 32 bits of the
|
|
16955
|
+
* 8-hex serverId (MSB first per nibble), ZERO_BIT for 0 and ONE_BIT for 1
|
|
16956
|
+
* — so the browser half can tell WHICH server a remote model group came
|
|
16957
|
+
* from (effects/model-group-side.ts classifies per-server hiding from
|
|
16958
|
+
* heading text alone). A serverId that is not 8 lowercase hex (the format
|
|
16959
|
+
* every real handshake carries) degrades to the bare legacy mark: the
|
|
16960
|
+
* heading stays a marked remote heading, just without a decodable server.
|
|
16961
|
+
*/
|
|
16962
|
+
function encodeServerIdZeroWidth(serverId) {
|
|
16963
|
+
if (typeof serverId !== 'string' || !SERVER_ID_PATTERN.test(serverId))
|
|
16964
|
+
return exports.VIRTUAL_GROUP_NAME_MARK;
|
|
16965
|
+
let bits = '';
|
|
16966
|
+
for (const nibble of serverId) {
|
|
16967
|
+
for (const bit of Number.parseInt(nibble, 16).toString(2).padStart(4, '0')) {
|
|
16968
|
+
bits += bit === '0' ? ZERO_BIT : ONE_BIT;
|
|
16969
|
+
}
|
|
16970
|
+
}
|
|
16971
|
+
return `${exports.VIRTUAL_GROUP_NAME_MARK}${bits}${exports.VIRTUAL_GROUP_NAME_MARK}`;
|
|
16972
|
+
}
|
|
16973
|
+
/**
|
|
16974
|
+
* MS-4: read the server id back out of one group heading, or `undefined`.
|
|
16975
|
+
* Strict like every decoder here: the text must start with the mark, the
|
|
16976
|
+
* window right after it must be exactly 32 zero-width bits closed by the
|
|
16977
|
+
* terminator mark — anything else (the legacy bare-mark heading, visible
|
|
16978
|
+
* text, a short or long bit run, a missing terminator) answers
|
|
16979
|
+
* `undefined`, which the classifier reads as "remote, server unknown".
|
|
16980
|
+
*/
|
|
16981
|
+
function decodeServerIdZeroWidth(headingText) {
|
|
16982
|
+
if (typeof headingText !== 'string' || !headingText.startsWith(exports.VIRTUAL_GROUP_NAME_MARK))
|
|
16983
|
+
return undefined;
|
|
16984
|
+
const rest = headingText.slice(exports.VIRTUAL_GROUP_NAME_MARK.length);
|
|
16985
|
+
if (rest.length < SERVER_ID_BITS + 1 || rest[SERVER_ID_BITS] !== exports.VIRTUAL_GROUP_NAME_MARK)
|
|
16986
|
+
return undefined;
|
|
16987
|
+
let hex = '';
|
|
16988
|
+
for (let i = 0; i < SERVER_ID_BITS; i += 4) {
|
|
16989
|
+
let nibble = 0;
|
|
16990
|
+
for (let bit = 0; bit < 4; bit++) {
|
|
16991
|
+
const bitChar = rest[i + bit];
|
|
16992
|
+
if (bitChar === ZERO_BIT)
|
|
16993
|
+
nibble = nibble << 1;
|
|
16994
|
+
else if (bitChar === ONE_BIT)
|
|
16995
|
+
nibble = (nibble << 1) | 1;
|
|
16996
|
+
else
|
|
16997
|
+
return undefined;
|
|
16998
|
+
}
|
|
16999
|
+
hex += nibble.toString(16);
|
|
17000
|
+
}
|
|
17001
|
+
return hex;
|
|
17002
|
+
}
|
|
16763
17003
|
/** Build the virtual form of one server-side session or workspace id. */
|
|
16764
17004
|
function toVirtual(serverId, id) {
|
|
16765
17005
|
return `${exports.VIRTUAL_ID_PREFIX}${serverId}~${id}`;
|
|
@@ -17526,9 +17766,38 @@ function pairFailText(fail, t) {
|
|
|
17526
17766
|
}
|
|
17527
17767
|
case 'unreachable': return t('settings.client.failUnreachable');
|
|
17528
17768
|
case 'role-mismatch': return t('settings.client.failRoleMismatch');
|
|
17769
|
+
case 'limit-reached': return t('settings.client.failLimitReached');
|
|
17529
17770
|
default: return t('settings.client.failUnexpected');
|
|
17530
17771
|
}
|
|
17531
17772
|
}
|
|
17773
|
+
/** Whether one paired server has anything for the per-server diagnostics
|
|
17774
|
+
* group (MS-7): a last error, a compat block, or bridge verdicts. */
|
|
17775
|
+
function serverHasDiag(server) {
|
|
17776
|
+
return server.lastError !== '' || server.compat !== undefined || server.pluginBridge.length > 0;
|
|
17777
|
+
}
|
|
17778
|
+
/** One paired server's state badge (MS-7): copy plus the tone the badge
|
|
17779
|
+
* wears — `live` green for connected, `warn` for an offline row (with the
|
|
17780
|
+
* retry countdown when one is armed), `invalid` red for a verdict only a
|
|
17781
|
+
* re-pair or an upgrade fixes. */
|
|
17782
|
+
function serverStateBadge(server, now, t) {
|
|
17783
|
+
switch (server.state) {
|
|
17784
|
+
case 'connected': return { text: t('settings.client.serverStateConnected'), tone: 'live' };
|
|
17785
|
+
case 'unreachable':
|
|
17786
|
+
case 'unexpected': {
|
|
17787
|
+
if (server.nextRetryAt === undefined)
|
|
17788
|
+
return { text: t('settings.client.serverStateOffline'), tone: 'warn' };
|
|
17789
|
+
const seconds = Math.max(0, Math.ceil((server.nextRetryAt - now) / 1000));
|
|
17790
|
+
return {
|
|
17791
|
+
text: seconds > 0 ? t('settings.client.serverStateRetry', { seconds }) : t('settings.client.serverStateRetrySoon'),
|
|
17792
|
+
tone: 'warn',
|
|
17793
|
+
};
|
|
17794
|
+
}
|
|
17795
|
+
case 'revoked': return { text: t('settings.client.serverStateRevoked'), tone: 'invalid' };
|
|
17796
|
+
case 'incompatible': return { text: t('settings.client.serverStateIncompatible'), tone: 'invalid' };
|
|
17797
|
+
case 'invalid-url': return { text: t('settings.client.serverStateInvalidUrl'), tone: 'invalid' };
|
|
17798
|
+
default: return { text: t('settings.client.serverStateUnpaired'), tone: undefined };
|
|
17799
|
+
}
|
|
17800
|
+
}
|
|
17532
17801
|
function SettingsSectionPage({ config, shares, t }) {
|
|
17533
17802
|
const form = (0, react_1.useSyncExternalStore)((0, react_1.useCallback)((cb) => config.subscribe(cb), [config]), () => config.getSnapshot());
|
|
17534
17803
|
// Latest-wins gates (T15-fix 4): every status request takes a ticket and
|
|
@@ -17561,11 +17830,16 @@ function SettingsSectionPage({ config, shares, t }) {
|
|
|
17561
17830
|
const [pairCode, setPairCode] = (0, react_1.useState)('');
|
|
17562
17831
|
const [claimBusy, setClaimBusy] = (0, react_1.useState)(false);
|
|
17563
17832
|
const [claimFail, setClaimFail] = (0, react_1.useState)(null);
|
|
17564
|
-
const [claimWriteFailed, setClaimWriteFailed] = (0, react_1.useState)(false);
|
|
17565
17833
|
const [unpairBusy, setUnpairBusy] = (0, react_1.useState)(false);
|
|
17566
17834
|
const [unpairDone, setUnpairDone] = (0, react_1.useState)(false);
|
|
17567
17835
|
// T43: the 立即重连 button's in-flight marker.
|
|
17568
17836
|
const [reconnectBusy, setReconnectBusy] = (0, react_1.useState)(false);
|
|
17837
|
+
// MS-7: the pool list's in-flight row action — the entry id, or 'all' for
|
|
17838
|
+
// the remove-all — and whether the last one failed.
|
|
17839
|
+
const [entryBusy, setEntryBusy] = (0, react_1.useState)(null);
|
|
17840
|
+
const [entryActionFailed, setEntryActionFailed] = (0, react_1.useState)(false);
|
|
17841
|
+
// MS-7: the 重新配对 action focuses the code box.
|
|
17842
|
+
const pairCodeRef = (0, react_1.useRef)(null);
|
|
17569
17843
|
const loadStatus = (0, react_1.useCallback)(() => {
|
|
17570
17844
|
const ticket = statusGate.next();
|
|
17571
17845
|
fetch(settings_form_ts_1.ADMIN_STATUS_ROUTE)
|
|
@@ -17599,26 +17873,9 @@ function SettingsSectionPage({ config, shares, t }) {
|
|
|
17599
17873
|
: { state: 'error', status: error instanceof StatusError ? error.status : undefined });
|
|
17600
17874
|
});
|
|
17601
17875
|
}, [statusGate]);
|
|
17602
|
-
//
|
|
17603
|
-
//
|
|
17604
|
-
//
|
|
17605
|
-
// update reaches the backend with both sides now EQUAL, so the push path
|
|
17606
|
-
// stays silent and the two ends cannot bounce the name back and forth.
|
|
17607
|
-
// T59-fix: the decision runs HERE, on each FRESH answer, judging the row
|
|
17608
|
-
// and the draft AS THEY ARE at that moment — never off a stale
|
|
17609
|
-
// `clientView` racing a row change (an effect keyed on the row value
|
|
17610
|
-
// would fire on every save with the PREVIOUS poll's name and write the
|
|
17611
|
-
// old one straight back over the user's fresh save).
|
|
17612
|
-
const followServerDeviceName = (0, react_1.useCallback)((view) => {
|
|
17613
|
-
if (!config.savedRoleIsClient())
|
|
17614
|
-
return;
|
|
17615
|
-
if (view.state !== 'connected')
|
|
17616
|
-
return;
|
|
17617
|
-
const rowName = typeof config.rowValue('serverName') === 'string' ? config.rowValue('serverName') : '';
|
|
17618
|
-
if (!(0, settings_form_ts_1.shouldFollowServerDeviceName)(view.deviceName, rowName, config.hasDraft('serverName')))
|
|
17619
|
-
return;
|
|
17620
|
-
void config.writeDeviceName(view.deviceName);
|
|
17621
|
-
}, [config]);
|
|
17876
|
+
// MS-7: the device name flows ONE way — a saved row serverName is
|
|
17877
|
+
// broadcast to every paired server by the backend pool; a server-side
|
|
17878
|
+
// record of this device's name never writes back into the row.
|
|
17622
17879
|
const loadClientStatus = (0, react_1.useCallback)(() => {
|
|
17623
17880
|
const ticket = clientGate.next();
|
|
17624
17881
|
fetch(settings_form_ts_1.CLIENT_STATUS_ROUTE)
|
|
@@ -17630,9 +17887,7 @@ function SettingsSectionPage({ config, shares, t }) {
|
|
|
17630
17887
|
.then((body) => {
|
|
17631
17888
|
if (!clientGate.isLatest(ticket))
|
|
17632
17889
|
return;
|
|
17633
|
-
|
|
17634
|
-
setClientLoad({ state: 'ready', view });
|
|
17635
|
-
followServerDeviceName(view);
|
|
17890
|
+
setClientLoad({ state: 'ready', view: (0, settings_form_ts_1.deriveClientStatusView)(body) });
|
|
17636
17891
|
})
|
|
17637
17892
|
.catch(() => {
|
|
17638
17893
|
if (!clientGate.isLatest(ticket))
|
|
@@ -17641,17 +17896,36 @@ function SettingsSectionPage({ config, shares, t }) {
|
|
|
17641
17896
|
? { state: 'stale', view: prev.view }
|
|
17642
17897
|
: { state: 'error' });
|
|
17643
17898
|
});
|
|
17644
|
-
}, [clientGate
|
|
17899
|
+
}, [clientGate]);
|
|
17645
17900
|
// The client group's rendered view, hoisted for the countdown ticker and
|
|
17646
17901
|
// the retry-follow refresh below (T43).
|
|
17647
17902
|
const clientView = clientLoad.state === 'ready' || clientLoad.state === 'stale' ? clientLoad.view : undefined;
|
|
17903
|
+
// MS-7: the paired-server pool, in pairing order (empty on a legacy body),
|
|
17904
|
+
// and the cap the counters and the red alert read.
|
|
17905
|
+
const serverRows = clientView?.servers ?? [];
|
|
17906
|
+
const maxServers = clientView?.maxServers ?? settings_form_ts_1.MAX_PAIRED_SERVERS;
|
|
17648
17907
|
// RD-3: the remote drop guard's browser-side counters, read at render
|
|
17649
17908
|
// time — no snapshot state of its own: the initial mount, the 15s
|
|
17650
17909
|
// connected-status poll and the countdown ticker all re-render this page,
|
|
17651
17910
|
// and each render takes the values as they are right then.
|
|
17652
17911
|
const dropDiag = (0, remote_drop_ts_2.remoteDropDiagnostics)();
|
|
17653
|
-
|
|
17912
|
+
// MS-7: the SOONEST armed retry across the top level and every pool row —
|
|
17913
|
+
// each offline row counts down in its own badge, and the follow refresh
|
|
17914
|
+
// below fires after whichever retry comes due first.
|
|
17915
|
+
const clientRetryAt = [clientView?.nextRetryAt, ...serverRows.map((server) => server.nextRetryAt)]
|
|
17916
|
+
.reduce((soonest, at) => (at !== undefined && (soonest === undefined || at < soonest) ? at : soonest), undefined);
|
|
17654
17917
|
const clientCountingDown = clientRetryAt !== undefined;
|
|
17918
|
+
// MS-7 diagnostics gates: the per-server group, the pre-pool fallback to
|
|
17919
|
+
// the top-level mirrors, and whether the section has any line at all.
|
|
17920
|
+
const legacyTopDiag = clientView !== undefined && serverRows.length === 0;
|
|
17921
|
+
const hasServerDiag = serverRows.some(serverHasDiag);
|
|
17922
|
+
const diagVisible = clientView !== undefined && (clientView.intercept !== undefined
|
|
17923
|
+
|| clientView.pluginRouteIntercept !== undefined
|
|
17924
|
+
|| clientView.uploadProbe !== undefined
|
|
17925
|
+
|| dropDiag.installed
|
|
17926
|
+
|| dropDiag.notInstalledReason !== undefined
|
|
17927
|
+
|| hasServerDiag
|
|
17928
|
+
|| (legacyTopDiag && (clientView.lastError !== '' || clientView.compat !== undefined || (clientView.pluginBridge?.length ?? 0) > 0)));
|
|
17655
17929
|
// Offline with a pending retry: refresh the status shortly after the
|
|
17656
17930
|
// retry comes due, so a server that came back flips the line to connected
|
|
17657
17931
|
// without a manual refresh. A still-offline answer carries the NEXT
|
|
@@ -17957,23 +18231,29 @@ function SettingsSectionPage({ config, shares, t }) {
|
|
|
17957
18231
|
setPairUrl(rowServerUrl);
|
|
17958
18232
|
}, [pairUrlTouched, pairUrl, rowServerUrl]);
|
|
17959
18233
|
const codeNormalized = (0, settings_form_ts_1.normalizePairingCode)(pairCode);
|
|
17960
|
-
//
|
|
17961
|
-
//
|
|
17962
|
-
//
|
|
17963
|
-
//
|
|
17964
|
-
|
|
18234
|
+
// MS-7: the whole gate is the MS-6 pure truth table — an address and a
|
|
18235
|
+
// complete code, a quiet page, a valid device-name draft (T60: a red-boxed
|
|
18236
|
+
// name would silently pair under the fallback copy), and the 8-server cap,
|
|
18237
|
+
// which blocks only a NEW address: re-entering an already-paired one
|
|
18238
|
+
// re-pairs it in place, at 8/8 too.
|
|
18239
|
+
const claimVerdict = (0, settings_form_ts_1.canSubmitClientPairing)({
|
|
18240
|
+
pairUrl,
|
|
18241
|
+
pairCode: codeNormalized,
|
|
18242
|
+
servers: serverRows,
|
|
18243
|
+
maxServers,
|
|
18244
|
+
busy: claimBusy,
|
|
18245
|
+
deviceNameInvalid: form.serverName.invalid,
|
|
18246
|
+
});
|
|
18247
|
+
const claimAllowed = form.available && form.writable && claimVerdict.allowed;
|
|
17965
18248
|
const runClaim = async () => {
|
|
17966
18249
|
setClaimBusy(true);
|
|
17967
18250
|
setClaimFail(null);
|
|
17968
|
-
setClaimWriteFailed(false);
|
|
17969
18251
|
// UX-1: captured BEFORE the pairing — whether the page ran as client, and
|
|
17970
18252
|
// whether the backend's stream interceptor was already active per the
|
|
17971
18253
|
// last client/status diagnostics.
|
|
17972
18254
|
const wasEffectiveClient = clientRole;
|
|
17973
18255
|
const streamInterceptAlreadyActive = clientView?.intercept?.installed === true;
|
|
17974
|
-
// T60:
|
|
17975
|
-
// ({@link claimDeviceNameOf}), sent to the server AND written into the
|
|
17976
|
-
// row when the claim lands.
|
|
18256
|
+
// T60: the name registers trimmed and legal ({@link claimDeviceNameOf}).
|
|
17977
18257
|
const claimName = (0, settings_form_ts_1.claimDeviceNameOf)(form.serverName.text, t('settings.client.deviceNameDefault'));
|
|
17978
18258
|
try {
|
|
17979
18259
|
const res = await fetch(settings_form_ts_1.CLIENT_CLAIM_ROUTE, {
|
|
@@ -17981,8 +18261,7 @@ function SettingsSectionPage({ config, shares, t }) {
|
|
|
17981
18261
|
headers: { 'Content-Type': 'application/json' },
|
|
17982
18262
|
// T57: the name registers under the shared 设备名称 field's current
|
|
17983
18263
|
// displayed value (a staged draft counts), falling back to the
|
|
17984
|
-
// default copy when the field is empty
|
|
17985
|
-
// admin baseline (T55), so an unstored name displays empty here.
|
|
18264
|
+
// default copy when the field is empty.
|
|
17986
18265
|
body: JSON.stringify({
|
|
17987
18266
|
serverUrl: pairUrl.trim(),
|
|
17988
18267
|
code: codeNormalized,
|
|
@@ -17990,51 +18269,32 @@ function SettingsSectionPage({ config, shares, t }) {
|
|
|
17990
18269
|
}),
|
|
17991
18270
|
});
|
|
17992
18271
|
const body = await res.json().catch(() => ({}));
|
|
17993
|
-
if (res.ok && body.ok === true
|
|
17994
|
-
//
|
|
17995
|
-
//
|
|
17996
|
-
//
|
|
17997
|
-
//
|
|
17998
|
-
|
|
17999
|
-
// actually sent (T60) — either way the row now matches the server.
|
|
18000
|
-
const urlToWrite = typeof body.serverUrl === 'string' && body.serverUrl !== '' ? body.serverUrl : pairUrl.trim();
|
|
18001
|
-
// T59-fix: the echo is judged by the SAME rules as our own draft
|
|
18002
|
-
// (claimDeviceNameOf trims and validates) — a server answer we
|
|
18003
|
-
// could not store falls back to the name we actually sent.
|
|
18004
|
-
const echoed = typeof body.deviceName === 'string' && body.deviceName !== '' ? (0, settings_form_ts_1.claimDeviceNameOf)(body.deviceName, claimName) : claimName;
|
|
18005
|
-
const landed = await config.writeClientPairing(urlToWrite, body.token, echoed);
|
|
18006
|
-
if (landed && (0, settings_form_ts_1.shouldReloadAfterClientActivation)({
|
|
18272
|
+
if (res.ok && body.ok === true) {
|
|
18273
|
+
// MS-5/MS-7: the backend landed the pairing straight into
|
|
18274
|
+
// zen-remote-state.json (the token never reaches the browser), so
|
|
18275
|
+
// there is nothing to write here — only the FIRST activation still
|
|
18276
|
+
// reloads, so the page's resident global streams reopen wrapped.
|
|
18277
|
+
if ((0, settings_form_ts_1.shouldReloadAfterClientActivation)({
|
|
18007
18278
|
wasEffectiveClient,
|
|
18008
18279
|
nowClientRole: true,
|
|
18009
18280
|
savedOrPaired: true,
|
|
18010
18281
|
streamInterceptAlreadyActive,
|
|
18011
18282
|
})) {
|
|
18012
|
-
// UX-1: FIRST pairing on a page whose resident global streams were
|
|
18013
|
-
// opened unwrapped (or with the interceptor inactive) — wait out the
|
|
18014
|
-
// backend's row-reload window, then reload so the streams reopen
|
|
18015
|
-
// wrapped and the server's sessions enter the sidebar.
|
|
18016
18283
|
setClaimBusy(false);
|
|
18017
18284
|
void (0, settings_form_ts_1.awaitClientStatusReady)().then(() => window.location.reload());
|
|
18018
18285
|
return;
|
|
18019
18286
|
}
|
|
18020
|
-
|
|
18021
|
-
|
|
18022
|
-
|
|
18023
|
-
|
|
18024
|
-
|
|
18025
|
-
setPairUrl(urlToWrite);
|
|
18026
|
-
setUnpairDone(false);
|
|
18027
|
-
loadClientStatus();
|
|
18028
|
-
}
|
|
18029
|
-
else {
|
|
18030
|
-
setClaimWriteFailed(true);
|
|
18031
|
-
}
|
|
18287
|
+
setPairCode('');
|
|
18288
|
+
if (typeof body.serverUrl === 'string' && body.serverUrl !== '')
|
|
18289
|
+
setPairUrl(body.serverUrl);
|
|
18290
|
+
setUnpairDone(false);
|
|
18291
|
+
loadClientStatus();
|
|
18032
18292
|
}
|
|
18033
18293
|
else {
|
|
18034
|
-
// Every other shape is a failure (T16-fix)
|
|
18035
|
-
//
|
|
18036
|
-
//
|
|
18037
|
-
//
|
|
18294
|
+
// Every other shape is a failure (T16-fix), shown with the classified
|
|
18295
|
+
// code when the route sent one (MS-5 adds `limit-reached`). The
|
|
18296
|
+
// body's `message` is ignored: the wording is this build's
|
|
18297
|
+
// dictionary (pairFailText).
|
|
18038
18298
|
const fail = { code: typeof body.code === 'string' && body.code !== '' ? body.code : 'unexpected' };
|
|
18039
18299
|
if (typeof body.retryAfterMs === 'number')
|
|
18040
18300
|
fail.retryAfterMs = body.retryAfterMs;
|
|
@@ -18057,6 +18317,70 @@ function SettingsSectionPage({ config, shares, t }) {
|
|
|
18057
18317
|
loadClientStatus();
|
|
18058
18318
|
}
|
|
18059
18319
|
};
|
|
18320
|
+
/** MS-7: one POST of the pool list's row actions — whether it answered ok. */
|
|
18321
|
+
const postClientRoute = async (route, payload) => {
|
|
18322
|
+
try {
|
|
18323
|
+
const res = await fetch(route, {
|
|
18324
|
+
method: 'POST',
|
|
18325
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18326
|
+
body: payload,
|
|
18327
|
+
});
|
|
18328
|
+
return res.ok;
|
|
18329
|
+
}
|
|
18330
|
+
catch {
|
|
18331
|
+
return false;
|
|
18332
|
+
}
|
|
18333
|
+
};
|
|
18334
|
+
// MS-7 立即重连 for ONE server: refresh now (the state is at least
|
|
18335
|
+
// "connecting") and once more after the attempt can have settled — the
|
|
18336
|
+
// T43 top-level button's cadence, per entry.
|
|
18337
|
+
const runEntryReconnect = async (entryId) => {
|
|
18338
|
+
setEntryBusy(entryId);
|
|
18339
|
+
setEntryActionFailed(false);
|
|
18340
|
+
if (await postClientRoute(settings_form_ts_1.CLIENT_RECONNECT_ROUTE, JSON.stringify({ entryId }))) {
|
|
18341
|
+
loadClientStatus();
|
|
18342
|
+
refreshTimers.current.push(window.setTimeout(() => { loadClientStatus(); }, 1500));
|
|
18343
|
+
}
|
|
18344
|
+
else {
|
|
18345
|
+
setEntryActionFailed(true);
|
|
18346
|
+
}
|
|
18347
|
+
setEntryBusy(null);
|
|
18348
|
+
};
|
|
18349
|
+
// MS-7 移除: forget one paired server (the server's own device record is
|
|
18350
|
+
// untouched — the confirm copy says so).
|
|
18351
|
+
const runUnpairEntry = async (entryId) => {
|
|
18352
|
+
if (!window.confirm(t('settings.client.unpairServerConfirm')))
|
|
18353
|
+
return;
|
|
18354
|
+
setEntryBusy(entryId);
|
|
18355
|
+
setEntryActionFailed(false);
|
|
18356
|
+
if (await postClientRoute(settings_form_ts_1.CLIENT_UNPAIR_ROUTE, JSON.stringify({ entryId })))
|
|
18357
|
+
loadClientStatus();
|
|
18358
|
+
else
|
|
18359
|
+
setEntryActionFailed(true);
|
|
18360
|
+
setEntryBusy(null);
|
|
18361
|
+
};
|
|
18362
|
+
// MS-7 全部移除: forget every paired server at once.
|
|
18363
|
+
const runUnpairAllServers = async () => {
|
|
18364
|
+
if (!window.confirm(t('settings.client.unpairAllServersConfirm')))
|
|
18365
|
+
return;
|
|
18366
|
+
setEntryBusy('all');
|
|
18367
|
+
setEntryActionFailed(false);
|
|
18368
|
+
if (await postClientRoute(settings_form_ts_1.CLIENT_UNPAIR_ROUTE, JSON.stringify({ all: true })))
|
|
18369
|
+
loadClientStatus();
|
|
18370
|
+
else
|
|
18371
|
+
setEntryActionFailed(true);
|
|
18372
|
+
setEntryBusy(null);
|
|
18373
|
+
};
|
|
18374
|
+
// MS-7 重新配对: point the pairing form at this server — its address in the
|
|
18375
|
+
// box (an already-paired address passes the cap gate), a blank code, the
|
|
18376
|
+
// focus on the code box so the user only types the fresh code.
|
|
18377
|
+
const repurposeServer = (server) => {
|
|
18378
|
+
setPairUrl(server.serverUrl);
|
|
18379
|
+
setPairUrlTouched(true);
|
|
18380
|
+
setPairCode('');
|
|
18381
|
+
setClaimFail(null);
|
|
18382
|
+
pairCodeRef.current?.focus();
|
|
18383
|
+
};
|
|
18060
18384
|
const formDisabled = !form.available || !form.writable;
|
|
18061
18385
|
// T17: saving any restart-required field (the role among them) moves the
|
|
18062
18386
|
// row, and the host half's watcher reloads it — the gateway child restarts
|
|
@@ -18095,7 +18419,8 @@ function SettingsSectionPage({ config, shares, t }) {
|
|
|
18095
18419
|
// One-of control (the shared fields have no select): the shared field chrome, a select body.
|
|
18096
18420
|
const select = (state, field, label, hint, options, optionLabel) => ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-head", children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: `zr-settings-${field}`, children: label }), state.overridden && (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-badge", children: t('settings.overridden') }), state.overridden && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-settings-reset", disabled: formDisabled || state.locked, onClick: () => { config.resetField(field); }, children: t('settings.reset') }))] }), (0, jsx_runtime_1.jsx)("select", { id: `zr-settings-${field}`, className: "zr-settings-input", disabled: formDisabled || state.locked, value: state.text, onChange: (e) => { config.stage(field, e.currentTarget.value); }, children: options.map((option) => (0, jsx_runtime_1.jsx)("option", { value: option, children: optionLabel(option) }, option)) }), hint !== '' && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: hint }), clearPreviewNote(state), annotations(field)] }, field));
|
|
18097
18421
|
// Boolean control (the shared fields have no checkbox): label row + switch.
|
|
18098
|
-
const bool = (state, field, label, hint) => ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-head", children: [(0, jsx_runtime_1.jsx)("
|
|
18422
|
+
const bool = (state, field, label, hint, parentDisabled = false) => ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", "data-disabled": parentDisabled ? 'true' : undefined, children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-settings-head-title", children: label }), state.overridden && (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-badge", children: t('settings.overridden') }), state.overridden && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-settings-reset", disabled: formDisabled || state.locked || parentDisabled, onClick: () => { config.resetField(field); }, children: t('settings.reset') })), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Switch, { disabled: formDisabled || state.locked || parentDisabled, checked: !parentDisabled && state.text === 'true', label: label, onChange: (checked) => { config.stage(field, checked ? 'true' : 'false'); } })] }), (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: hint }), clearPreviewNote(state), annotations(field)] }, field));
|
|
18423
|
+
const pushOff = form.pushEnabled.text !== 'true';
|
|
18099
18424
|
const deviceRow = (device) => ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-device", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-device-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-settings-device-name", children: device.name }), (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-badge", children: device.role === 'web' ? t('settings.deviceRoleWeb') : t('settings.deviceRoleDesktop') }), (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-badge", children: device.hasPush ? t('settings.devicePushOn') : t('settings.devicePushOff') }), (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-hint", children: t('settings.deviceLastSeen', { time: new Date(device.lastSeen).toLocaleString() }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-device-actions", children: [(0, jsx_runtime_1.jsxs)("select", { className: "zr-settings-input", "aria-label": t('settings.deviceRoleSelect', { name: device.name }), disabled: !localOps || localBusy, value: device.role, onChange: (e) => { void runDeviceAction({ action: 'set-role', id: device.id, role: e.currentTarget.value }); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "web", children: t('settings.deviceRoleWeb') }), (0, jsx_runtime_1.jsx)("option", { value: "desktop-client", children: t('settings.deviceRoleDesktop') })] }), (0, jsx_runtime_1.jsx)("select", { className: "zr-settings-input", "aria-label": t('settings.deviceKindSelect', { name: device.name }), disabled: !localOps || localBusy, value: device.kind, onChange: (e) => { void runDeviceAction({ action: 'set-kind', id: device.id, kind: e.currentTarget.value }); }, children: DEVICE_KINDS.map((kind) => ((0, jsx_runtime_1.jsx)("option", { value: kind, children: kind === 'auto' ? t('settings.kindAuto') : kind === 'phone' ? t('settings.kindPhone') : t('settings.kindDesktop') }, kind))) }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: !localOps || localBusy, onClick: () => { setRenaming({ id: device.id, draft: device.name }); }, children: t('settings.deviceRename') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: !localOps || localBusy, onClick: () => {
|
|
18100
18425
|
if (window.confirm(t('settings.revokeConfirm')))
|
|
18101
18426
|
void runDeviceAction({ action: 'revoke', id: device.id });
|
|
@@ -18168,7 +18493,7 @@ function SettingsSectionPage({ config, shares, t }) {
|
|
|
18168
18493
|
}
|
|
18169
18494
|
saveLanded(reloadsRow);
|
|
18170
18495
|
});
|
|
18171
|
-
}, onDiscard: () => { config.discard(); }, children: [(0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.roleTitle') }), select(form.role, 'role', t('settings.role'), t('settings.roleHint'), ['host', 'client'], (option) => (option === 'host' ? t('settings.roleHost') : t('settings.roleClient'))), text(form.serverName, 'serverName', t('settings.fieldServerName'), t(clientRole ? 'settings.fieldServerNameHintClient' : 'settings.fieldServerNameHint'), t('settings.invalidName')), reloadPending && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-status-line", children: t('settings.reloadNote') }), !clientRole && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.gatewayTitle') }), text(form.host, 'host', t('settings.fieldHost'), t('settings.fieldHostHint'), '', false, t('settings.fieldHostNote')), text(form.port, 'port', t('settings.fieldPort'), t('settings.fieldPortHint'), t('settings.invalidPort'), true), text(form.trustedProxies, 'trustedProxies', t('settings.fieldTrustedProxies'), t('settings.fieldTrustedProxiesHint'), ''), text(form.rateLimit, 'rateLimit', t('settings.fieldRateLimit'), t('settings.fieldRateLimitHint'), t('settings.invalidPositiveInt'), true), text(form.vapidSubject, 'vapidSubject', t('settings.fieldVapidSubject'), t('settings.fieldVapidSubjectHint'), ''), (0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.pushTitle') }), bool(form.pushSummary, 'pushSummary', t('settings.fieldPushSummary'), t('settings.fieldPushSummaryHint')), bool(form.pushTurnEnd, 'pushTurnEnd', t('settings.fieldPushTurnEnd'), t('settings.fieldPushTurnEndHint')), bool(form.pushTool, 'pushTool', t('settings.fieldPushTool'), t('settings.fieldPushToolHint')), text(form.pushDebounceMs, 'pushDebounceMs', t('settings.fieldPushDebounceMs'), t('settings.fieldPushDebounceMsHint'), t('settings.invalidNonNegativeInt'), true), select(form.lang, 'lang', t('settings.fieldLang'), t('settings.fieldLangHint'), ['auto', 'zh', 'en'], (option) => (option === 'auto' ? t('settings.langAuto') : option === 'zh' ? t('settings.langZh') : t('settings.langEn'))), (0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.shareTitle') }), text(form.idleHours, 'idleHours', t('settings.fieldIdleHours'), t('settings.fieldIdleHoursHint'), t('settings.invalidHours'), true), bool(form.autoShareNewSessions, 'autoShareNewSessions', t('settings.fieldAutoShare'), t('settings.fieldAutoShareHint')), (0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.bridge.title') }), bridgeState === 'loading' && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: t('settings.bridge.loading') }), bridgeState === 'error' && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.bridge.loadFailed') }), bridgeState === 'empty' && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: t('settings.bridge.empty') }), bridgeLoad.rows.map((row) => {
|
|
18496
|
+
}, onDiscard: () => { config.discard(); }, children: [(0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.roleTitle') }), select(form.role, 'role', t('settings.role'), t('settings.roleHint'), ['host', 'client'], (option) => (option === 'host' ? t('settings.roleHost') : t('settings.roleClient'))), text(form.serverName, 'serverName', t('settings.fieldServerName'), t(clientRole ? 'settings.fieldServerNameHintClient' : 'settings.fieldServerNameHint'), t('settings.invalidName')), reloadPending && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-status-line", children: t('settings.reloadNote') }), !clientRole && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.gatewayTitle') }), text(form.host, 'host', t('settings.fieldHost'), t('settings.fieldHostHint'), '', false, t('settings.fieldHostNote')), text(form.port, 'port', t('settings.fieldPort'), t('settings.fieldPortHint'), t('settings.invalidPort'), true), text(form.trustedProxies, 'trustedProxies', t('settings.fieldTrustedProxies'), t('settings.fieldTrustedProxiesHint'), ''), text(form.rateLimit, 'rateLimit', t('settings.fieldRateLimit'), t('settings.fieldRateLimitHint'), t('settings.invalidPositiveInt'), true), text(form.vapidSubject, 'vapidSubject', t('settings.fieldVapidSubject'), t('settings.fieldVapidSubjectHint'), ''), (0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.pushTitle') }), bool(form.pushEnabled, 'pushEnabled', t('settings.fieldPushEnabled'), t('settings.fieldPushEnabledHint')), bool(form.pushSessionName, 'pushSessionName', t('settings.fieldPushSessionName'), t('settings.fieldPushSessionNameHint'), pushOff), bool(form.pushSummary, 'pushSummary', t('settings.fieldPushSummary'), t('settings.fieldPushSummaryHint'), pushOff), bool(form.pushTurnEnd, 'pushTurnEnd', t('settings.fieldPushTurnEnd'), t('settings.fieldPushTurnEndHint'), pushOff), bool(form.pushTool, 'pushTool', t('settings.fieldPushTool'), t('settings.fieldPushToolHint'), pushOff), text(form.pushDebounceMs, 'pushDebounceMs', t('settings.fieldPushDebounceMs'), t('settings.fieldPushDebounceMsHint'), t('settings.invalidNonNegativeInt'), true), select(form.lang, 'lang', t('settings.fieldLang'), t('settings.fieldLangHint'), ['auto', 'zh', 'en'], (option) => (option === 'auto' ? t('settings.langAuto') : option === 'zh' ? t('settings.langZh') : t('settings.langEn'))), (0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.shareTitle') }), text(form.idleHours, 'idleHours', t('settings.fieldIdleHours'), t('settings.fieldIdleHoursHint'), t('settings.invalidHours'), true), bool(form.autoShareNewSessions, 'autoShareNewSessions', t('settings.fieldAutoShare'), t('settings.fieldAutoShareHint')), (0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.bridge.title') }), bridgeState === 'loading' && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: t('settings.bridge.loading') }), bridgeState === 'error' && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.bridge.loadFailed') }), bridgeState === 'empty' && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: t('settings.bridge.empty') }), bridgeLoad.rows.map((row) => {
|
|
18172
18497
|
let current = {};
|
|
18173
18498
|
try {
|
|
18174
18499
|
const parsed = JSON.parse(form.pluginBridgeConsent.text);
|
|
@@ -18189,12 +18514,15 @@ function SettingsSectionPage({ config, shares, t }) {
|
|
|
18189
18514
|
: t('settings.gatewayDown') }), (0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.pairingTitle') }), (0, jsx_runtime_1.jsx)("div", { className: "zr-settings-role-cards", children: PAIR_ROLES.map((role) => ((0, jsx_runtime_1.jsxs)("label", { className: "zr-settings-role-card", "data-active": pairRole === role ? 'true' : undefined, children: [(0, jsx_runtime_1.jsxs)("span", { className: "zr-settings-role-card-title", children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: "zr-settings-pair-role", checked: pairRole === role, onChange: () => { setPairRole(role); } }), role === 'web' ? t('settings.deviceRoleWeb') : t('settings.deviceRoleDesktop')] }), (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-hint", children: role === 'web' ? t('settings.pairingWebGuide') : t('settings.pairingDesktopGuide') })] }, role))) }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-row", style: { paddingBottom: 12 }, children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", disabled: !localOps || pairBusy, onClick: () => { void runPair(); }, children: pairBusy ? t('settings.pairingGenerating') : t('settings.pairingGenerate') }), pairFailed && (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.pairingFail') })] }), shownPairing !== null && ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-settings-head-title", children: t('settings.pairingCodeLabel') }), (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-badge", children: shownPairing.role === 'web' ? t('settings.deviceRoleWeb') : t('settings.deviceRoleDesktop') }), (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-badge", "data-warn": "true", children: t('settings.pairingRemaining', { count: shownPairing.remainingSeconds }) })] }), (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-pair-code", children: shownPairing.code })] })), (0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.devicesTitle') }), (data?.devices.length ?? 0) === 0 && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: t('settings.devicesEmpty') }), data?.devices.map(deviceRow), deviceFailed && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.actionFail') }), (data?.devices.length ?? 0) > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "zr-settings-row", style: { paddingBottom: 12 }, children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: !localOps || localBusy, onClick: () => {
|
|
18190
18515
|
if (window.confirm(t('settings.revokeAllConfirm')))
|
|
18191
18516
|
void runDeviceAction({ action: 'revoke-all' });
|
|
18192
|
-
}, children: t('settings.revokeAll') }) })), (0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-settings-head-title", children: t('settings.pushTestLabel') }), (0, jsx_runtime_1.jsx)("span", { style: { flex: 1 } }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", disabled: !localOps || pushTest.state === 'busy', onClick: () => { void runPushTest(); }, children: pushTest.state === 'busy' ? t('settings.pushTestRunning') : t('settings.pushTestRun') })] }), pushTest.state === 'ok' && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: t('settings.pushTestOk', { sent: pushTest.sent, failed: pushTest.failed }) }), pushTest.state === 'fail' && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.pushTestFail') })] }), (0, jsx_runtime_1.jsx)(SharesList, { shares: shares, t: t })] })), clientRole && ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-card", children: [(0, jsx_runtime_1.
|
|
18517
|
+
}, children: t('settings.revokeAll') }) })), (0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-settings-head-title", children: t('settings.pushTestLabel') }), (0, jsx_runtime_1.jsx)("span", { style: { flex: 1 } }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", disabled: !localOps || pushTest.state === 'busy', onClick: () => { void runPushTest(); }, children: pushTest.state === 'busy' ? t('settings.pushTestRunning') : t('settings.pushTestRun') })] }), pushTest.state === 'ok' && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: t('settings.pushTestOk', { sent: pushTest.sent, failed: pushTest.failed }) }), pushTest.state === 'fail' && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.pushTestFail') })] }), (0, jsx_runtime_1.jsx)(SharesList, { shares: shares, t: t })] })), clientRole && ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-card", children: [(0, jsx_runtime_1.jsxs)("h3", { className: "zr-settings-card-title", children: [t('settings.client.connectTitle'), (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-badge", "data-invalid": clientView?.limitReached === true ? 'true' : undefined, children: t('settings.client.poolCount', { count: serverRows.length, max: maxServers }) })] }), (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", style: { paddingTop: 4 }, children: t('settings.client.poolCapNote', { max: maxServers }) }), clientView?.limitReached === true && ((0, jsx_runtime_1.jsx)("p", { className: "zr-settings-limit-alert", "data-invalid": "true", children: t('settings.client.limitReached', { count: serverRows.length, max: maxServers }) })), (0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsx)("div", { className: "zr-settings-head", children: (0, jsx_runtime_1.jsx)("label", { htmlFor: "zr-settings-client-server", children: t('settings.client.serverUrl') }) }), (0, jsx_runtime_1.jsx)("input", { id: "zr-settings-client-server", className: "zr-settings-input", type: "text", autoComplete: "off", spellCheck: false, value: pairUrl, onChange: (e) => { setPairUrl(e.currentTarget.value); setPairUrlTouched(true); } }), (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: t('settings.client.serverUrlHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsx)("div", { className: "zr-settings-head", children: (0, jsx_runtime_1.jsx)("label", { htmlFor: "zr-settings-client-code", children: t('settings.client.pairingCode') }) }), (0, jsx_runtime_1.jsx)("input", { id: "zr-settings-client-code", ref: pairCodeRef, className: "zr-settings-input zr-settings-code-input", type: "text", autoComplete: "off", spellCheck: false,
|
|
18193
18518
|
// No maxLength: the draft is normalized (uppercased, spaces and
|
|
18194
18519
|
// hyphens stripped) on every keystroke, and only the
|
|
18195
18520
|
// NORMALIZED length gates the pair button — a code pasted with
|
|
18196
18521
|
// separators must survive pasting intact.
|
|
18197
|
-
value: pairCode, onChange: (e) => { setPairCode((0, settings_form_ts_1.normalizePairingCode)(e.currentTarget.value)); } }), (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: t('settings.client.pairingCodeHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-row", style: { paddingBottom: 12 }, children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", disabled: !claimAllowed, onClick: () => { void runClaim(); }, children: claimBusy ? t('settings.client.pairing') : t('settings.client.pair') }), form.serverName.invalid && (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.client.fixDeviceName') }), claimFail !== null && (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-hint", "data-invalid": "true", children: pairFailText(claimFail, t) })
|
|
18522
|
+
value: pairCode, onChange: (e) => { setPairCode((0, settings_form_ts_1.normalizePairingCode)(e.currentTarget.value)); } }), (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: t('settings.client.pairingCodeHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-row", style: { paddingBottom: 12 }, children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", disabled: !claimAllowed, onClick: () => { void runClaim(); }, children: claimBusy ? t('settings.client.pairing') : t('settings.client.pair') }), form.serverName.invalid && (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.client.fixDeviceName') }), claimFail !== null && (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-hint", "data-invalid": "true", children: pairFailText(claimFail, t) })] }), (0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.client.serversTitle') }), serverRows.length === 0 && ((0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", style: { padding: '8px 0 12px' }, children: t('settings.client.serversEmpty', { max: maxServers }) })), serverRows.map((server) => {
|
|
18523
|
+
const badge = serverStateBadge(server, now, t);
|
|
18524
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-device", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-device-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-settings-device-name", children: server.displayName !== '' ? server.displayName : server.serverUrl }), (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-badge", "data-live": badge.tone === 'live' ? 'true' : undefined, "data-warn": badge.tone === 'warn' ? 'true' : undefined, "data-invalid": badge.tone === 'invalid' ? 'true' : undefined, children: badge.text }), (0, jsx_runtime_1.jsx)("span", { className: "zr-settings-hint", children: server.serverUrl })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-device-actions", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: entryBusy !== null || server.nextRetryAt === undefined, onClick: () => { void runEntryReconnect(server.entryId); }, children: entryBusy === server.entryId ? t('settings.client.reconnectBusy') : t('settings.client.reconnect') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: entryBusy !== null, onClick: () => { repurposeServer(server); }, children: t('settings.client.repair') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: entryBusy !== null, onClick: () => { void runUnpairEntry(server.entryId); }, children: t('settings.client.serverRemove') })] })] }, server.entryId));
|
|
18525
|
+
}), entryActionFailed && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.actionFail') }), serverRows.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "zr-settings-row", style: { paddingBottom: 12 }, children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: entryBusy !== null, onClick: () => { void runUnpairAllServers(); }, children: t('settings.client.unpairAllServers') }) })), (0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.client.statusTitle') }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-row", style: { paddingBottom: 4 }, children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-settings-status-line", style: { flex: 1 }, "data-down": clientView !== undefined && (clientView.state === 'unreachable' || clientView.state === 'unexpected' || clientView.state === 'revoked' || clientView.state === 'invalid-url' || clientView.state === 'incompatible') ? 'true' : undefined, children: clientView === undefined ? '' : clientStatusText(clientView, now, t) }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", disabled: clientView === undefined || clientView.nextRetryAt === undefined || reconnectBusy, onClick: () => { void runReconnect(); }, children: reconnectBusy ? t('settings.client.reconnectBusy') : t('settings.client.reconnect') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", disabled: clientLoad.state === 'loading', onClick: loadClientStatus, children: clientLoad.state === 'loading' ? t('settings.client.statusRefreshing') : t('settings.client.statusRefresh') })] }), form.deviceToken.configured && ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-row", style: { paddingBottom: 12 }, children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-settings-badge", children: t('settings.client.tokenSet') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: !form.available || !form.writable || unpairBusy, onClick: () => { void runUnpair(); }, children: unpairBusy ? t('settings.client.unpairBusy') : t('settings.client.unpair') })] })), unpairDone && (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", style: { paddingBottom: 12 }, children: t('settings.client.unpairDone') }), diagVisible && clientView !== undefined && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("h3", { className: "zr-settings-card-title", children: t('settings.client.diagTitle') }), legacyTopDiag && clientView.lastError !== '' && ((0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": clientView.state === 'unreachable' ? 'true' : undefined, children: t('settings.client.diagLastError', { code: clientView.lastError }) })), clientView.intercept !== undefined && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: clientView.intercept.installed
|
|
18198
18526
|
? t('settings.client.diagInterceptInstalled')
|
|
18199
18527
|
: t('settings.client.diagInterceptMissing') }), clientView.intercept.reasons.length > 0 && ((0, jsx_runtime_1.jsxs)("p", { className: "zr-settings-hint", "data-invalid": "true", children: [t('settings.client.diagInterceptDisabled'), clientView.intercept.reasons.map((reason, index) => ((0, jsx_runtime_1.jsx)("span", { className: "zr-settings-hint", children: reason }, index)))] })), clientView.intercept.recentFailures.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsx)("div", { className: "zr-settings-head", children: (0, jsx_runtime_1.jsx)("label", { children: t('settings.client.diagRecentFailures') }) }), clientView.intercept.recentFailures.map((failure, index) => ((0, jsx_runtime_1.jsxs)("p", { className: "zr-settings-hint", children: [new Date(failure.time).toLocaleString(), " \u00B7 ", failure.method !== '' ? failure.method : '—', " \u00B7 ", failure.code] }, index)))] }))] })), (dropDiag.installed || dropDiag.notInstalledReason !== undefined) && ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsx)("div", { className: "zr-settings-head", children: (0, jsx_runtime_1.jsx)("label", { children: t('settings.client.diagRemoteDrop') }) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: dropDiag.installed
|
|
18200
18528
|
? t('settings.client.diagRemoteDropOk', {
|
|
@@ -18225,9 +18553,11 @@ function SettingsSectionPage({ config, shares, t }) {
|
|
|
18225
18553
|
fileUploads: typeof probe.fileUploadsService === 'string' ? probe.fileUploadsService : '—',
|
|
18226
18554
|
});
|
|
18227
18555
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsx)("div", { className: "zr-settings-head", children: (0, jsx_runtime_1.jsx)("label", { children: t('settings.client.diagUploadRedirect') }) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: text })] }));
|
|
18228
|
-
})(), clientView.compat !== undefined && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [clientView.compat.mismatchedGroups.length > 0 && ((0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.client.diagCompatGroups', { groups: clientView.compat.mismatchedGroups.join(', ') }) })), clientView.compat.recentCalls.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsx)("div", { className: "zr-settings-head", children: (0, jsx_runtime_1.jsx)("label", { children: t('settings.client.diagCompatCalls') }) }), clientView.compat.recentCalls.map((failure, index) => ((0, jsx_runtime_1.jsxs)("p", { className: "zr-settings-hint", children: [new Date(failure.time).toLocaleString(), " \u00B7 ", failure.method !== '' ? failure.method : '—', " \u00B7 ", failure.code] }, index)))] }))] })), (clientView.pluginBridge?.length ?? 0) > 0 && clientView.pluginBridge !== undefined && ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsx)("div", { className: "zr-settings-head", children: (0, jsx_runtime_1.jsx)("label", { children: t('settings.bridge.title') }) }), clientView.pluginBridge.map((row) => ((0, jsx_runtime_1.jsxs)("p", { className: "zr-settings-hint", children: [row.plugin, " \u00B7 ", row.localVersion !== '' ? `v${row.localVersion}` : '—', " \u00B7", ' ', row.stateKey === 'settings.bridge.stateVersionMismatch'
|
|
18556
|
+
})(), legacyTopDiag && clientView.compat !== undefined && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [clientView.compat.mismatchedGroups.length > 0 && ((0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.client.diagCompatGroups', { groups: clientView.compat.mismatchedGroups.join(', ') }) })), clientView.compat.recentCalls.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsx)("div", { className: "zr-settings-head", children: (0, jsx_runtime_1.jsx)("label", { children: t('settings.client.diagCompatCalls') }) }), clientView.compat.recentCalls.map((failure, index) => ((0, jsx_runtime_1.jsxs)("p", { className: "zr-settings-hint", children: [new Date(failure.time).toLocaleString(), " \u00B7 ", failure.method !== '' ? failure.method : '—', " \u00B7 ", failure.code] }, index)))] }))] })), legacyTopDiag && (clientView.pluginBridge?.length ?? 0) > 0 && clientView.pluginBridge !== undefined && ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsx)("div", { className: "zr-settings-head", children: (0, jsx_runtime_1.jsx)("label", { children: t('settings.bridge.title') }) }), clientView.pluginBridge.map((row) => ((0, jsx_runtime_1.jsxs)("p", { className: "zr-settings-hint", children: [row.plugin, " \u00B7 ", row.localVersion !== '' ? `v${row.localVersion}` : '—', " \u00B7", ' ', row.stateKey === 'settings.bridge.stateVersionMismatch'
|
|
18229
18557
|
? t(row.stateKey, { version: row.serverVersion ?? '?' })
|
|
18230
|
-
: t(row.stateKey)] }, row.plugin)))] })), clientView.pluginRouteIntercept !== undefined && !clientView.pluginRouteIntercept.installed && ((0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.bridge.interceptOff', { reason: clientView.pluginRouteIntercept.reasons.join('; ') || '—' }) }))
|
|
18558
|
+
: t(row.stateKey)] }, row.plugin)))] })), clientView.pluginRouteIntercept !== undefined && !clientView.pluginRouteIntercept.installed && ((0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.bridge.interceptOff', { reason: clientView.pluginRouteIntercept.reasons.join('; ') || '—' }) })), hasServerDiag && ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsx)("div", { className: "zr-settings-head", children: (0, jsx_runtime_1.jsx)("label", { children: t('settings.client.serverDiagTitle') }) }), serverRows.filter(serverHasDiag).map((server) => ((0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-device", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-settings-device-name", children: server.displayName !== '' ? server.displayName : server.serverUrl }), server.lastError !== '' && ((0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": server.state === 'unreachable' ? 'true' : undefined, children: t('settings.client.diagLastError', { code: server.lastError }) })), server.compat !== undefined && server.compat.mismatchedGroups.length > 0 && ((0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", "data-invalid": "true", children: t('settings.client.diagCompatGroups', { groups: server.compat.mismatchedGroups.join(', ') }) })), server.compat?.recentCalls.map((failure, index) => ((0, jsx_runtime_1.jsxs)("p", { className: "zr-settings-hint", children: [t('settings.client.diagCompatCalls'), " \u00B7 ", new Date(failure.time).toLocaleString(), " \u00B7 ", failure.method !== '' ? failure.method : '—', " \u00B7 ", failure.code] }, index))), server.pluginBridge.map((row) => ((0, jsx_runtime_1.jsxs)("p", { className: "zr-settings-hint", children: [t('settings.bridge.title'), " \u00B7 ", row.plugin, " \u00B7 ", row.localVersion !== '' ? `v${row.localVersion}` : '—', " \u00B7", ' ', row.stateKey === 'settings.bridge.stateVersionMismatch'
|
|
18559
|
+
? t(row.stateKey, { version: row.serverVersion ?? '?' })
|
|
18560
|
+
: t(row.stateKey)] }, row.plugin)))] }, server.entryId)))] }))] }))] })), (0, jsx_runtime_1.jsx)("div", { className: "zr-settings-card", children: (0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-settings-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-settings-head-title", children: t('settings.debug.title') }), (0, jsx_runtime_1.jsx)("span", { style: { flex: 1 } }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", disabled: debugBusy, onClick: () => { void runDebugExport(); }, children: debugBusy ? t('settings.debug.exporting') : t('settings.debug.export') })] }), (0, jsx_runtime_1.jsx)("p", { className: "zr-settings-hint", children: t('settings.debug.hint') })] }) })] }));
|
|
18231
18561
|
}
|
|
18232
18562
|
/** Display name of a value's source layer for the invalid-saved-value note. */
|
|
18233
18563
|
function sourceName(source, t) {
|
|
@@ -18342,6 +18672,9 @@ exports.SETTINGS_CSS = `
|
|
|
18342
18672
|
[data-zen-remote="settings"] .zr-settings-field + .zr-settings-field {
|
|
18343
18673
|
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
18344
18674
|
}
|
|
18675
|
+
[data-zen-remote="settings"] .zr-settings-field[data-disabled="true"] {
|
|
18676
|
+
opacity: 0.5;
|
|
18677
|
+
}
|
|
18345
18678
|
[data-zen-remote="settings"] .zr-settings-head {
|
|
18346
18679
|
display: flex;
|
|
18347
18680
|
align-items: center;
|
|
@@ -18517,6 +18850,21 @@ exports.SETTINGS_CSS = `
|
|
|
18517
18850
|
[data-zen-remote="settings"] .zr-settings-badge[data-live="true"] {
|
|
18518
18851
|
color: var(--dsw-alias-state-success-primary, #16a34a);
|
|
18519
18852
|
}
|
|
18853
|
+
[data-zen-remote="settings"] .zr-settings-badge[data-invalid="true"] {
|
|
18854
|
+
color: var(--dsw-alias-state-error-primary, #d5304f);
|
|
18855
|
+
}
|
|
18856
|
+
/* MS-7: the full-pool alert must be impossible to miss — red, bold, framed. */
|
|
18857
|
+
[data-zen-remote="settings"] .zr-settings-limit-alert,
|
|
18858
|
+
[data-zen-remote="settings"] .zr-settings-limit-alert[data-invalid="true"] {
|
|
18859
|
+
font-size: 13px;
|
|
18860
|
+
font-weight: 700;
|
|
18861
|
+
line-height: 1.5;
|
|
18862
|
+
color: var(--dsw-alias-state-error-primary, #d5304f);
|
|
18863
|
+
border: 1px solid var(--dsw-alias-state-error-primary, #d5304f);
|
|
18864
|
+
border-radius: 8px;
|
|
18865
|
+
padding: 8px 12px;
|
|
18866
|
+
margin: 8px 0 4px;
|
|
18867
|
+
}
|
|
18520
18868
|
[data-zen-remote="settings"] .zr-settings-footer {
|
|
18521
18869
|
display: flex;
|
|
18522
18870
|
align-items: center;
|
|
@@ -18896,6 +19244,12 @@ __modules["client-data/wizard.js"] = function (require, module, exports) {
|
|
|
18896
19244
|
* row layer applies the same rule when the fields land there, so a client
|
|
18897
19245
|
* that accepts something the resolver would silently drop would only set
|
|
18898
19246
|
* the user up for a mystery.
|
|
19247
|
+
*
|
|
19248
|
+
* MS-6 adds the paired-pool overview pieces: the pool count is a first-class
|
|
19249
|
+
* "configured" signal (MS-5 pairs straight into the pool store, not the
|
|
19250
|
+
* row), and {@link deriveClientOverviewSummary} derives the overview card's
|
|
19251
|
+
* counters and its two lights (connection health / pool fill) off the
|
|
19252
|
+
* `client/status` pool list.
|
|
18899
19253
|
*/
|
|
18900
19254
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18901
19255
|
exports.SNIPPET_KINDS = exports.WIZARD_TUNNEL_STATUS_ROUTE = exports.WIZARD_TUNNEL_STOP_ROUTE = exports.WIZARD_TUNNEL_SETUP_ROUTE = exports.WIZARD_TUNNEL_CHECK_ROUTE = exports.WIZARD_CADDY_STATUS_ROUTE = exports.WIZARD_CADDY_STOP_ROUTE = exports.WIZARD_CADDY_START_ROUTE = exports.WIZARD_TUNNEL_TOKEN_ROUTE = exports.WIZARD_CF_TOKEN_ROUTE = exports.WIZARD_VERIFY_ROUTE = exports.WIZARD_STATUS_ROUTE = void 0;
|
|
@@ -18911,12 +19265,15 @@ exports.isValidPort = isValidPort;
|
|
|
18911
19265
|
exports.publicUrlFrom = publicUrlFrom;
|
|
18912
19266
|
exports.configureReadiness = configureReadiness;
|
|
18913
19267
|
exports.deriveWizardLights = deriveWizardLights;
|
|
19268
|
+
exports.deriveClientOverviewSummary = deriveClientOverviewSummary;
|
|
18914
19269
|
exports.deriveClientWizardLights = deriveClientWizardLights;
|
|
19270
|
+
exports.deriveClientServerLights = deriveClientServerLights;
|
|
18915
19271
|
exports.pairingSecondsLeft = pairingSecondsLeft;
|
|
18916
19272
|
exports.caddyFailureCopy = caddyFailureCopy;
|
|
18917
19273
|
exports.nginxSnippetText = nginxSnippetText;
|
|
18918
19274
|
exports.caddySnippetText = caddySnippetText;
|
|
18919
19275
|
exports.luckyStepTexts = luckyStepTexts;
|
|
19276
|
+
const settings_form_ts_1 = require("./client-data/settings-form.js");
|
|
18920
19277
|
/** Wizard route constants — mirror of src/wizard/routes.ts. */
|
|
18921
19278
|
exports.WIZARD_STATUS_ROUTE = '/_dsh/zen-remote/admin/wizard/status';
|
|
18922
19279
|
exports.WIZARD_VERIFY_ROUTE = '/_dsh/zen-remote/admin/wizard/verify';
|
|
@@ -18967,9 +19324,16 @@ function initialStep(status) {
|
|
|
18967
19324
|
}
|
|
18968
19325
|
/**
|
|
18969
19326
|
* Whether the inline overview card should render the configured status
|
|
18970
|
-
* dashboard instead of the first-time setup prompt.
|
|
19327
|
+
* dashboard instead of the first-time setup prompt. MS-6: the paired pool's
|
|
19328
|
+
* size is the client's first-class signal — since MS-5 a claim lands in the
|
|
19329
|
+
* pool store and never touches the row's `serverUrl` / `deviceToken`, so a
|
|
19330
|
+
* freshly multi-paired machine with an empty row must still read as
|
|
19331
|
+
* configured. `> 0` answers true no matter what the other inputs say; the
|
|
19332
|
+
* field is optional and an omitted one (or a zero) changes nothing.
|
|
18971
19333
|
*/
|
|
18972
19334
|
function isWizardConfigured(input) {
|
|
19335
|
+
if ((input.pairedServerCount ?? 0) > 0)
|
|
19336
|
+
return true;
|
|
18973
19337
|
if (input.effectiveClient) {
|
|
18974
19338
|
return input.tokenConfigured || input.clientServerUrl.trim() !== '' || input.clientState === 'connected';
|
|
18975
19339
|
}
|
|
@@ -19100,6 +19464,46 @@ const CLIENT_FAIL_STATES = new Set([
|
|
|
19100
19464
|
'invalid-url',
|
|
19101
19465
|
'unexpected',
|
|
19102
19466
|
]);
|
|
19467
|
+
/**
|
|
19468
|
+
* The view states that will NOT come back on their own (MS-6): a revoked
|
|
19469
|
+
* token, an incompatible interface, an address that no longer parses — each
|
|
19470
|
+
* needs the settings page before that server serves again. They turn the
|
|
19471
|
+
* overview's connection light red; a merely `unreachable` server is mid-retry
|
|
19472
|
+
* and stays amber.
|
|
19473
|
+
*/
|
|
19474
|
+
const CLIENT_DEAD_STATES = new Set(['revoked', 'incompatible', 'invalid-url']);
|
|
19475
|
+
/**
|
|
19476
|
+
* Derive the sub-client overview summary off one derived pool list: green
|
|
19477
|
+
* only when EVERY paired server is connected; red when any server is in a
|
|
19478
|
+
* state that will not recover on its own (revoked / incompatible /
|
|
19479
|
+
* invalid-url) or when the pool is FULL — the cap is a servers fact, so a
|
|
19480
|
+
* full pool of healthy connections still shows a red servers light while its
|
|
19481
|
+
* connection light stays green; amber for the rest (nothing paired yet, or
|
|
19482
|
+
* servers the retry ladder is still working on).
|
|
19483
|
+
*/
|
|
19484
|
+
function deriveClientOverviewSummary(servers, maxServers = settings_form_ts_1.MAX_PAIRED_SERVERS) {
|
|
19485
|
+
const totalCount = servers.length;
|
|
19486
|
+
let onlineCount = 0;
|
|
19487
|
+
let revokedCount = 0;
|
|
19488
|
+
let deadCount = 0;
|
|
19489
|
+
for (const server of servers) {
|
|
19490
|
+
if (server.state === 'connected')
|
|
19491
|
+
onlineCount += 1;
|
|
19492
|
+
if (server.state === 'revoked')
|
|
19493
|
+
revokedCount += 1;
|
|
19494
|
+
if (CLIENT_DEAD_STATES.has(server.state))
|
|
19495
|
+
deadCount += 1;
|
|
19496
|
+
}
|
|
19497
|
+
const offlineCount = totalCount - onlineCount - revokedCount;
|
|
19498
|
+
const limitReached = totalCount >= maxServers;
|
|
19499
|
+
const connectionLight = totalCount === 0
|
|
19500
|
+
? 'pending'
|
|
19501
|
+
: onlineCount === totalCount
|
|
19502
|
+
? 'pass'
|
|
19503
|
+
: deadCount > 0 ? 'fail' : 'pending';
|
|
19504
|
+
const serversLight = limitReached ? 'fail' : totalCount > 0 ? 'pass' : 'pending';
|
|
19505
|
+
return { totalCount, onlineCount, offlineCount, revokedCount, maxServers, limitReached, connectionLight, serversLight };
|
|
19506
|
+
}
|
|
19103
19507
|
/**
|
|
19104
19508
|
* Derive the three status lights for the desktop sub-client verification
|
|
19105
19509
|
* step (`role === 'client'`).
|
|
@@ -19113,6 +19517,27 @@ function deriveClientWizardLights(input) {
|
|
|
19113
19517
|
sync: connected ? (input.interceptInstalled === false ? 'fail' : 'pass') : 'pending',
|
|
19114
19518
|
};
|
|
19115
19519
|
}
|
|
19520
|
+
/**
|
|
19521
|
+
* The wizard step-3's three lights for ONE selected paired server (MS-8):
|
|
19522
|
+
* the credential is green by the row's own existence (the token lives in the
|
|
19523
|
+
* pool store, never in the row) and red only when the server refused it
|
|
19524
|
+
* (`revoked`); the connection follows the same fail-set the global lights
|
|
19525
|
+
* use (an offline server mid-retry is red here too — the step is staring at
|
|
19526
|
+
* THAT one server, not the pool); sync is green on a connected row whose
|
|
19527
|
+
* interface groups all match, red on a compat mismatch, amber otherwise.
|
|
19528
|
+
* `undefined` (nothing paired, or nothing selected yet) keeps all three
|
|
19529
|
+
* amber.
|
|
19530
|
+
*/
|
|
19531
|
+
function deriveClientServerLights(server) {
|
|
19532
|
+
if (server === undefined)
|
|
19533
|
+
return { credential: 'pending', connection: 'pending', sync: 'pending' };
|
|
19534
|
+
const connected = server.state === 'connected';
|
|
19535
|
+
return {
|
|
19536
|
+
credential: server.state === 'revoked' ? 'fail' : 'pass',
|
|
19537
|
+
connection: connected ? 'pass' : CLIENT_FAIL_STATES.has(server.state) ? 'fail' : 'pending',
|
|
19538
|
+
sync: connected ? ((server.compat?.mismatchedGroups.length ?? 0) > 0 ? 'fail' : 'pass') : 'pending',
|
|
19539
|
+
};
|
|
19540
|
+
}
|
|
19116
19541
|
/** Pairing-code countdown, whole seconds, never negative. */
|
|
19117
19542
|
function pairingSecondsLeft(expiresAt, now) {
|
|
19118
19543
|
return Math.max(0, Math.ceil((expiresAt - now) / 1000));
|
|
@@ -19152,8 +19577,11 @@ function nginxSnippetText(input) {
|
|
|
19152
19577
|
const parts = valid(input);
|
|
19153
19578
|
if (parts === null)
|
|
19154
19579
|
return null;
|
|
19580
|
+
const headerComment = input.lang === 'en'
|
|
19581
|
+
? '# Place this in the http {} block (skip if you already have this map)'
|
|
19582
|
+
: '# 这段放在 http {} 层(若已有同名 map 则跳过)';
|
|
19155
19583
|
return [
|
|
19156
|
-
|
|
19584
|
+
headerComment,
|
|
19157
19585
|
'map $http_upgrade $connection_upgrade {',
|
|
19158
19586
|
' default upgrade;',
|
|
19159
19587
|
" '' close;",
|
|
@@ -19192,6 +19620,13 @@ function luckyStepTexts(input) {
|
|
|
19192
19620
|
const parts = valid(input);
|
|
19193
19621
|
if (parts === null)
|
|
19194
19622
|
return null;
|
|
19623
|
+
if (input.lang === 'en') {
|
|
19624
|
+
return [
|
|
19625
|
+
'In Lucky "Web Service", create a new rule listening on port 443 and bind your domain.',
|
|
19626
|
+
`Set the backend (reverse proxy target) to http://${parts.upstream}:${parts.port} and enable WebSocket support.`,
|
|
19627
|
+
'Turn on the catch-all switch to allow all paths, then save.',
|
|
19628
|
+
];
|
|
19629
|
+
}
|
|
19195
19630
|
return [
|
|
19196
19631
|
'在 Lucky 的「Web 服务」里新建一条规则,监听 443 端口,绑定你的域名。',
|
|
19197
19632
|
`后端(反代目标)填 http://${parts.upstream}:${parts.port},开启 WebSocket 支持。`,
|
|
@@ -19292,7 +19727,8 @@ const wizard_ts_1 = require("./client-data/wizard.js");
|
|
|
19292
19727
|
const WizardCard_tsx_1 = require("./client/wizard/WizardCard.js");
|
|
19293
19728
|
function ProxyCard({ t, domain, upstreamPort, onCheck, onClose }) {
|
|
19294
19729
|
const [tab, setTab] = (0, react_1.useState)('nginx');
|
|
19295
|
-
const
|
|
19730
|
+
const lang = t('wizard.card.close') === 'Close' ? 'en' : 'zh';
|
|
19731
|
+
const input = { domain, upstreamHost: '127.0.0.1', upstreamPort, lang };
|
|
19296
19732
|
const text = tab === 'nginx'
|
|
19297
19733
|
? (0, wizard_ts_1.nginxSnippetText)(input)
|
|
19298
19734
|
: tab === 'caddy' ? (0, wizard_ts_1.caddySnippetText)(input) : ((0, wizard_ts_1.luckyStepTexts)(input) ?? []).join('\n');
|
|
@@ -19407,21 +19843,6 @@ const PATH_OPTIONS = [
|
|
|
19407
19843
|
['cloudflared', 'wizard.path.cloudflared', 'wizard.path.cloudflaredDesc'],
|
|
19408
19844
|
['lan', 'wizard.path.lan', 'wizard.path.lanDesc'],
|
|
19409
19845
|
];
|
|
19410
|
-
function clientStatusCopy(view, now, t) {
|
|
19411
|
-
const line = (0, settings_form_ts_1.clientStatusLineOf)(view, now);
|
|
19412
|
-
switch (line.kind) {
|
|
19413
|
-
case 'connectedName': return t('settings.client.statusConnectedName', { serverName: line.serverName });
|
|
19414
|
-
case 'connected': return t('settings.client.statusConnected', { serverUrl: line.serverUrl });
|
|
19415
|
-
case 'offlineRetry': return t('settings.client.statusOfflineRetry', { seconds: line.seconds });
|
|
19416
|
-
case 'offlineRetrySoon': return t('settings.client.statusOfflineRetrySoon');
|
|
19417
|
-
case 'unreachable': return t('settings.client.statusUnreachable');
|
|
19418
|
-
case 'revoked': return t('settings.client.statusRevoked');
|
|
19419
|
-
case 'incompatible': return t('settings.client.statusIncompatible');
|
|
19420
|
-
case 'unexpected': return t('settings.client.statusUnexpected');
|
|
19421
|
-
case 'invalidUrl': return t('settings.client.statusInvalidUrl');
|
|
19422
|
-
default: return t('settings.client.statusUnpaired');
|
|
19423
|
-
}
|
|
19424
|
-
}
|
|
19425
19846
|
function claimFailCopy(code, retryAfterMs, t) {
|
|
19426
19847
|
switch (code) {
|
|
19427
19848
|
case 'invalid': return t('settings.client.failInvalid');
|
|
@@ -19433,9 +19854,43 @@ function claimFailCopy(code, retryAfterMs, t) {
|
|
|
19433
19854
|
}
|
|
19434
19855
|
case 'unreachable': return t('settings.client.failUnreachable');
|
|
19435
19856
|
case 'role-mismatch': return t('settings.client.failRoleMismatch');
|
|
19857
|
+
case 'limit-reached': return t('settings.client.failLimitReached');
|
|
19436
19858
|
default: return t('settings.client.failUnexpected');
|
|
19437
19859
|
}
|
|
19438
19860
|
}
|
|
19861
|
+
/** The verdicts that will not recover on their own (MS-8): a pool row's dot
|
|
19862
|
+
* turns red on these, amber on everything else, green only when connected. */
|
|
19863
|
+
const SERVER_DEAD_STATES = new Set(['revoked', 'incompatible', 'invalid-url']);
|
|
19864
|
+
/** One paired server's dot state in the management surfaces (MS-8). */
|
|
19865
|
+
function serverDotState(state) {
|
|
19866
|
+
if (state === 'connected')
|
|
19867
|
+
return 'pass';
|
|
19868
|
+
return SERVER_DEAD_STATES.has(state) ? 'fail' : 'pending';
|
|
19869
|
+
}
|
|
19870
|
+
/**
|
|
19871
|
+
* One paired server's badge copy + tone (MS-8): the settings page's
|
|
19872
|
+
* per-server wording on the same keys, so the wizard's management modal and
|
|
19873
|
+
* the settings block cannot drift apart in what a state is CALLED.
|
|
19874
|
+
*/
|
|
19875
|
+
function serverBadgeCopy(server, now, t) {
|
|
19876
|
+
switch (server.state) {
|
|
19877
|
+
case 'connected': return { text: t('settings.client.serverStateConnected'), tone: 'live' };
|
|
19878
|
+
case 'unreachable':
|
|
19879
|
+
case 'unexpected': {
|
|
19880
|
+
if (server.nextRetryAt === undefined)
|
|
19881
|
+
return { text: t('settings.client.serverStateOffline'), tone: 'warn' };
|
|
19882
|
+
const seconds = Math.max(0, Math.ceil((server.nextRetryAt - now) / 1000));
|
|
19883
|
+
return {
|
|
19884
|
+
text: seconds > 0 ? t('settings.client.serverStateRetry', { seconds }) : t('settings.client.serverStateRetrySoon'),
|
|
19885
|
+
tone: 'warn',
|
|
19886
|
+
};
|
|
19887
|
+
}
|
|
19888
|
+
case 'revoked': return { text: t('settings.client.serverStateRevoked'), tone: 'invalid' };
|
|
19889
|
+
case 'incompatible': return { text: t('settings.client.serverStateIncompatible'), tone: 'invalid' };
|
|
19890
|
+
case 'invalid-url': return { text: t('settings.client.serverStateInvalidUrl'), tone: 'invalid' };
|
|
19891
|
+
default: return { text: t('settings.client.serverStateUnpaired'), tone: undefined };
|
|
19892
|
+
}
|
|
19893
|
+
}
|
|
19439
19894
|
function GatewayWizard(props) {
|
|
19440
19895
|
const { t, config } = props;
|
|
19441
19896
|
const [modalOpen, setModalOpen] = (0, react_1.useState)(false);
|
|
@@ -19506,8 +19961,14 @@ function GatewayWizard(props) {
|
|
|
19506
19961
|
const [clientClaimFail, setClientClaimFail] = (0, react_1.useState)(null);
|
|
19507
19962
|
const [clientView, setClientView] = (0, react_1.useState)();
|
|
19508
19963
|
const [clientLoading, setClientLoading] = (0, react_1.useState)(false);
|
|
19509
|
-
const [clientReconnectBusy, setClientReconnectBusy] = (0, react_1.useState)(false);
|
|
19510
19964
|
const [clientUnpairBusy, setClientUnpairBusy] = (0, react_1.useState)(false);
|
|
19965
|
+
// MS-8: the paired-server pool's management surfaces — the 已配对服务端
|
|
19966
|
+
// 管理弹窗, the entry the wizard's step 3 is staring at, and the per-entry
|
|
19967
|
+
// action busy/failed markers both the modal and step 3 share.
|
|
19968
|
+
const [serversModalOpen, setServersModalOpen] = (0, react_1.useState)(false);
|
|
19969
|
+
const [activeClientEntryId, setActiveClientEntryId] = (0, react_1.useState)();
|
|
19970
|
+
const [serverBusyEntry, setServerBusyEntry] = (0, react_1.useState)(null);
|
|
19971
|
+
const [serverActionFailed, setServerActionFailed] = (0, react_1.useState)(false);
|
|
19511
19972
|
// The saved role, live.
|
|
19512
19973
|
const roleSnapshot = (0, react_1.useSyncExternalStore)((0, react_1.useCallback)((cb) => config.subscribe(cb), [config]), () => config.getSnapshot());
|
|
19513
19974
|
const savedClient = config.savedRoleIsClient();
|
|
@@ -19637,9 +20098,9 @@ function GatewayWizard(props) {
|
|
|
19637
20098
|
setDeviceBusy(false);
|
|
19638
20099
|
}
|
|
19639
20100
|
};
|
|
19640
|
-
// Close the wizard modal or
|
|
20101
|
+
// Close the wizard modal, devices modal or servers modal on Escape when no sub-card is open and not mid-save.
|
|
19641
20102
|
(0, react_1.useEffect)(() => {
|
|
19642
|
-
if ((!modalOpen && !devicesModalOpen) || card !== undefined)
|
|
20103
|
+
if ((!modalOpen && !devicesModalOpen && !serversModalOpen) || card !== undefined)
|
|
19643
20104
|
return;
|
|
19644
20105
|
const onKey = (event) => {
|
|
19645
20106
|
if (event.key === 'Escape' && !savingConfig && !clientClaimBusy && !deviceBusy) {
|
|
@@ -19651,12 +20112,16 @@ function GatewayWizard(props) {
|
|
|
19651
20112
|
setDevicesModalOpen(false);
|
|
19652
20113
|
return;
|
|
19653
20114
|
}
|
|
20115
|
+
if (serversModalOpen) {
|
|
20116
|
+
setServersModalOpen(false);
|
|
20117
|
+
return;
|
|
20118
|
+
}
|
|
19654
20119
|
setModalOpen(false);
|
|
19655
20120
|
}
|
|
19656
20121
|
};
|
|
19657
20122
|
document.addEventListener('keydown', onKey);
|
|
19658
20123
|
return () => document.removeEventListener('keydown', onKey);
|
|
19659
|
-
}, [modalOpen, devicesModalOpen, card, savingConfig, clientClaimBusy, deviceBusy, renamingDevice]);
|
|
20124
|
+
}, [modalOpen, devicesModalOpen, serversModalOpen, card, savingConfig, clientClaimBusy, deviceBusy, renamingDevice]);
|
|
19660
20125
|
// Seed the entry step AND the configure forms from the FIRST ready
|
|
19661
20126
|
// wizard status on a host deployment.
|
|
19662
20127
|
const seededRef = (0, react_1.useRef)(false);
|
|
@@ -20016,12 +20481,12 @@ function GatewayWizard(props) {
|
|
|
20016
20481
|
if (wizardStatus?.lanAddress !== undefined && wizardStatus.lanAddress !== '') {
|
|
20017
20482
|
return `http://${wizardStatus.lanAddress}:${port}`;
|
|
20018
20483
|
}
|
|
20019
|
-
return
|
|
20020
|
-
}, [path, serverUrl, readiness, wizardStatus]);
|
|
20484
|
+
return t('wizard.desktop.addressFallback', { port: String(port) });
|
|
20485
|
+
}, [path, serverUrl, readiness, wizardStatus, t]);
|
|
20021
20486
|
const copyDesktopInfo = async () => {
|
|
20022
20487
|
if (desktopPairing === undefined)
|
|
20023
20488
|
return;
|
|
20024
|
-
const text =
|
|
20489
|
+
const text = t('wizard.desktop.copiedText', { address: resolvedHostEndpoint, code: desktopPairing.code });
|
|
20025
20490
|
try {
|
|
20026
20491
|
await navigator.clipboard?.writeText(text);
|
|
20027
20492
|
setDesktopCopied(true);
|
|
@@ -20049,6 +20514,14 @@ function GatewayWizard(props) {
|
|
|
20049
20514
|
setFormError('bad-client-code');
|
|
20050
20515
|
return;
|
|
20051
20516
|
}
|
|
20517
|
+
// MS-8: a NEW address at a full pool is refused locally (the button is
|
|
20518
|
+
// gated too — this backs it up); an already-paired address still
|
|
20519
|
+
// re-pairs its entry in place, which is how a revoked token at 8/8
|
|
20520
|
+
// gets back in.
|
|
20521
|
+
if (claimGate.blockedByLimit) {
|
|
20522
|
+
setClientClaimFail({ code: 'limit-reached' });
|
|
20523
|
+
return;
|
|
20524
|
+
}
|
|
20052
20525
|
setClientClaimBusy(true);
|
|
20053
20526
|
try {
|
|
20054
20527
|
// If the deployment is currently running as 'host', switch role to 'client'
|
|
@@ -20070,18 +20543,29 @@ function GatewayWizard(props) {
|
|
|
20070
20543
|
const response = await fetch(settings_form_ts_1.CLIENT_CLAIM_ROUTE, {
|
|
20071
20544
|
method: 'POST',
|
|
20072
20545
|
headers: { 'Content-Type': 'application/json' },
|
|
20546
|
+
// MS-5 pool mode: the route reads `name` (the settings page's field).
|
|
20073
20547
|
body: JSON.stringify({
|
|
20074
20548
|
serverUrl: trimmedUrl,
|
|
20075
20549
|
code: normalizedCode,
|
|
20076
|
-
deviceName,
|
|
20550
|
+
name: deviceName,
|
|
20077
20551
|
}),
|
|
20078
20552
|
});
|
|
20079
20553
|
const body = (await response.json());
|
|
20080
|
-
if (body?.ok === true
|
|
20081
|
-
|
|
20082
|
-
|
|
20083
|
-
|
|
20084
|
-
|
|
20554
|
+
if (body?.ok === true) {
|
|
20555
|
+
if (typeof body.entryId === 'string' && body.entryId !== '') {
|
|
20556
|
+
// Pool mode: the pairing landed straight in the pool store (the
|
|
20557
|
+
// token never rides the answer) — remember WHICH entry the wizard
|
|
20558
|
+
// just touched so step 3 opens on it.
|
|
20559
|
+
setActiveClientEntryId(body.entryId);
|
|
20560
|
+
}
|
|
20561
|
+
else if (typeof body.token === 'string' && body.token !== '') {
|
|
20562
|
+
// Legacy single-server route: the token rides the answer and the
|
|
20563
|
+
// row is the store.
|
|
20564
|
+
const urlToWrite = typeof body.serverUrl === 'string' && body.serverUrl !== '' ? body.serverUrl : trimmedUrl;
|
|
20565
|
+
const echoed = typeof body.deviceName === 'string' && body.deviceName.trim() !== '' ? body.deviceName.trim() : deviceName;
|
|
20566
|
+
await config.writeClientPairing(urlToWrite, body.token, echoed);
|
|
20567
|
+
}
|
|
20568
|
+
if ((0, settings_form_ts_1.shouldReloadAfterClientActivation)({
|
|
20085
20569
|
wasEffectiveClient,
|
|
20086
20570
|
nowClientRole: true,
|
|
20087
20571
|
savedOrPaired: true,
|
|
@@ -20112,32 +20596,42 @@ function GatewayWizard(props) {
|
|
|
20112
20596
|
setClientClaimBusy(false);
|
|
20113
20597
|
}
|
|
20114
20598
|
};
|
|
20115
|
-
const
|
|
20116
|
-
|
|
20599
|
+
const runClientUnpair = async () => {
|
|
20600
|
+
setClientUnpairBusy(true);
|
|
20117
20601
|
try {
|
|
20118
|
-
await
|
|
20602
|
+
await config.clearDeviceToken();
|
|
20119
20603
|
await loadClientStatus();
|
|
20604
|
+
setStep('configure');
|
|
20120
20605
|
}
|
|
20121
20606
|
catch {
|
|
20122
20607
|
// ignore
|
|
20123
20608
|
}
|
|
20124
20609
|
finally {
|
|
20125
|
-
|
|
20610
|
+
setClientUnpairBusy(false);
|
|
20126
20611
|
}
|
|
20127
20612
|
};
|
|
20128
|
-
|
|
20129
|
-
|
|
20613
|
+
// MS-8: one paired server's management action — the reconnect route's
|
|
20614
|
+
// `{ entryId }` shape or the unpair route's. The status refresh runs
|
|
20615
|
+
// either way so the surface re-renders on the pool's verdict.
|
|
20616
|
+
const runServerAction = async (route, entryId, payload) => {
|
|
20617
|
+
setServerBusyEntry(entryId);
|
|
20618
|
+
setServerActionFailed(false);
|
|
20130
20619
|
try {
|
|
20131
|
-
await
|
|
20132
|
-
|
|
20133
|
-
|
|
20620
|
+
const response = await fetch(route, {
|
|
20621
|
+
method: 'POST',
|
|
20622
|
+
headers: { 'Content-Type': 'application/json' },
|
|
20623
|
+
body: JSON.stringify(payload),
|
|
20624
|
+
});
|
|
20625
|
+
if (!response.ok)
|
|
20626
|
+
setServerActionFailed(true);
|
|
20134
20627
|
}
|
|
20135
20628
|
catch {
|
|
20136
|
-
|
|
20629
|
+
setServerActionFailed(true);
|
|
20137
20630
|
}
|
|
20138
20631
|
finally {
|
|
20139
|
-
|
|
20632
|
+
setServerBusyEntry(null);
|
|
20140
20633
|
}
|
|
20634
|
+
await loadClientStatus();
|
|
20141
20635
|
};
|
|
20142
20636
|
// Managed-Caddy & Tunnel controls (GW-13a / GW-13b): keyed strictly off the
|
|
20143
20637
|
// currently selected `path` so switching away from Tunnel or Caddy never
|
|
@@ -20269,6 +20763,22 @@ function GatewayWizard(props) {
|
|
|
20269
20763
|
clientState: clientView?.state,
|
|
20270
20764
|
interceptInstalled: clientView?.intercept?.installed,
|
|
20271
20765
|
});
|
|
20766
|
+
// MS-8: the paired-server pool as the overview grid, wizard steps 2-3 and
|
|
20767
|
+
// the management modal all read it. The step-3 selection falls back to the
|
|
20768
|
+
// first row (pairing order) when nothing is recorded yet or the recorded
|
|
20769
|
+
// entry was removed.
|
|
20770
|
+
const serverRows = clientView?.servers ?? [];
|
|
20771
|
+
const poolSummary = (0, wizard_ts_1.deriveClientOverviewSummary)(serverRows, clientView?.maxServers);
|
|
20772
|
+
const activeServer = serverRows.find((server) => server.entryId === activeClientEntryId) ?? serverRows[0];
|
|
20773
|
+
const serverLights = (0, wizard_ts_1.deriveClientServerLights)(activeServer);
|
|
20774
|
+
// The pairing form's cap gate (MS-8): blockedByLimit is true only for a NEW
|
|
20775
|
+
// address at a full pool — an already-paired address never trips it.
|
|
20776
|
+
const claimGate = (0, settings_form_ts_1.canSubmitClientPairing)({
|
|
20777
|
+
pairUrl: clientServerUrl,
|
|
20778
|
+
pairCode: clientPairCode,
|
|
20779
|
+
servers: serverRows,
|
|
20780
|
+
...(clientView !== undefined ? { maxServers: clientView.maxServers } : {}),
|
|
20781
|
+
});
|
|
20272
20782
|
const verified = poll?.result === 'pass';
|
|
20273
20783
|
const qr = startResult?.ok === true ? (0, qr_ts_1.qrMatrix)(startResult.publicUrl) : null;
|
|
20274
20784
|
const stepLabel = (name) => t(name === 'pick' ? 'wizard.step.pick' : name === 'configure' ? 'wizard.step.configure' : 'wizard.step.verify');
|
|
@@ -20291,9 +20801,16 @@ function GatewayWizard(props) {
|
|
|
20291
20801
|
tokenConfigured: roleSnapshot.deviceToken.configured,
|
|
20292
20802
|
clientServerUrl,
|
|
20293
20803
|
clientState: clientView?.state,
|
|
20804
|
+
pairedServerCount: serverRows.length,
|
|
20294
20805
|
});
|
|
20295
20806
|
const statusSummary = (() => {
|
|
20296
20807
|
if (effectiveClient) {
|
|
20808
|
+
if (poolSummary.totalCount > 0) {
|
|
20809
|
+
return {
|
|
20810
|
+
state: (poolSummary.onlineCount > 0 ? 'pass' : 'pending'),
|
|
20811
|
+
text: `${t('wizard.role.client')} · ${t('wizard.overview.clientOnlineCount', { online: poolSummary.onlineCount, total: poolSummary.totalCount })}`,
|
|
20812
|
+
};
|
|
20813
|
+
}
|
|
20297
20814
|
if (clientView?.state === 'connected') {
|
|
20298
20815
|
const label = clientView.serverName !== '' ? clientView.serverName : (clientView.serverUrl || t('wizard.role.client'));
|
|
20299
20816
|
return { state: 'pass', text: `${t('wizard.role.client')} · ${label}` };
|
|
@@ -20328,9 +20845,41 @@ function GatewayWizard(props) {
|
|
|
20328
20845
|
setStep(targetStep);
|
|
20329
20846
|
setModalOpen(true);
|
|
20330
20847
|
};
|
|
20331
|
-
|
|
20332
|
-
|
|
20333
|
-
|
|
20848
|
+
// MS-8: 「+ 添加服务端」 — a BLANK pairing form on step 2, so a stale
|
|
20849
|
+
// address or code from an earlier pairing can never ride into the new one.
|
|
20850
|
+
const openAddServerWizard = () => {
|
|
20851
|
+
setClientServerUrl('');
|
|
20852
|
+
setClientPairCode('');
|
|
20853
|
+
setClientClaimFail(null);
|
|
20854
|
+
openWizardAt('configure', { role: 'client' });
|
|
20855
|
+
};
|
|
20856
|
+
return ((0, jsx_runtime_1.jsxs)("div", { "data-zen-remote": "wizard", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-header", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-headerText", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-title", children: t('wizard.title') }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-subtitle", children: t('wizard.subtitle') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-statusBadge", "data-state": statusSummary.state, children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-dot", "data-state": statusSummary.state === 'pass' ? 'pass' : 'pending' }), (0, jsx_runtime_1.jsx)("span", { children: statusSummary.text })] })] }), statusLoaded && viaGateway && !effectiveClient ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('wizard.remoteOnly') }) : null, notWired ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", children: t('wizard.notWired') }) : null, configured ? (effectiveClient ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-overviewGrid", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-overviewItem", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewLabel", children: t('wizard.overview.roleLabel') }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewValue", children: t('wizard.role.client') }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewSub", children: clientDeviceName.trim() !== '' ? clientDeviceName : t('settings.client.deviceNameDefault') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-overviewItem", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewLabel", children: t('wizard.overview.clientSyncLabel') }), (0, jsx_runtime_1.jsxs)("p", { className: "zr-wizard-overviewValue", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-dot", "data-state": clientLights.sync }), (0, jsx_runtime_1.jsx)("span", { children: clientLights.sync === 'pass'
|
|
20857
|
+
? t('wizard.overview.syncOk')
|
|
20858
|
+
: clientLights.sync === 'fail'
|
|
20859
|
+
? t('wizard.overview.syncBad')
|
|
20860
|
+
: t('wizard.overview.syncPending') })] }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewSub", children: t(LIGHT_LABEL[clientLights.sync]) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-overviewItem", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewLabel", children: t('wizard.overview.clientConnLabel') }), (0, jsx_runtime_1.jsxs)("p", { className: "zr-wizard-overviewValue", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-dot", "data-state": poolSummary.connectionLight }), (0, jsx_runtime_1.jsx)("span", { children: t('wizard.overview.clientOnlineCount', { online: poolSummary.onlineCount, total: poolSummary.totalCount }) })] }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewSub", children: poolSummary.totalCount === 0
|
|
20861
|
+
? t('settings.client.statusUnpaired')
|
|
20862
|
+
: poolSummary.offlineCount === 0 && poolSummary.revokedCount === 0
|
|
20863
|
+
? t('wizard.overview.clientAllOnline')
|
|
20864
|
+
: [
|
|
20865
|
+
poolSummary.offlineCount > 0 ? t('wizard.overview.clientOfflineCount', { count: poolSummary.offlineCount }) : '',
|
|
20866
|
+
poolSummary.revokedCount > 0 ? t('wizard.overview.clientRevokedCount', { count: poolSummary.revokedCount }) : '',
|
|
20867
|
+
].filter((line) => line !== '').join(' · ') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-overviewItem", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-overviewHead", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewLabel", children: t('wizard.overview.clientServersLabel') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", onClick: () => {
|
|
20868
|
+
setServerActionFailed(false);
|
|
20869
|
+
setServersModalOpen(true);
|
|
20870
|
+
}, children: t('wizard.overview.manageServers') })] }), (0, jsx_runtime_1.jsxs)("p", { className: "zr-wizard-overviewValue", "data-limit": poolSummary.limitReached ? 'true' : undefined, children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-dot", "data-state": poolSummary.serversLight }), (0, jsx_runtime_1.jsx)("span", { children: poolSummary.limitReached
|
|
20871
|
+
? t('wizard.overview.clientPairedFull', { count: poolSummary.totalCount, max: poolSummary.maxServers })
|
|
20872
|
+
: t('settings.client.poolCount', { count: poolSummary.totalCount, max: poolSummary.maxServers }) })] }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewSub", "data-limit": poolSummary.limitReached ? 'true' : undefined, children: poolSummary.limitReached
|
|
20873
|
+
? t('wizard.overview.limitNote', { max: poolSummary.maxServers })
|
|
20874
|
+
: serverRows.length === 0
|
|
20875
|
+
? t('settings.client.serversEmpty', { max: poolSummary.maxServers })
|
|
20876
|
+
: t('wizard.overview.clientPairedNames', {
|
|
20877
|
+
names: serverRows.map((server) => (server.displayName !== '' ? server.displayName : server.serverUrl)).join('、'),
|
|
20878
|
+
max: poolSummary.maxServers,
|
|
20879
|
+
}) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-footer", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: clientLoading, onClick: () => { void loadClientStatus(); }, children: clientLoading ? t('settings.client.statusRefreshing') : t('settings.client.statusRefresh') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", onClick: () => openWizardAt('pick', { role: 'client' }), children: t('wizard.overview.reconfigure') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", onClick: () => {
|
|
20880
|
+
setServerActionFailed(false);
|
|
20881
|
+
setServersModalOpen(true);
|
|
20882
|
+
}, children: t('wizard.overview.manageServers') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: poolSummary.limitReached, onClick: openAddServerWizard, children: t('wizard.overview.addServer') })] })] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-overviewGrid", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-overviewItem", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewLabel", children: t('wizard.overview.roleLabel') }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewValue", children: t('wizard.role.host') }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewSub", children: hostName.trim() !== '' ? hostName : t('settings.sourceDefault') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-overviewItem", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewLabel", children: t('wizard.overview.gatewayLabel') }), (0, jsx_runtime_1.jsxs)("p", { className: "zr-wizard-overviewValue", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-dot", "data-state": gatewayReachable ? 'pass' : 'fail' }), (0, jsx_runtime_1.jsx)("span", { children: gatewayReachable
|
|
20334
20883
|
? t('wizard.overview.gatewayReady', { port: String((0, wizard_ts_1.upstreamPortOf)(wizardStatus)) })
|
|
20335
20884
|
: t('wizard.overview.gatewayDown') })] }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewSub", children: autoShare ? t('wizard.autoShare') : t('settings.fieldAutoShare') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-overviewItem", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewLabel", children: t('wizard.overview.ingressLabel') }), (0, jsx_runtime_1.jsxs)("p", { className: "zr-wizard-overviewValue", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-dot", "data-state": statusSummary.state === 'pass' ? 'pass' : 'pending' }), (0, jsx_runtime_1.jsx)("span", { children: pathName(path) })] }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewSub", children: resolvedHostEndpoint })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-overviewItem", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-overviewHead", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-overviewLabel", children: t('wizard.overview.devicesLabel') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: disabled, onClick: () => {
|
|
20336
20885
|
setDeviceError(false);
|
|
@@ -20373,10 +20922,10 @@ function GatewayWizard(props) {
|
|
|
20373
20922
|
plan: wizardRole === 'host'
|
|
20374
20923
|
? `${t('wizard.role.host')} · ${pathName(path)}`
|
|
20375
20924
|
: t('wizard.role.client'),
|
|
20376
|
-
}) }) }), wizardRole === 'host' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-formStack", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-host-name", children: t('wizard.hostName') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-host-name", className: "zr-wizard-input", type: "text", placeholder: t('wizard.hostNamePlaceholder'), value: hostName, disabled: disabled, onChange: (event) => setHostName(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.hostNameHint') })] }), (0, jsx_runtime_1.jsxs)("label", { className: "zr-wizard-checkRow", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: autoShare, disabled: disabled, onChange: (event) => setAutoShare(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: t('wizard.autoShare') })] })] }), path === 'lan' ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-lan-url", children: t('wizard.lanUrl') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-lan-url", className: "zr-wizard-input", type: "url", placeholder: t('wizard.lanUrlPlaceholder'), value: serverUrl, disabled: disabled, onChange: (event) => setServerUrl(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.lanUrlHint') })] })) : path === 'external' ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-server-url", children: t('wizard.serverUrl') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-server-url", className: "zr-wizard-input", type: "url", placeholder: "https://dsh.example.com", value: serverUrl, disabled: disabled, onChange: (event) => setServerUrl(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.serverUrlHint') }), (0, jsx_runtime_1.jsx)("div", { className: "zr-wizard-row", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: disabled, onClick: () => setCard('proxy'), children: t('wizard.proxySnippet') }) })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-formStack", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-formGrid", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-domain", children: t('wizard.domain') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-domain", className: "zr-wizard-input", type: "text", placeholder: "dsh.example.com", value: domain, disabled: disabled, onChange: (event) => setDomain(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.domainHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field zr-wizard-fieldPort", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-port", children: t('wizard.httpsPort') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-port", className: "zr-wizard-input", type: "text", inputMode: "numeric", value: httpsPort, disabled: disabled, onChange: (event) => setHttpsPort(event.target.value) })] })] }), path === 'caddy' && (0, wizard_ts_1.isValidPort)(httpsPort.trim()) && httpsPort.trim() !== '443' ? ((0, jsx_runtime_1.jsx)("div", { className: "zr-wizard-row", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: disabled, onClick: () => setCard('portforward'), children: t('wizard.portForwardEntry') }) })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-labelRow", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-token", children: t('wizard.cfToken') }), tokenSaved ? (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-savedTag", children: t('wizard.token.saved') }) : null] }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-token", className: "zr-wizard-input", type: "password", placeholder: tokenSaved ? '••••••••••••••••' : '', value: cfToken, disabled: disabled, onChange: (event) => setCfToken(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.cfTokenHint') }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: disabled, onClick: () => setCard('token'), children: t('wizard.cfTokenHowTo') }), path === 'cloudflared' ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: disabled, onClick: () => setCard('migrate'), children: t('wizard.migrateEntry') })) : null] })] })] })), tokenNotice === 'failed' ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('wizard.token.saveFailed') })) : null, formError !== null ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t(formError === 'bad-domain' ? 'wizard.badDomain' : formError === 'bad-port' ? 'wizard.badPort' : formError === 'missing-token' ? 'wizard.missingToken' : 'wizard.badServerUrl') })) : null] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-formStack", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-client-device", children: t('wizard.client.deviceName') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-client-device", className: "zr-wizard-input", type: "text", placeholder: t('wizard.client.deviceNamePlaceholder'), value: clientDeviceName, disabled: clientClaimBusy, onChange: (event) => setClientDeviceName(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.client.deviceNameHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-client-url", children: t('wizard.client.serverUrl') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-client-url", className: "zr-wizard-input", type: "url", placeholder: t('wizard.client.serverUrlPlaceholder'), value: clientServerUrl, disabled: clientClaimBusy, onChange: (event) => setClientServerUrl(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.client.serverUrlHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-client-code", children: t('wizard.client.pairingCode') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-client-code", className: "zr-wizard-input", type: "text", autoComplete: "off", spellCheck: false, placeholder: t('wizard.client.pairingCodePlaceholder'), value: clientPairCode, disabled: clientClaimBusy, onChange: (event) => setClientPairCode((0, settings_form_ts_1.normalizePairingCode)(event.target.value)) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.client.pairingCodeHint') })] })] }), formError === 'bad-client-url' ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('settings.client.failInvalid') })) : null, formError === 'bad-client-code' ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('settings.client.failBadCode') })) : null, clientClaimFail !== null ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: claimFailCopy(clientClaimFail.code, clientClaimFail.retryAfterMs, t) })) : null] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-modalFooter", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: disabled || savingConfig || clientClaimBusy, onClick: () => {
|
|
20925
|
+
}) }) }), wizardRole === 'host' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-formStack", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-host-name", children: t('wizard.hostName') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-host-name", className: "zr-wizard-input", type: "text", placeholder: t('wizard.hostNamePlaceholder'), value: hostName, disabled: disabled, onChange: (event) => setHostName(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.hostNameHint') })] }), (0, jsx_runtime_1.jsxs)("label", { className: "zr-wizard-checkRow", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: autoShare, disabled: disabled, onChange: (event) => setAutoShare(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: t('wizard.autoShare') })] })] }), path === 'lan' ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-lan-url", children: t('wizard.lanUrl') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-lan-url", className: "zr-wizard-input", type: "url", placeholder: t('wizard.lanUrlPlaceholder'), value: serverUrl, disabled: disabled, onChange: (event) => setServerUrl(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.lanUrlHint') })] })) : path === 'external' ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-server-url", children: t('wizard.serverUrl') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-server-url", className: "zr-wizard-input", type: "url", placeholder: "https://dsh.example.com", value: serverUrl, disabled: disabled, onChange: (event) => setServerUrl(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.serverUrlHint') }), (0, jsx_runtime_1.jsx)("div", { className: "zr-wizard-row", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: disabled, onClick: () => setCard('proxy'), children: t('wizard.proxySnippet') }) })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-formStack", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-formGrid", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-domain", children: t('wizard.domain') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-domain", className: "zr-wizard-input", type: "text", placeholder: "dsh.example.com", value: domain, disabled: disabled, onChange: (event) => setDomain(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.domainHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field zr-wizard-fieldPort", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-port", children: t('wizard.httpsPort') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-port", className: "zr-wizard-input", type: "text", inputMode: "numeric", value: httpsPort, disabled: disabled, onChange: (event) => setHttpsPort(event.target.value) })] })] }), path === 'caddy' && (0, wizard_ts_1.isValidPort)(httpsPort.trim()) && httpsPort.trim() !== '443' ? ((0, jsx_runtime_1.jsx)("div", { className: "zr-wizard-row", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: disabled, onClick: () => setCard('portforward'), children: t('wizard.portForwardEntry') }) })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-labelRow", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-token", children: t('wizard.cfToken') }), tokenSaved ? (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-savedTag", children: t('wizard.token.saved') }) : null] }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-token", className: "zr-wizard-input", type: "password", placeholder: tokenSaved ? '••••••••••••••••' : '', value: cfToken, disabled: disabled, onChange: (event) => setCfToken(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.cfTokenHint') }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: disabled, onClick: () => setCard('token'), children: t('wizard.cfTokenHowTo') }), path === 'cloudflared' ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: disabled, onClick: () => setCard('migrate'), children: t('wizard.migrateEntry') })) : null] })] })] })), tokenNotice === 'failed' ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('wizard.token.saveFailed') })) : null, formError !== null ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t(formError === 'bad-domain' ? 'wizard.badDomain' : formError === 'bad-port' ? 'wizard.badPort' : formError === 'missing-token' ? 'wizard.missingToken' : 'wizard.badServerUrl') })) : null] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", children: t('wizard.client.poolCapNote', { count: serverRows.length, max: poolSummary.maxServers }) }), poolSummary.limitReached ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('wizard.overview.limitNote', { max: poolSummary.maxServers }) })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-formStack", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-client-device", children: t('wizard.client.deviceName') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-client-device", className: "zr-wizard-input", type: "text", placeholder: t('wizard.client.deviceNamePlaceholder'), value: clientDeviceName, disabled: clientClaimBusy, onChange: (event) => setClientDeviceName(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.client.deviceNameHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-client-url", children: t('wizard.client.serverUrl') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-client-url", className: "zr-wizard-input", type: "url", placeholder: t('wizard.client.serverUrlPlaceholder'), value: clientServerUrl, disabled: clientClaimBusy, onChange: (event) => setClientServerUrl(event.target.value) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.client.serverUrlHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "zr-wizard-label", htmlFor: "zr-wizard-client-code", children: t('wizard.client.pairingCode') }), (0, jsx_runtime_1.jsx)("input", { id: "zr-wizard-client-code", className: "zr-wizard-input", type: "text", autoComplete: "off", spellCheck: false, placeholder: t('wizard.client.pairingCodePlaceholder'), value: clientPairCode, disabled: clientClaimBusy, onChange: (event) => setClientPairCode((0, settings_form_ts_1.normalizePairingCode)(event.target.value)) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.client.pairingCodeHint') })] })] }), formError === 'bad-client-url' ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('settings.client.failInvalid') })) : null, formError === 'bad-client-code' ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('settings.client.failBadCode') })) : null, clientClaimFail !== null ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: claimFailCopy(clientClaimFail.code, clientClaimFail.retryAfterMs, t) })) : null] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-modalFooter", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: disabled || savingConfig || clientClaimBusy, onClick: () => {
|
|
20377
20926
|
setFormError(null);
|
|
20378
20927
|
setStep('pick');
|
|
20379
|
-
}, children: t('wizard.back') }), wizardRole === 'host' ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: disabled || savingConfig, onClick: () => { void advanceFromConfigure(); }, children: t('wizard.modal.saveAndNext') })) : ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [roleSnapshot.deviceToken.configured ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: clientClaimBusy, onClick: () => setStep('verify'), children: t('wizard.client.skipToStatus') })) : null, (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: clientClaimBusy, onClick: () => { void runClientClaim(); }, children: clientClaimBusy ? t('wizard.client.pairingBusy') : t('wizard.client.pairAndConnect') })] }))] })] })) : null, step === 'verify' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-modalBody", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-stepIntro", children: t('wizard.modal.step3Desc') }), wizardRole === 'host' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [caddyPath ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-serviceBar", "data-zen-remote": "wizard-caddy-controls", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-serviceInfo", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-dot", "data-state": caddyState === 'running' ? 'pass' : 'pending' }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-label", children: t('wizard.ingress') }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-hint", children: caddyBusy
|
|
20928
|
+
}, children: t('wizard.back') }), wizardRole === 'host' ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: disabled || savingConfig, onClick: () => { void advanceFromConfigure(); }, children: t('wizard.modal.saveAndNext') })) : ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [roleSnapshot.deviceToken.configured ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: clientClaimBusy, onClick: () => setStep('verify'), children: t('wizard.client.skipToStatus') })) : null, (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: clientClaimBusy || claimGate.blockedByLimit, onClick: () => { void runClientClaim(); }, children: clientClaimBusy ? t('wizard.client.pairingBusy') : t('wizard.client.pairAndConnect') })] }))] })] })) : null, step === 'verify' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-modalBody", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-stepIntro", children: t('wizard.modal.step3Desc') }), wizardRole === 'host' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [caddyPath ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-serviceBar", "data-zen-remote": "wizard-caddy-controls", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-serviceInfo", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-dot", "data-state": caddyState === 'running' ? 'pass' : 'pending' }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-label", children: t('wizard.ingress') }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-hint", children: caddyBusy
|
|
20380
20929
|
? t('wizard.caddy.starting')
|
|
20381
20930
|
: t(caddyState === 'running' ? 'wizard.caddy.running' : caddyState === 'stopped' ? 'wizard.caddy.stopped' : 'wizard.caddy.neverStarted') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: disabled || caddyBusy || caddyState === 'running', onClick: () => { void startCaddy(); }, children: caddyBusy ? t('wizard.caddy.starting') : t('wizard.caddy.start') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: disabled || caddyBusy || caddyState !== 'running', onClick: () => { void stopCaddy(); }, children: t('wizard.caddy.stop') })] })] })) : null, tunnelPath ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-serviceBar", "data-zen-remote": "wizard-tunnel-controls", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-serviceInfo", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-dot", "data-state": tunnelState === 'running' ? 'pass' : 'pending' }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-label", children: t('wizard.ingress') }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-hint", children: t(tunnelState === 'running' ? 'wizard.tunnel.running' : tunnelState === 'stopped' ? 'wizard.tunnel.stopped' : 'wizard.tunnel.neverStarted') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: disabled || tunnelBusy, onClick: () => { void setupTunnelNow(adoptTunnelId); }, children: tunnelBusy ? t('wizard.tunnel.checking') : tunnelState === 'running' ? t('wizard.reverify') : t('wizard.tunnel.setup') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: disabled || tunnelState !== 'running', onClick: () => {
|
|
20382
20931
|
void (async () => {
|
|
@@ -20391,7 +20940,7 @@ function GatewayWizard(props) {
|
|
|
20391
20940
|
return (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": copy.tone === 'error' ? 'error' : undefined, children: copy.key === 'wizard.check.failed'
|
|
20392
20941
|
? t('wizard.check.failed', { reason: tunnelCheck.ok ? '' : String(tunnelCheck.reason) })
|
|
20393
20942
|
: t(copy.key) });
|
|
20394
|
-
})() : null, tunnelPath && tunnelResult !== undefined && !tunnelResult.ok && tunnelResult.reason === 'tunnel-conflict' && tunnelResult.existing !== undefined ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-label", children: t('wizard.tunnel.conflict') }), (0, jsx_runtime_1.jsx)("div", { className: "zr-wizard-row", children: tunnelResult.existing.map((tunnel) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: disabled || tunnelBusy, onClick: () => { setAdoptTunnelId(tunnel.id); void setupTunnelNow(tunnel.id); }, children: [t('wizard.tunnel.useExisting'), "
|
|
20943
|
+
})() : null, tunnelPath && tunnelResult !== undefined && !tunnelResult.ok && tunnelResult.reason === 'tunnel-conflict' && tunnelResult.existing !== undefined ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-label", children: t('wizard.tunnel.conflict') }), (0, jsx_runtime_1.jsx)("div", { className: "zr-wizard-row", children: tunnelResult.existing.map((tunnel) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: disabled || tunnelBusy, onClick: () => { setAdoptTunnelId(tunnel.id); void setupTunnelNow(tunnel.id); }, children: [t('wizard.tunnel.useExisting'), ": ", tunnel.name, " (", tunnel.status, ")"] }, tunnel.id))) })] })) : null, tunnelPath && tunnelResult !== undefined && tunnelResult.ok ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", children: t('wizard.tunnel.ok') }) : null, tunnelPath && tunnelResult !== undefined && !tunnelResult.ok && tunnelResult.reason !== 'tunnel-conflict' ? (() => {
|
|
20395
20944
|
const copy = (0, wizard_ts_1.tunnelFailureCopy)(tunnelResult.reason, tunnelResult.detail);
|
|
20396
20945
|
return (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t(copy.key, copy.params) });
|
|
20397
20946
|
})() : null, caddyPath && caddyStartError !== undefined ? ((0, jsx_runtime_1.jsxs)("p", { className: "zr-wizard-notice", "data-tone": "error", children: [t('wizard.caddy.startFailed', { reason: t(caddyStartError.key, caddyStartError.params) }), caddyStartError.installUrl !== undefined ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [" ", (0, jsx_runtime_1.jsx)("a", { className: "zr-wizard-link", href: caddyStartError.installUrl, target: "_blank", rel: "noreferrer", children: caddyStartError.installUrl })] })) : null] })) : null, (0, jsx_runtime_1.jsx)("ul", { className: "zr-wizard-lights", children: LIGHT_ORDER.map(({ key, light }) => {
|
|
@@ -20403,9 +20952,17 @@ function GatewayWizard(props) {
|
|
|
20403
20952
|
}) }), startResult !== undefined && !startResult.ok ? (() => {
|
|
20404
20953
|
const copy = startFailureCopy(startResult);
|
|
20405
20954
|
return (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('wizard.startFailed', { reason: t(copy.key, copy.params) }) });
|
|
20406
|
-
})() : null, (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-targetTabs", role: "tablist", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", role: "tab", "aria-selected": pairTarget === 'web', className: "zr-wizard-targetTab", "data-selected": pairTarget === 'web', disabled: disabled, onClick: () => setPairTarget('web'), children: t('wizard.pairTarget.web') }), (0, jsx_runtime_1.jsx)("button", { type: "button", role: "tab", "aria-selected": pairTarget === 'desktop-client', className: "zr-wizard-targetTab", "data-selected": pairTarget === 'desktop-client', disabled: disabled, onClick: () => setPairTarget('desktop-client'), children: t('wizard.pairTarget.desktop') })] }), pairTarget === 'web' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [webPairRequested && startResult?.ok === true && startResult.pairingCode !== '' ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-pairPanel", children: [(0, jsx_runtime_1.jsx)("svg", { className: "zr-wizard-qr", "data-usable": qr !== null, role: "img", "aria-label": t('wizard.qrHint'), shapeRendering: "crispEdges", viewBox: qr === null ? undefined : `-16 -16 ${(qr.length + 8) * 4} ${(qr.length + 8) * 4}`, children: qr === null ? null : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("rect", { x: -16, y: -16, width: (qr.length + 8) * 4, height: (qr.length + 8) * 4, fill: "#fff" }), (0, jsx_runtime_1.jsx)("path", { d: qrSvgPath(qr, 4), fill: "#000" })] })) }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-pairMeta", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-label", children: t('wizard.pairingCode') }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-code", children: startResult.pairingCode }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-countdown", children: t('wizard.pairingSeconds', { seconds: String((0, wizard_ts_1.pairingSecondsLeft)(startResult.expiresAt, now)) }) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.qrHint') })] })] })) : null, poll?.result === 'pass' ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "ok", children: t('wizard.verifyPass', { deviceId: poll.deviceId }) }) : null, poll?.result === 'cookie-insecure' ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('wizard.verifyCookieInsecure') }) : null, poll?.result === 'code-expired' ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('wizard.verifyExpired') }) : null, poll?.result === 'stale' ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('wizard.verifyStale') }) : null, webPairRequested && (poll?.result === 'pending' || poll?.result === 'not-started') ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.verifyPending') }) : null, !webPairRequested && poll === undefined ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.verifyNotStarted') }) : null] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-desktopBox", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.desktop.guide') }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-label", children: t('wizard.desktop.addressLabel') }), (0, jsx_runtime_1.jsx)("code", { className: "zr-wizard-input", children: resolvedHostEndpoint })] }), desktopPairing !== undefined && desktopPairing.expiresAt > now ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-pairMeta", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-label", children: t('wizard.pairingCode') }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-code", children: desktopPairing.code }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-countdown", children: t('wizard.pairingSeconds', { seconds: String((0, wizard_ts_1.pairingSecondsLeft)(desktopPairing.expiresAt, now)) }) })] })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: disabled || desktopPairBusy, onClick: () => { void runDesktopPair(); }, children: desktopPairing !== undefined && desktopPairing.expiresAt > now ? t('wizard.desktop.regenerate') : t('wizard.desktop.generate') }), desktopPairing !== undefined && desktopPairing.expiresAt > now ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: disabled, onClick: () => { void copyDesktopInfo(); }, children: desktopCopied ? t('wizard.card.copied') : t('wizard.desktop.copyInfo') })) : null] })] }))] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [
|
|
20407
|
-
|
|
20408
|
-
|
|
20955
|
+
})() : null, (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-targetTabs", role: "tablist", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", role: "tab", "aria-selected": pairTarget === 'web', className: "zr-wizard-targetTab", "data-selected": pairTarget === 'web', disabled: disabled, onClick: () => setPairTarget('web'), children: t('wizard.pairTarget.web') }), (0, jsx_runtime_1.jsx)("button", { type: "button", role: "tab", "aria-selected": pairTarget === 'desktop-client', className: "zr-wizard-targetTab", "data-selected": pairTarget === 'desktop-client', disabled: disabled, onClick: () => setPairTarget('desktop-client'), children: t('wizard.pairTarget.desktop') })] }), pairTarget === 'web' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [webPairRequested && startResult?.ok === true && startResult.pairingCode !== '' ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-pairPanel", children: [(0, jsx_runtime_1.jsx)("svg", { className: "zr-wizard-qr", "data-usable": qr !== null, role: "img", "aria-label": t('wizard.qrHint'), shapeRendering: "crispEdges", viewBox: qr === null ? undefined : `-16 -16 ${(qr.length + 8) * 4} ${(qr.length + 8) * 4}`, children: qr === null ? null : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("rect", { x: -16, y: -16, width: (qr.length + 8) * 4, height: (qr.length + 8) * 4, fill: "#fff" }), (0, jsx_runtime_1.jsx)("path", { d: qrSvgPath(qr, 4), fill: "#000" })] })) }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-pairMeta", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-label", children: t('wizard.pairingCode') }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-code", children: startResult.pairingCode }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-countdown", children: t('wizard.pairingSeconds', { seconds: String((0, wizard_ts_1.pairingSecondsLeft)(startResult.expiresAt, now)) }) }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.qrHint') })] })] })) : null, poll?.result === 'pass' ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "ok", children: t('wizard.verifyPass', { deviceId: poll.deviceId }) }) : null, poll?.result === 'cookie-insecure' ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('wizard.verifyCookieInsecure') }) : null, poll?.result === 'code-expired' ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('wizard.verifyExpired') }) : null, poll?.result === 'stale' ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('wizard.verifyStale') }) : null, webPairRequested && (poll?.result === 'pending' || poll?.result === 'not-started') ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.verifyPending') }) : null, !webPairRequested && poll === undefined ? (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.verifyNotStarted') }) : null] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-desktopBox", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-hint", children: t('wizard.desktop.guide') }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-field", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-label", children: t('wizard.desktop.addressLabel') }), (0, jsx_runtime_1.jsx)("code", { className: "zr-wizard-input", children: resolvedHostEndpoint })] }), desktopPairing !== undefined && desktopPairing.expiresAt > now ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-pairMeta", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-label", children: t('wizard.pairingCode') }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-code", children: desktopPairing.code }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-countdown", children: t('wizard.pairingSeconds', { seconds: String((0, wizard_ts_1.pairingSecondsLeft)(desktopPairing.expiresAt, now)) }) })] })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: disabled || desktopPairBusy, onClick: () => { void runDesktopPair(); }, children: desktopPairing !== undefined && desktopPairing.expiresAt > now ? t('wizard.desktop.regenerate') : t('wizard.desktop.generate') }), desktopPairing !== undefined && desktopPairing.expiresAt > now ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: disabled, onClick: () => { void copyDesktopInfo(); }, children: desktopCopied ? t('wizard.card.copied') : t('wizard.desktop.copyInfo') })) : null] })] }))] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [serverRows.length > 1 ? ((0, jsx_runtime_1.jsx)("div", { className: "zr-wizard-targetTabs", role: "tablist", "aria-label": t('wizard.client.serverPicker'), children: serverRows.map((server) => {
|
|
20956
|
+
const selected = activeServer?.entryId === server.entryId;
|
|
20957
|
+
return ((0, jsx_runtime_1.jsx)("button", { type: "button", role: "tab", "aria-selected": selected, className: "zr-wizard-targetTab", "data-selected": selected, onClick: () => setActiveClientEntryId(server.entryId), children: server.displayName !== '' ? server.displayName : server.serverUrl }, server.entryId));
|
|
20958
|
+
}) })) : null, activeServer !== undefined ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-serviceInfo", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-dot", "data-state": serverDotState(activeServer.state) }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-lightName", children: activeServer.displayName !== '' ? activeServer.displayName : activeServer.serverUrl }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-hint", children: activeServer.serverUrl })] })) : null, (0, jsx_runtime_1.jsx)("ul", { className: "zr-wizard-lights", children: CLIENT_LIGHT_ORDER.map(({ key, light }) => ((0, jsx_runtime_1.jsxs)("li", { className: "zr-wizard-light", "data-state": serverLights[light], children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-lightMain", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-dot", "data-state": serverLights[light] }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-lightName", children: t(key) })] }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-lightState", "data-state": serverLights[light], children: t(LIGHT_LABEL[serverLights[light]]) })] }, key))) }), activeServer !== undefined ? ((0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [activeServer.nextRetryAt !== undefined ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: serverBusyEntry !== null, onClick: () => { void runServerAction(settings_form_ts_1.CLIENT_RECONNECT_ROUTE, activeServer.entryId, { entryId: activeServer.entryId }); }, children: serverBusyEntry === activeServer.entryId ? t('settings.client.reconnectBusy') : t('settings.client.reconnect') })) : null, (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: serverBusyEntry !== null, onClick: () => {
|
|
20959
|
+
if (!window.confirm(t('settings.client.unpairServerConfirm')))
|
|
20960
|
+
return;
|
|
20961
|
+
const entryId = activeServer.entryId;
|
|
20962
|
+
void runServerAction(settings_form_ts_1.CLIENT_UNPAIR_ROUTE, entryId, { entryId }).then(() => {
|
|
20963
|
+
setActiveClientEntryId((current) => (current === entryId ? undefined : current));
|
|
20964
|
+
});
|
|
20965
|
+
}, children: t('settings.client.serverRemove') })] })) : null, serverRows.length === 0 && roleSnapshot.deviceToken.configured ? ((0, jsx_runtime_1.jsx)("div", { className: "zr-wizard-row", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: clientUnpairBusy, onClick: () => { void runClientUnpair(); }, children: clientUnpairBusy ? t('settings.client.unpairBusy') : t('settings.client.unpair') }) })) : null] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-modalFooter", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: disabled, onClick: () => setStep('configure'), children: t('wizard.back') }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [wizardRole === 'host' && resolvedVerifyUrl !== '' ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: disabled || probingUrl, onClick: () => { void probeAccessUrl(resolvedVerifyUrl); }, children: probingUrl ? t('wizard.light.probing') : t('wizard.devicesModal.retest') })) : null, wizardRole === 'host' && pairTarget === 'web' ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: disabled || probingUrl, onClick: () => { void startVerification(); }, children: verified ? t('wizard.reverify') : t('wizard.startVerify') })) : null, wizardRole === 'host' && pairTarget === 'desktop-client' ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: disabled || desktopPairBusy, onClick: () => { void runDesktopPair(); }, children: desktopPairing !== undefined && desktopPairing.expiresAt > now ? t('wizard.desktop.regenerate') : t('wizard.desktop.generate') })) : null, wizardRole === 'client' && !poolSummary.limitReached ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", onClick: openAddServerWizard, children: t('wizard.client.continueAdd') })) : null, (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", onClick: () => setModalOpen(false), children: t('wizard.modal.finish') })] })] })] })) : null] }) })) : null, devicesModalOpen ? ((0, jsx_runtime_1.jsx)("div", { "data-zen-remote": "wizard-modal", onClick: () => {
|
|
20409
20966
|
if (!deviceBusy)
|
|
20410
20967
|
setDevicesModalOpen(false);
|
|
20411
20968
|
}, children: (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-modalDialog", role: "dialog", "aria-modal": "true", "aria-label": t('wizard.devicesModal.title'), onClick: (event) => event.stopPropagation(), children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-modalHeader", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-headerText", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-modalTitle", children: t('wizard.devicesModal.title') }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-subtitle", children: t('wizard.devicesModal.subtitle') })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-cardCloseIcon", "aria-label": t('wizard.card.close'), disabled: deviceBusy, onClick: () => setDevicesModalOpen(false), children: "\u00D7" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-modalBody", children: [deviceError ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('settings.actionFail') })) : null, allDevices.length === 0 ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", children: t('settings.devicesEmpty') })) : ((0, jsx_runtime_1.jsx)("ul", { className: "zr-wizard-deviceList", children: allDevices.map((device) => ((0, jsx_runtime_1.jsxs)("li", { className: "zr-wizard-deviceItem", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-deviceHead", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-deviceTitleRow", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-lightName", children: device.name }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-deviceBadge", children: device.role === 'web' ? t('settings.deviceRoleWeb') : t('settings.deviceRoleDesktop') }), device.role === 'web' ? ((0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-deviceBadge", children: device.hasPush ? t('settings.devicePushOn') : t('settings.devicePushOff') })) : null] }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-hint", children: t('settings.deviceLastSeen', { time: new Date(device.lastSeen).toLocaleString() }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-deviceActions", children: [device.role === 'web' ? ((0, jsx_runtime_1.jsxs)("select", { className: "zr-wizard-select", "aria-label": t('settings.deviceKindSelect', { name: device.name }), disabled: disabled || deviceBusy, value: device.kind, onChange: (event) => {
|
|
@@ -20440,7 +20997,43 @@ function GatewayWizard(props) {
|
|
|
20440
20997
|
}, children: t('wizard.overview.pairDesktop') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: disabled || deviceBusy, onClick: () => {
|
|
20441
20998
|
setDevicesModalOpen(false);
|
|
20442
20999
|
openWizardAt('verify', { role: 'host', target: 'web' });
|
|
20443
|
-
}, children: t('wizard.overview.pairPhone') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: deviceBusy, onClick: () => setDevicesModalOpen(false), children: t('wizard.card.close') })] })] })] }) })) : null,
|
|
21000
|
+
}, children: t('wizard.overview.pairPhone') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: deviceBusy, onClick: () => setDevicesModalOpen(false), children: t('wizard.card.close') })] })] })] }) })) : null, serversModalOpen ? ((0, jsx_runtime_1.jsx)("div", { "data-zen-remote": "wizard-modal", onClick: () => {
|
|
21001
|
+
if (serverBusyEntry === null)
|
|
21002
|
+
setServersModalOpen(false);
|
|
21003
|
+
}, children: (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-modalDialog", role: "dialog", "aria-modal": "true", "aria-label": t('wizard.serversModal.title'), onClick: (event) => event.stopPropagation(), children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-modalHeader", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-headerText", children: [(0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-modalTitle", children: t('wizard.serversModal.title') }), (0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-subtitle", "data-limit": poolSummary.limitReached ? 'true' : undefined, children: t('wizard.serversModal.subtitle', { count: serverRows.length, max: poolSummary.maxServers }) })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-cardCloseIcon", "aria-label": t('wizard.card.close'), disabled: serverBusyEntry !== null, onClick: () => setServersModalOpen(false), children: "\u00D7" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-modalBody", children: [poolSummary.limitReached ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('wizard.overview.limitNote', { max: poolSummary.maxServers }) })) : null, serverActionFailed ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", "data-tone": "error", children: t('settings.actionFail') })) : null, serverRows.length === 0 ? ((0, jsx_runtime_1.jsx)("p", { className: "zr-wizard-notice", children: t('settings.client.serversEmpty', { max: poolSummary.maxServers }) })) : ((0, jsx_runtime_1.jsx)("ul", { className: "zr-wizard-deviceList", children: serverRows.map((server) => {
|
|
21004
|
+
const badge = serverBadgeCopy(server, now, t);
|
|
21005
|
+
const name = server.displayName !== '' ? server.displayName : server.serverUrl;
|
|
21006
|
+
return ((0, jsx_runtime_1.jsxs)("li", { className: "zr-wizard-deviceItem", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-deviceHead", children: [(0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-deviceTitleRow", children: [(0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-dot", "data-state": serverDotState(server.state) }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-lightName", children: name }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-deviceBadge", "data-tone": badge.tone, children: badge.text })] }), (0, jsx_runtime_1.jsx)("span", { className: "zr-wizard-hint", children: server.serverUrl })] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-deviceActions", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: serverBusyEntry !== null, onClick: () => {
|
|
21007
|
+
setServersModalOpen(false);
|
|
21008
|
+
setActiveClientEntryId(server.entryId);
|
|
21009
|
+
openWizardAt('verify', { role: 'client' });
|
|
21010
|
+
}, children: t('wizard.serversModal.viewStatus') }), server.nextRetryAt !== undefined ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: serverBusyEntry !== null, onClick: () => { void runServerAction(settings_form_ts_1.CLIENT_RECONNECT_ROUTE, server.entryId, { entryId: server.entryId }); }, children: serverBusyEntry === server.entryId ? t('settings.client.reconnectBusy') : t('settings.client.reconnect') })) : null, (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: serverBusyEntry !== null, onClick: () => {
|
|
21011
|
+
// 覆盖重配 pre-fills the entry's own address, so
|
|
21012
|
+
// it stays usable at a FULL pool (an already-paired
|
|
21013
|
+
// address never trips the cap).
|
|
21014
|
+
setServersModalOpen(false);
|
|
21015
|
+
setClientServerUrl(server.serverUrl);
|
|
21016
|
+
setClientPairCode('');
|
|
21017
|
+
setClientClaimFail(null);
|
|
21018
|
+
openWizardAt('configure', { role: 'client' });
|
|
21019
|
+
}, children: t('settings.client.repair') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-linkBtn", disabled: serverBusyEntry !== null, onClick: () => {
|
|
21020
|
+
if (!window.confirm(t('settings.client.unpairServerConfirm')))
|
|
21021
|
+
return;
|
|
21022
|
+
const entryId = server.entryId;
|
|
21023
|
+
void runServerAction(settings_form_ts_1.CLIENT_UNPAIR_ROUTE, entryId, { entryId }).then(() => {
|
|
21024
|
+
setActiveClientEntryId((current) => (current === entryId ? undefined : current));
|
|
21025
|
+
});
|
|
21026
|
+
}, children: t('settings.client.serverRemove') })] })] }, server.entryId));
|
|
21027
|
+
}) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-modalFooter", children: [(0, jsx_runtime_1.jsx)("div", { className: "zr-wizard-row", children: serverRows.length > 0 ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: serverBusyEntry !== null, onClick: () => {
|
|
21028
|
+
if (window.confirm(t('settings.client.unpairAllServersConfirm'))) {
|
|
21029
|
+
void runServerAction(settings_form_ts_1.CLIENT_UNPAIR_ROUTE, 'all', { all: true }).then(() => {
|
|
21030
|
+
setActiveClientEntryId(undefined);
|
|
21031
|
+
});
|
|
21032
|
+
}
|
|
21033
|
+
}, children: t('settings.client.unpairAllServers') })) : null }), (0, jsx_runtime_1.jsxs)("div", { className: "zr-wizard-row", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn zr-wizard-btnPrimary", disabled: poolSummary.limitReached || serverBusyEntry !== null, onClick: () => {
|
|
21034
|
+
setServersModalOpen(false);
|
|
21035
|
+
openAddServerWizard();
|
|
21036
|
+
}, children: t('wizard.overview.addServer') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "zr-wizard-btn", disabled: serverBusyEntry !== null, onClick: () => setServersModalOpen(false), children: t('wizard.card.close') })] })] })] }) })) : null, card === 'proxy' ? ((0, jsx_runtime_1.jsx)(ProxyCard_tsx_1.ProxyCard, { t: t, domain: (() => {
|
|
20444
21037
|
if (domain.trim() !== '')
|
|
20445
21038
|
return domain.trim();
|
|
20446
21039
|
try {
|
|
@@ -20607,6 +21200,18 @@ exports.WIZARD_CSS = `
|
|
|
20607
21200
|
overflow-wrap: anywhere;
|
|
20608
21201
|
word-break: break-word;
|
|
20609
21202
|
}
|
|
21203
|
+
/* MS-8: the pool cap's loud form — the 已配对服务端 cell's value and sub
|
|
21204
|
+
wear data-limit="true" at 8/8 and turn bold red (the same loudness the
|
|
21205
|
+
settings page's limit alert uses). */
|
|
21206
|
+
[data-zen-remote="wizard"] .zr-wizard-overviewValue[data-limit="true"] {
|
|
21207
|
+
color: var(--dsw-alias-state-error-primary, #d5304f);
|
|
21208
|
+
font-weight: 700;
|
|
21209
|
+
}
|
|
21210
|
+
[data-zen-remote="wizard"] .zr-wizard-overviewSub[data-limit="true"],
|
|
21211
|
+
[data-zen-remote="wizard-modal"] .zr-wizard-subtitle[data-limit="true"] {
|
|
21212
|
+
color: var(--dsw-alias-state-error-primary, #d5304f);
|
|
21213
|
+
font-weight: 600;
|
|
21214
|
+
}
|
|
20610
21215
|
[data-zen-remote="wizard"] .zr-wizard-emptyBanner {
|
|
20611
21216
|
display: flex;
|
|
20612
21217
|
align-items: center;
|
|
@@ -21242,6 +21847,17 @@ exports.WIZARD_CSS = `
|
|
|
21242
21847
|
background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-module-platform));
|
|
21243
21848
|
color: var(--dsw-alias-label-secondary);
|
|
21244
21849
|
}
|
|
21850
|
+
/* MS-8: the servers-modal state badge tones — green online, amber offline,
|
|
21851
|
+
red for a verdict only a re-pair or an upgrade fixes. */
|
|
21852
|
+
[data-zen-remote="wizard"] .zr-wizard-deviceBadge[data-tone="live"] {
|
|
21853
|
+
color: var(--dsw-alias-state-success-primary, #16a34a);
|
|
21854
|
+
}
|
|
21855
|
+
[data-zen-remote="wizard"] .zr-wizard-deviceBadge[data-tone="warn"] {
|
|
21856
|
+
color: var(--dsw-alias-state-warning-primary, #b8860b);
|
|
21857
|
+
}
|
|
21858
|
+
[data-zen-remote="wizard"] .zr-wizard-deviceBadge[data-tone="invalid"] {
|
|
21859
|
+
color: var(--dsw-alias-state-error-primary, #d5304f);
|
|
21860
|
+
}
|
|
21245
21861
|
[data-zen-remote="wizard"] .zr-wizard-deviceActions {
|
|
21246
21862
|
display: flex;
|
|
21247
21863
|
align-items: center;
|
|
@@ -21834,6 +22450,10 @@ __modules["client-data/remote-status.js"] = function (require, module, exports)
|
|
|
21834
22450
|
* - `parseRemoteStatusBody(body, now)` maps the
|
|
21835
22451
|
* `GET /_dsh/zen-remote/client/remote-status` body
|
|
21836
22452
|
* (`{ state, versionMismatch, serverName, closed }`) into a tolerant view;
|
|
22453
|
+
* since MS-6 the body's `servers` dict (per handshake serverId) parses
|
|
22454
|
+
* alongside, and `resolveServerStatusView(view, sessionId)` picks the
|
|
22455
|
+
* session's OWN server entry — falling back to the top-level mirror (the
|
|
22456
|
+
* FIRST pool entry) whenever the dict cannot answer;
|
|
21837
22457
|
* - `describeRemoteStatus(view)` maps one view to the icon's three states —
|
|
21838
22458
|
* online (lit) / offline (grey; `revoked` / `unpaired` count as offline —
|
|
21839
22459
|
* the link is not serving either way) / mismatch (yellow, only ever while
|
|
@@ -21857,6 +22477,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
21857
22477
|
exports.REMOTE_STATUS_POLL_MS = exports.windowNetworkHint = exports.documentVisibility = exports.BRIDGE_STAMP_ATTRIBUTE = exports.BRIDGE_STAMP_PLUGIN = exports.CLIENT_UNSHARE_ROUTE = exports.NETWORK_HINT_ROUTE = exports.REMOTE_STATUS_ROUTE = void 0;
|
|
21858
22478
|
exports.parseRemoteStatusBody = parseRemoteStatusBody;
|
|
21859
22479
|
exports.shouldStampBridgeAttribute = shouldStampBridgeAttribute;
|
|
22480
|
+
exports.resolveServerStatusView = resolveServerStatusView;
|
|
22481
|
+
exports.shouldStampBridgeForSession = shouldStampBridgeForSession;
|
|
21860
22482
|
exports.createZhRemoteStatusFormatter = createZhRemoteStatusFormatter;
|
|
21861
22483
|
exports.describeRemoteStatus = describeRemoteStatus;
|
|
21862
22484
|
exports.bannerText = bannerText;
|
|
@@ -21873,12 +22495,25 @@ exports.REMOTE_STATUS_ROUTE = '/_dsh/zen-remote/client/remote-status';
|
|
|
21873
22495
|
exports.NETWORK_HINT_ROUTE = '/_dsh/zen-remote/client/network-hint';
|
|
21874
22496
|
/** Same-origin client route closing one remote session from this machine. */
|
|
21875
22497
|
exports.CLIENT_UNSHARE_ROUTE = '/_dsh/zen-remote/client/unshare';
|
|
22498
|
+
/** The tolerant per-entry parse the top level and the `servers` dict share:
|
|
22499
|
+
* a garbage shape is "not online yet", a garbage field degrades to its
|
|
22500
|
+
* default — never a throw into the polling loop. */
|
|
22501
|
+
function parseServerStatusEntry(value) {
|
|
22502
|
+
const record = value !== null && typeof value === 'object' ? value : {};
|
|
22503
|
+
return {
|
|
22504
|
+
state: record.state === 'online' || record.state === 'revoked' || record.state === 'unpaired' ? record.state : 'offline',
|
|
22505
|
+
versionMismatch: record.versionMismatch === true,
|
|
22506
|
+
serverName: typeof record.serverName === 'string' ? record.serverName : '',
|
|
22507
|
+
bridgedPlugins: Array.isArray(record.bridgedPlugins)
|
|
22508
|
+
? record.bridgedPlugins.filter((name) => typeof name === 'string' && name !== '')
|
|
22509
|
+
: [],
|
|
22510
|
+
};
|
|
22511
|
+
}
|
|
21876
22512
|
/** Parse one GET body tolerantly: a garbage shape is "not online yet", a
|
|
21877
22513
|
* garbage field degrades to its default — never a throw into the polling
|
|
21878
22514
|
* loop. */
|
|
21879
22515
|
function parseRemoteStatusBody(body, now) {
|
|
21880
22516
|
const record = body !== null && typeof body === 'object' ? body : {};
|
|
21881
|
-
const state = record.state === 'online' || record.state === 'revoked' || record.state === 'unpaired' ? record.state : 'offline';
|
|
21882
22517
|
const closed = {};
|
|
21883
22518
|
if (record.closed !== null && typeof record.closed === 'object') {
|
|
21884
22519
|
for (const [sessionId, reason] of Object.entries(record.closed)) {
|
|
@@ -21887,14 +22522,21 @@ function parseRemoteStatusBody(body, now) {
|
|
|
21887
22522
|
closed[sessionId] = reason === 'client' || reason === 'idle' ? reason : 'manual';
|
|
21888
22523
|
}
|
|
21889
22524
|
}
|
|
22525
|
+
// MS-6: the per-server dict, keyed by the handshake serverId. A legacy
|
|
22526
|
+
// body (or a broken one) parses to the EMPTY dict — the parts then read
|
|
22527
|
+
// the top-level mirror, the pre-MS-6 answer.
|
|
22528
|
+
const servers = {};
|
|
22529
|
+
if (record.servers !== null && typeof record.servers === 'object' && !Array.isArray(record.servers)) {
|
|
22530
|
+
for (const [serverId, entry] of Object.entries(record.servers)) {
|
|
22531
|
+
if (serverId === '')
|
|
22532
|
+
continue;
|
|
22533
|
+
servers[serverId] = parseServerStatusEntry(entry);
|
|
22534
|
+
}
|
|
22535
|
+
}
|
|
21890
22536
|
return {
|
|
21891
|
-
|
|
21892
|
-
versionMismatch: record.versionMismatch === true,
|
|
21893
|
-
serverName: typeof record.serverName === 'string' ? record.serverName : '',
|
|
22537
|
+
...parseServerStatusEntry(record),
|
|
21894
22538
|
closed,
|
|
21895
|
-
|
|
21896
|
-
? record.bridgedPlugins.filter((name) => typeof name === 'string' && name !== '')
|
|
21897
|
-
: [],
|
|
22539
|
+
servers,
|
|
21898
22540
|
asOf: now,
|
|
21899
22541
|
};
|
|
21900
22542
|
}
|
|
@@ -21912,6 +22554,41 @@ function parseRemoteStatusBody(body, now) {
|
|
|
21912
22554
|
function shouldStampBridgeAttribute(bridgedPlugins, plugin) {
|
|
21913
22555
|
return Array.isArray(bridgedPlugins) && bridgedPlugins.includes(plugin);
|
|
21914
22556
|
}
|
|
22557
|
+
/**
|
|
22558
|
+
* Which server's facts ONE session reads (MS-6): given the session id on
|
|
22559
|
+
* screen, its virtual form names the owning serverId, and THAT server's
|
|
22560
|
+
* entry answers — per-server isolation for the icon, the banner and the
|
|
22561
|
+
* bridge stamp. Everything else falls back to the TOP-LEVEL view, the
|
|
22562
|
+
* pre-MS-6 answer: a session id that is not virtual (a local session), a
|
|
22563
|
+
* serverId the dict never answered (its entry never handshook), a legacy
|
|
22564
|
+
* body without a dict, or no session id at all. Tolerant the same way as
|
|
22565
|
+
* the parse: hand-rolled views without `servers` never throw here.
|
|
22566
|
+
*/
|
|
22567
|
+
function resolveServerStatusView(view, sessionId) {
|
|
22568
|
+
if (view === undefined || view === null)
|
|
22569
|
+
return undefined;
|
|
22570
|
+
if (sessionId !== undefined) {
|
|
22571
|
+
const parts = (0, virtual_id_ts_1.fromVirtual)(sessionId);
|
|
22572
|
+
if (parts !== undefined) {
|
|
22573
|
+
const server = view.servers?.[parts.serverId];
|
|
22574
|
+
if (server !== undefined)
|
|
22575
|
+
return server;
|
|
22576
|
+
}
|
|
22577
|
+
}
|
|
22578
|
+
// RemoteStatusView structurally carries every RemoteServerStatusView field.
|
|
22579
|
+
return view;
|
|
22580
|
+
}
|
|
22581
|
+
/**
|
|
22582
|
+
* Whether the remote session CURRENTLY ON SCREEN has its own server's bridge
|
|
22583
|
+
* verdict going its way (MS-6): the verdict comes from the session's server
|
|
22584
|
+
* entry ({@link resolveServerStatusView}), not the page-wide mirror of the
|
|
22585
|
+
* FIRST pool entry — with several servers paired, A bridging a plugin must
|
|
22586
|
+
* not un-hide it over B's sessions. Tolerant like the 2-argument core it
|
|
22587
|
+
* wraps: a garbage or missing list reads as "not bridged".
|
|
22588
|
+
*/
|
|
22589
|
+
function shouldStampBridgeForSession(view, sessionId, plugin) {
|
|
22590
|
+
return shouldStampBridgeAttribute(resolveServerStatusView(view, sessionId)?.bridgedPlugins, plugin);
|
|
22591
|
+
}
|
|
21915
22592
|
/** The plugin the bridge stamp names. */
|
|
21916
22593
|
exports.BRIDGE_STAMP_PLUGIN = 'dsh-better-sidebar';
|
|
21917
22594
|
/** The html attribute the stamp writes (`data-zr-bridge-dsh-better-sidebar`). */
|
|
@@ -21922,15 +22599,20 @@ exports.BRIDGE_STAMP_ATTRIBUTE = `data-zr-bridge-${exports.BRIDGE_STAMP_PLUGIN}`
|
|
|
21922
22599
|
function createZhRemoteStatusFormatter() {
|
|
21923
22600
|
return (key) => locales_ts_1.zh[key];
|
|
21924
22601
|
}
|
|
21925
|
-
function describeRemoteStatus(view, t = createZhRemoteStatusFormatter()
|
|
21926
|
-
|
|
22602
|
+
function describeRemoteStatus(view, t = createZhRemoteStatusFormatter(),
|
|
22603
|
+
/** MS-6: the session on screen — its virtual id picks its OWN server's
|
|
22604
|
+
* entry; omitted (or unanswered by the dict) keeps the pre-MS-6 top-level
|
|
22605
|
+
* read. */
|
|
22606
|
+
sessionId) {
|
|
22607
|
+
const status = resolveServerStatusView(view, sessionId);
|
|
22608
|
+
if (status === undefined)
|
|
21927
22609
|
return { state: 'offline', hoverText: t('remoteStatusOffline') };
|
|
21928
|
-
if (
|
|
22610
|
+
if (status.state === 'revoked')
|
|
21929
22611
|
return { state: 'revoked', hoverText: t('remoteStatusRevoked') };
|
|
21930
|
-
if (
|
|
22612
|
+
if (status.state === 'unpaired')
|
|
21931
22613
|
return { state: 'unpaired', hoverText: t('remoteStatusUnpaired') };
|
|
21932
|
-
if (
|
|
21933
|
-
return
|
|
22614
|
+
if (status.state === 'online') {
|
|
22615
|
+
return status.versionMismatch
|
|
21934
22616
|
? { state: 'mismatch', hoverText: t('remoteStatusMismatch') }
|
|
21935
22617
|
: { state: 'online', hoverText: t('remoteStatusOnline') };
|
|
21936
22618
|
}
|
|
@@ -21948,6 +22630,8 @@ function describeRemoteStatus(view, t = createZhRemoteStatusFormatter()) {
|
|
|
21948
22630
|
* no banner.
|
|
21949
22631
|
*/
|
|
21950
22632
|
function bannerText(view, sessionId, t = (key) => locales_ts_1.zh[key]) {
|
|
22633
|
+
// The closed map is SESSION-keyed (virtual ids embed their server, so one
|
|
22634
|
+
// global dict cannot collide) and outranks every link-level line.
|
|
21951
22635
|
const reason = view?.closed[sessionId];
|
|
21952
22636
|
if (reason === 'idle')
|
|
21953
22637
|
return t('remoteBannerClosedIdle');
|
|
@@ -21957,14 +22641,17 @@ function bannerText(view, sessionId, t = (key) => locales_ts_1.zh[key]) {
|
|
|
21957
22641
|
// vocabulary): a closure with no certain reason reads as the manual close.
|
|
21958
22642
|
if (reason !== undefined)
|
|
21959
22643
|
return t('remoteBannerClosedManual');
|
|
21960
|
-
|
|
21961
|
-
|
|
22644
|
+
// MS-6: the link-level line reads the SESSION'S OWN server, not the page's
|
|
22645
|
+
// first-entry mirror.
|
|
22646
|
+
const status = resolveServerStatusView(view, sessionId);
|
|
22647
|
+
if (status !== undefined) {
|
|
22648
|
+
if (status.state === 'offline')
|
|
21962
22649
|
return t('remoteBannerOffline');
|
|
21963
22650
|
// T41a-fix2: a revoked token / an unpaired client is not "reconnecting"
|
|
21964
22651
|
// — the composer must not sit disabled on a promise no reconnect keeps.
|
|
21965
|
-
if (
|
|
22652
|
+
if (status.state === 'revoked')
|
|
21966
22653
|
return t('remoteBannerRevoked');
|
|
21967
|
-
if (
|
|
22654
|
+
if (status.state === 'unpaired')
|
|
21968
22655
|
return t('remoteBannerUnpaired');
|
|
21969
22656
|
}
|
|
21970
22657
|
return undefined;
|
|
@@ -22274,7 +22961,10 @@ function RemoteStatusIcon({ sessionId, status, t }) {
|
|
|
22274
22961
|
// still be ready from a PREVIOUS remote session when a local one opens.
|
|
22275
22962
|
if (!(0, virtual_id_js_1.isVirtual)(sessionId) || !snap.ready)
|
|
22276
22963
|
return null;
|
|
22277
|
-
|
|
22964
|
+
// MS-6: the session's virtual id picks its OWN server's entry — with
|
|
22965
|
+
// several servers paired, A online and B offline light and grey their own
|
|
22966
|
+
// sessions independently.
|
|
22967
|
+
const description = (0, remote_status_ts_1.describeRemoteStatus)(snap.view, t, sessionId);
|
|
22278
22968
|
const close = () => {
|
|
22279
22969
|
// Offline recovers on its own; revoked / unpaired do not (T34-fix) —
|
|
22280
22970
|
// each says its own word instead of "reconnecting".
|
|
@@ -22545,9 +23235,20 @@ function startCurrentSessionReporter(options = {}) {
|
|
|
22545
23235
|
return undefined;
|
|
22546
23236
|
}
|
|
22547
23237
|
};
|
|
22548
|
-
const
|
|
23238
|
+
const readLang = () => {
|
|
23239
|
+
if (options.getLang !== undefined)
|
|
23240
|
+
return options.getLang();
|
|
23241
|
+
const doc = globalThis.document;
|
|
23242
|
+
const htmlLang = doc?.documentElement?.lang;
|
|
23243
|
+
if (typeof htmlLang === 'string' && htmlLang !== '') {
|
|
23244
|
+
return htmlLang.toLowerCase().startsWith('zh') ? 'zh' : 'en';
|
|
23245
|
+
}
|
|
23246
|
+
return undefined;
|
|
23247
|
+
};
|
|
23248
|
+
const post = (report, lang) => {
|
|
22549
23249
|
const doFetch = options.fetchImpl ?? fetch;
|
|
22550
|
-
const
|
|
23250
|
+
const base = report.kind === 'open' ? { sessionId: report.sessionId } : { sessionId: null };
|
|
23251
|
+
const body = lang !== undefined ? { ...base, lang } : base;
|
|
22551
23252
|
return doFetch(options.route ?? CURRENT_SESSION_ROUTE, {
|
|
22552
23253
|
method: 'POST',
|
|
22553
23254
|
headers: { 'content-type': 'application/json; charset=utf-8' },
|
|
@@ -22561,6 +23262,7 @@ function startCurrentSessionReporter(options = {}) {
|
|
|
22561
23262
|
// T66: the timestamp of the last accepted post drives the slow unconditional
|
|
22562
23263
|
// re-send (0 = due immediately, which is the page-load post).
|
|
22563
23264
|
let lastReported;
|
|
23265
|
+
let lastReportedLang;
|
|
22564
23266
|
let lastSentAt = 0;
|
|
22565
23267
|
const resendEveryMs = options.resendIntervalMs ?? RESEND_INTERVAL_MS;
|
|
22566
23268
|
let role;
|
|
@@ -22578,16 +23280,22 @@ function startCurrentSessionReporter(options = {}) {
|
|
|
22578
23280
|
const report = (0, current_session_ts_1.parseCurrentSessionStorage)(readRaw());
|
|
22579
23281
|
if (report.kind === 'unavailable')
|
|
22580
23282
|
return;
|
|
23283
|
+
const lang = readLang();
|
|
22581
23284
|
const now = Date.now();
|
|
22582
23285
|
// T66: inside the re-send window an unchanged value says nothing; once
|
|
22583
23286
|
// the window passes, the CURRENT value re-posts even unchanged — the
|
|
22584
23287
|
// backend may have been reset underneath the page.
|
|
22585
23288
|
const resendDue = now - lastSentAt >= resendEveryMs;
|
|
22586
|
-
if (!resendDue &&
|
|
23289
|
+
if (!resendDue &&
|
|
23290
|
+
lastReported !== undefined &&
|
|
23291
|
+
(0, current_session_ts_1.currentSessionReportEquals)(report, lastReported) &&
|
|
23292
|
+
lang === lastReportedLang) {
|
|
22587
23293
|
return;
|
|
23294
|
+
}
|
|
22588
23295
|
try {
|
|
22589
|
-
await post(report);
|
|
23296
|
+
await post(report, lang);
|
|
22590
23297
|
lastReported = report;
|
|
23298
|
+
lastReportedLang = lang;
|
|
22591
23299
|
lastSentAt = now;
|
|
22592
23300
|
}
|
|
22593
23301
|
catch {
|
|
@@ -23291,13 +23999,27 @@ html[data-zr-remote-session="1"]:not([data-zr-bridge-dsh-better-sidebar="1"]) [d
|
|
|
23291
23999
|
|
|
23292
24000
|
KNOWN BOUNDARY (accepted, not fixed): in a remote session, searching the
|
|
23293
24001
|
model menu and pressing Enter can commit a HIDDEN local model — the
|
|
23294
|
-
interceptor refuses it with its usual message.
|
|
24002
|
+
interceptor refuses it with its usual message.
|
|
24003
|
+
|
|
24004
|
+
MS-4, several paired servers: each remote group heading also carries its
|
|
24005
|
+
server id (zero-width bits after the mark, virtual-id.ts
|
|
24006
|
+
encodeServerIdZeroWidth), and the effect reads the open session's server
|
|
24007
|
+
off html[data-zr-remote-server] (effects/remote-session.ts). A remote
|
|
24008
|
+
group of a DIFFERENT server is stamped "remote-other": hidden inside a
|
|
24009
|
+
remote session (only the open session's own server's models can run
|
|
24010
|
+
there) and, like every remote group, outside one. */
|
|
23295
24011
|
html[data-zr-remote-session="1"] section[role="group"][data-zr-group="local"] {
|
|
23296
24012
|
display: none !important;
|
|
23297
24013
|
}
|
|
24014
|
+
html[data-zr-remote-session="1"] section[role="group"][data-zr-group="remote-other"] {
|
|
24015
|
+
display: none !important;
|
|
24016
|
+
}
|
|
23298
24017
|
html:not([data-zr-remote-session]) section[role="group"][data-zr-group="remote"] {
|
|
23299
24018
|
display: none !important;
|
|
23300
24019
|
}
|
|
24020
|
+
html:not([data-zr-remote-session]) section[role="group"][data-zr-group="remote-other"] {
|
|
24021
|
+
display: none !important;
|
|
24022
|
+
}
|
|
23301
24023
|
|
|
23302
24024
|
/* ---------- T52-fix: hide the virtual groups in NATIVE <option> pickers ----
|
|
23303
24025
|
The model menu above is React DOM, but dsh-vision-router's settings render
|
|
@@ -23396,6 +24118,85 @@ html[data-zr-remote-session="1"] .zr-download-action[data-phase="error"] {
|
|
|
23396
24118
|
}
|
|
23397
24119
|
`;
|
|
23398
24120
|
};
|
|
24121
|
+
__modules["client-data/model-group-side.js"] = function (require, module, exports) {
|
|
24122
|
+
"use strict";
|
|
24123
|
+
/**
|
|
24124
|
+
* T74: which side of the pairing a model-menu GROUP belongs to, as pure
|
|
24125
|
+
* functions — the readable core of the browser effect
|
|
24126
|
+
* (src/client/effects/model-group-side.ts).
|
|
24127
|
+
*
|
|
24128
|
+
* WHY: the merged catalog (merge-streams.ts mergeModelCatalogs) appends the
|
|
24129
|
+
* server's provider groups everywhere, but a selection only runs on the side
|
|
24130
|
+
* the open session lives on — the interceptor refuses the other side. Through
|
|
24131
|
+
* DSH 0.1.7 every menu group carried its id in `aria-labelledby`
|
|
24132
|
+
* (`<useId>-<group.id>`), so CSS keyed the hiding on the `zr~` prefix (T52).
|
|
24133
|
+
* DSH 0.2.0 renders the groups with the primitives' MenuGroup, whose
|
|
24134
|
+
* `aria-labelledby` is a bare useId — the id is gone — and the same component
|
|
24135
|
+
* renders OTHER menus' groups too, so attribute scoping misfires in both
|
|
24136
|
+
* directions. Classification therefore moved to the one mark every version
|
|
24137
|
+
* renders: virtual group NAMES start with VIRTUAL_GROUP_NAME_MARK
|
|
24138
|
+
* (virtual-id.ts), read off each group's heading text.
|
|
24139
|
+
*
|
|
24140
|
+
* The container rule is what keeps the effect inert wherever it cannot be
|
|
24141
|
+
* sure: a container with not ONE marked heading — the model menu of a
|
|
24142
|
+
* sub-client with no shared models, every other menu on the page, the whole
|
|
24143
|
+
* rest of the page — classifies as `undefined` for every group, which the
|
|
24144
|
+
* effect reads as "leave the DOM alone". Only a container holding at least
|
|
24145
|
+
* one marked heading is decided, and inside it the unmarked groups are
|
|
24146
|
+
* exactly the LOCAL ones: the merged catalog only ever APPENDS virtual
|
|
24147
|
+
* groups to the local list, never mixes them the other way.
|
|
24148
|
+
*
|
|
24149
|
+
* Defensive like the rest of client-data: an empty or missing heading never
|
|
24150
|
+
* counts as a mark, so it can never decide a container by accident.
|
|
24151
|
+
*/
|
|
24152
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24153
|
+
exports.REMOTE_SERVER_ATTRIBUTE = void 0;
|
|
24154
|
+
exports.classifyModelGroups = classifyModelGroups;
|
|
24155
|
+
exports.classifyModelGroupsForSession = classifyModelGroupsForSession;
|
|
24156
|
+
const virtual_id_ts_1 = require("./virtual-id.js");
|
|
24157
|
+
/** MS-4: the <html> attribute carrying the open REMOTE session's server id
|
|
24158
|
+
* (`zr~<serverId>~…`'s middle half). Written by effects/remote-session.ts
|
|
24159
|
+
* together with `data-zr-remote-session`, read by
|
|
24160
|
+
* effects/model-group-side.ts as the `activeRemoteServerId`. */
|
|
24161
|
+
exports.REMOTE_SERVER_ATTRIBUTE = 'data-zr-remote-server';
|
|
24162
|
+
function classifyModelGroups(groups) {
|
|
24163
|
+
return classifyModelGroupsForSession(groups);
|
|
24164
|
+
}
|
|
24165
|
+
/**
|
|
24166
|
+
* The MS-4 extension: with `activeRemoteServerId` set (the `zr~<serverId>~`
|
|
24167
|
+
* half of the session the browser has open, effects/remote-session.ts),
|
|
24168
|
+
* each marked heading's embedded server id (decodeServerIdZeroWidth)
|
|
24169
|
+
* decides between plain `remote` (the open session's own server, or a
|
|
24170
|
+
* heading whose server cannot be decoded — shown, never hidden) and
|
|
24171
|
+
* `remote-other` (a DIFFERENT paired server's group — hidden in a remote
|
|
24172
|
+
* session by remote-session.css.ts). Without the argument the verdicts
|
|
24173
|
+
* are exactly {@link classifyModelGroups}'s — the single-paired-server
|
|
24174
|
+
* behavior, byte for byte.
|
|
24175
|
+
*/
|
|
24176
|
+
function classifyModelGroupsForSession(groups, activeRemoteServerId) {
|
|
24177
|
+
// Per container: did any heading carry the mark? Containers are decided
|
|
24178
|
+
// independently — one mixed model menu must not turn an all-local
|
|
24179
|
+
// container (a second menu elsewhere on the page) into "hide everything
|
|
24180
|
+
// local" there.
|
|
24181
|
+
const marked = new Map();
|
|
24182
|
+
for (const group of groups) {
|
|
24183
|
+
const had = marked.get(group.containerKey) ?? false;
|
|
24184
|
+
marked.set(group.containerKey, had || group.headingText.startsWith(virtual_id_ts_1.VIRTUAL_GROUP_NAME_MARK));
|
|
24185
|
+
}
|
|
24186
|
+
return groups.map((group) => {
|
|
24187
|
+
if (!marked.get(group.containerKey))
|
|
24188
|
+
return undefined;
|
|
24189
|
+
if (!group.headingText.startsWith(virtual_id_ts_1.VIRTUAL_GROUP_NAME_MARK))
|
|
24190
|
+
return 'local';
|
|
24191
|
+
if (activeRemoteServerId !== undefined) {
|
|
24192
|
+
const groupServerId = (0, virtual_id_ts_1.decodeServerIdZeroWidth)(group.headingText);
|
|
24193
|
+
if (groupServerId !== undefined && groupServerId !== activeRemoteServerId)
|
|
24194
|
+
return 'remote-other';
|
|
24195
|
+
}
|
|
24196
|
+
return 'remote';
|
|
24197
|
+
});
|
|
24198
|
+
}
|
|
24199
|
+
};
|
|
23399
24200
|
__modules["client/effects/remote-session.js"] = function (require, module, exports) {
|
|
23400
24201
|
"use strict";
|
|
23401
24202
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -23405,6 +24206,7 @@ const virtual_id_ts_1 = require("./virtual-id.js");
|
|
|
23405
24206
|
const remote_session_css_ts_1 = require("./client/styles/remote-session.css.js");
|
|
23406
24207
|
const remote_status_ts_1 = require("./client-data/remote-status.js");
|
|
23407
24208
|
const locales_ts_1 = require("./client/locales.js");
|
|
24209
|
+
const model_group_side_ts_1 = require("./client-data/model-group-side.js");
|
|
23408
24210
|
/**
|
|
23409
24211
|
* The remote-session marker (T41b): the CURRENT main-view session id, read
|
|
23410
24212
|
* the way the official uiSession `publishMain` reads it (RT
|
|
@@ -23414,6 +24216,12 @@ const locales_ts_1 = require("./client/locales.js");
|
|
|
23414
24216
|
* every open-on-the-server-machine entry, so the decision lives at the one
|
|
23415
24217
|
* place that knows the session and never inside component DOM.
|
|
23416
24218
|
*
|
|
24219
|
+
* MS-4: with several servers paired, the virtual id's serverId half is
|
|
24220
|
+
* stamped alongside as `data-zr-remote-server="<serverId>"` — the model
|
|
24221
|
+
* menu's per-server isolation (effects/model-group-side.ts reads it to hide
|
|
24222
|
+
* the OTHER paired servers' groups; remote-session.css.ts hides them). Both
|
|
24223
|
+
* attributes move together: a local session (or unmount) clears both.
|
|
24224
|
+
*
|
|
23417
24225
|
* The official tie-break is kept verbatim: when the previous answer is still
|
|
23418
24226
|
* mainView-retained it STAYS the answer, and only otherwise does the first
|
|
23419
24227
|
* mainView-retained row in catalog order win — so two simultaneously
|
|
@@ -23462,10 +24270,16 @@ function installRemoteSessionGuard(ctx) {
|
|
|
23462
24270
|
const apply = () => {
|
|
23463
24271
|
const sessionId = currentMainSessionIdOf(ctx);
|
|
23464
24272
|
const root = document.documentElement;
|
|
23465
|
-
|
|
24273
|
+
const parts = sessionId !== undefined ? (0, virtual_id_ts_1.fromVirtual)(sessionId) : undefined;
|
|
24274
|
+
if (parts !== undefined) {
|
|
23466
24275
|
root.setAttribute('data-zr-remote-session', '1');
|
|
23467
|
-
|
|
24276
|
+
if (root.getAttribute(model_group_side_ts_1.REMOTE_SERVER_ATTRIBUTE) !== parts.serverId)
|
|
24277
|
+
root.setAttribute(model_group_side_ts_1.REMOTE_SERVER_ATTRIBUTE, parts.serverId);
|
|
24278
|
+
}
|
|
24279
|
+
else {
|
|
23468
24280
|
root.removeAttribute('data-zr-remote-session');
|
|
24281
|
+
root.removeAttribute(model_group_side_ts_1.REMOTE_SERVER_ATTRIBUTE);
|
|
24282
|
+
}
|
|
23469
24283
|
};
|
|
23470
24284
|
apply();
|
|
23471
24285
|
const offs = [ctx.sessions.list.subscribe(apply)];
|
|
@@ -23479,6 +24293,7 @@ function installRemoteSessionGuard(ctx) {
|
|
|
23479
24293
|
for (const off of offs)
|
|
23480
24294
|
off();
|
|
23481
24295
|
document.documentElement.removeAttribute('data-zr-remote-session');
|
|
24296
|
+
document.documentElement.removeAttribute(model_group_side_ts_1.REMOTE_SERVER_ATTRIBUTE);
|
|
23482
24297
|
};
|
|
23483
24298
|
}, 'dsh-zen-remote: remote-session attribute');
|
|
23484
24299
|
// The bridge stamp (PB-14): while a REMOTE session is on screen, the
|
|
@@ -23502,7 +24317,7 @@ function installRemoteSessionGuard(ctx) {
|
|
|
23502
24317
|
};
|
|
23503
24318
|
const applyBridge = () => {
|
|
23504
24319
|
const view = store.getSnapshot().view;
|
|
23505
|
-
if ((0, remote_status_ts_1.
|
|
24320
|
+
if ((0, remote_status_ts_1.shouldStampBridgeForSession)(view, currentMainSessionIdOf(ctx), remote_status_ts_1.BRIDGE_STAMP_PLUGIN)) {
|
|
23506
24321
|
root.setAttribute(remote_status_ts_1.BRIDGE_STAMP_ATTRIBUTE, '1');
|
|
23507
24322
|
root.style.removeProperty('--zr-bridge-note');
|
|
23508
24323
|
}
|
|
@@ -23521,7 +24336,13 @@ function installRemoteSessionGuard(ctx) {
|
|
|
23521
24336
|
applyBridge();
|
|
23522
24337
|
return;
|
|
23523
24338
|
}
|
|
23524
|
-
|
|
24339
|
+
// MS-6: remote A → remote B keeps the subscription, but the verdict
|
|
24340
|
+
// belongs to the session's own server and must be re-judged.
|
|
24341
|
+
if (virtualNow) {
|
|
24342
|
+
applyBridge();
|
|
24343
|
+
return;
|
|
24344
|
+
}
|
|
24345
|
+
if (offStore !== undefined) {
|
|
23525
24346
|
offStore();
|
|
23526
24347
|
offStore = undefined;
|
|
23527
24348
|
clear();
|
|
@@ -23541,57 +24362,6 @@ function installRemoteSessionGuard(ctx) {
|
|
|
23541
24362
|
}, 'dsh-zen-remote: bridge stamp');
|
|
23542
24363
|
}
|
|
23543
24364
|
};
|
|
23544
|
-
__modules["client-data/model-group-side.js"] = function (require, module, exports) {
|
|
23545
|
-
"use strict";
|
|
23546
|
-
/**
|
|
23547
|
-
* T74: which side of the pairing a model-menu GROUP belongs to, as pure
|
|
23548
|
-
* functions — the readable core of the browser effect
|
|
23549
|
-
* (src/client/effects/model-group-side.ts).
|
|
23550
|
-
*
|
|
23551
|
-
* WHY: the merged catalog (merge-streams.ts mergeModelCatalogs) appends the
|
|
23552
|
-
* server's provider groups everywhere, but a selection only runs on the side
|
|
23553
|
-
* the open session lives on — the interceptor refuses the other side. Through
|
|
23554
|
-
* DSH 0.1.7 every menu group carried its id in `aria-labelledby`
|
|
23555
|
-
* (`<useId>-<group.id>`), so CSS keyed the hiding on the `zr~` prefix (T52).
|
|
23556
|
-
* DSH 0.2.0 renders the groups with the primitives' MenuGroup, whose
|
|
23557
|
-
* `aria-labelledby` is a bare useId — the id is gone — and the same component
|
|
23558
|
-
* renders OTHER menus' groups too, so attribute scoping misfires in both
|
|
23559
|
-
* directions. Classification therefore moved to the one mark every version
|
|
23560
|
-
* renders: virtual group NAMES start with VIRTUAL_GROUP_NAME_MARK
|
|
23561
|
-
* (virtual-id.ts), read off each group's heading text.
|
|
23562
|
-
*
|
|
23563
|
-
* The container rule is what keeps the effect inert wherever it cannot be
|
|
23564
|
-
* sure: a container with not ONE marked heading — the model menu of a
|
|
23565
|
-
* sub-client with no shared models, every other menu on the page, the whole
|
|
23566
|
-
* rest of the page — classifies as `undefined` for every group, which the
|
|
23567
|
-
* effect reads as "leave the DOM alone". Only a container holding at least
|
|
23568
|
-
* one marked heading is decided, and inside it the unmarked groups are
|
|
23569
|
-
* exactly the LOCAL ones: the merged catalog only ever APPENDS virtual
|
|
23570
|
-
* groups to the local list, never mixes them the other way.
|
|
23571
|
-
*
|
|
23572
|
-
* Defensive like the rest of client-data: an empty or missing heading never
|
|
23573
|
-
* counts as a mark, so it can never decide a container by accident.
|
|
23574
|
-
*/
|
|
23575
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23576
|
-
exports.classifyModelGroups = classifyModelGroups;
|
|
23577
|
-
const virtual_id_ts_1 = require("./virtual-id.js");
|
|
23578
|
-
function classifyModelGroups(groups) {
|
|
23579
|
-
// Per container: did any heading carry the mark? Containers are decided
|
|
23580
|
-
// independently — one mixed model menu must not turn an all-local
|
|
23581
|
-
// container (a second menu elsewhere on the page) into "hide everything
|
|
23582
|
-
// local" there.
|
|
23583
|
-
const marked = new Map();
|
|
23584
|
-
for (const group of groups) {
|
|
23585
|
-
const had = marked.get(group.containerKey) ?? false;
|
|
23586
|
-
marked.set(group.containerKey, had || group.headingText.startsWith(virtual_id_ts_1.VIRTUAL_GROUP_NAME_MARK));
|
|
23587
|
-
}
|
|
23588
|
-
return groups.map((group) => marked.get(group.containerKey)
|
|
23589
|
-
? group.headingText.startsWith(virtual_id_ts_1.VIRTUAL_GROUP_NAME_MARK)
|
|
23590
|
-
? 'remote'
|
|
23591
|
-
: 'local'
|
|
23592
|
-
: undefined);
|
|
23593
|
-
}
|
|
23594
|
-
};
|
|
23595
24365
|
__modules["client/effects/model-group-side.js"] = function (require, module, exports) {
|
|
23596
24366
|
"use strict";
|
|
23597
24367
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -23608,6 +24378,13 @@ const model_group_side_ts_1 = require("./client-data/model-group-side.js");
|
|
|
23608
24378
|
* virtual-id.ts) heading is decided at all, so every other menu and the
|
|
23609
24379
|
* whole rest of the page is never touched.
|
|
23610
24380
|
*
|
|
24381
|
+
* MS-4: the open remote session's server id (`data-zr-remote-server` on
|
|
24382
|
+
* <html>, written by effects/remote-session.ts) is the classifier's
|
|
24383
|
+
* `activeRemoteServerId` — a remote group whose heading decodes to a
|
|
24384
|
+
* DIFFERENT paired server is stamped `remote-other`. Switching between two
|
|
24385
|
+
* servers' sessions changes no group DOM at all, so a second observer
|
|
24386
|
+
* watches that one <html> attribute and rescans the moment it moves.
|
|
24387
|
+
*
|
|
23611
24388
|
* Synchronous in the observer callback, never behind rAF: the callback is a
|
|
23612
24389
|
* microtask and runs before this frame paints, so a group that must be
|
|
23613
24390
|
* hidden is hidden the first time it is drawn. A rAF would land a frame late
|
|
@@ -23618,8 +24395,8 @@ const model_group_side_ts_1 = require("./client-data/model-group-side.js");
|
|
|
23618
24395
|
*/
|
|
23619
24396
|
const GROUP_SELECTOR = 'section[role="group"]';
|
|
23620
24397
|
/** The stamp this effect writes; remote-session.css.ts hides
|
|
23621
|
-
* `[data-zr-group="local"]`
|
|
23622
|
-
*
|
|
24398
|
+
* `[data-zr-group="local"]` and `[data-zr-group="remote-other"]` in a
|
|
24399
|
+
* remote session and both remote stamps everywhere else. */
|
|
23623
24400
|
const STAMP = 'data-zr-group';
|
|
23624
24401
|
/**
|
|
23625
24402
|
* The heading text a group section renders, across the two DSH layouts:
|
|
@@ -23701,7 +24478,8 @@ function installModelGroupSide(ctx) {
|
|
|
23701
24478
|
const text = headingTextOf(section, (id) => document.getElementById(id));
|
|
23702
24479
|
rows.push({ section, containerKey: key, headingText: text ?? '' });
|
|
23703
24480
|
}
|
|
23704
|
-
const
|
|
24481
|
+
const activeRemoteServerId = document.documentElement.getAttribute(model_group_side_ts_1.REMOTE_SERVER_ATTRIBUTE) ?? undefined;
|
|
24482
|
+
const verdicts = (0, model_group_side_ts_1.classifyModelGroupsForSession)(rows, activeRemoteServerId);
|
|
23705
24483
|
rows.forEach((row, i) => applyVerdict(row.section, verdicts[i]));
|
|
23706
24484
|
};
|
|
23707
24485
|
// childList + subtree catches mounted/unmounted/re-parented groups,
|
|
@@ -23716,12 +24494,18 @@ function installModelGroupSide(ctx) {
|
|
|
23716
24494
|
}
|
|
23717
24495
|
});
|
|
23718
24496
|
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
24497
|
+
// MS-4: the session's server moves on <html> alone (A's session → B's
|
|
24498
|
+
// with the menu open touches no group) — rescan synchronously, same
|
|
24499
|
+
// no-rAF discipline as above.
|
|
24500
|
+
const serverObserver = new MutationObserver(() => scan());
|
|
24501
|
+
serverObserver.observe(document.documentElement, { attributes: true, attributeFilter: [model_group_side_ts_1.REMOTE_SERVER_ATTRIBUTE] });
|
|
23719
24502
|
// The initial paint may already hold groups (session restored under an
|
|
23720
24503
|
// open menu): stamp them synchronously at install, like every observer
|
|
23721
24504
|
// effect here does.
|
|
23722
24505
|
scan();
|
|
23723
24506
|
return () => {
|
|
23724
24507
|
observer.disconnect();
|
|
24508
|
+
serverObserver.disconnect();
|
|
23725
24509
|
// Undo every stamp so the host DOM reads exactly as before the effect
|
|
23726
24510
|
// ran — the CSS rules key on the stamp, so this un-hides everything.
|
|
23727
24511
|
for (const section of document.querySelectorAll(`${GROUP_SELECTOR}[${STAMP}]`)) {
|