@jingyi0605/codingns4dsh 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +306 -0
- package/README.md +190 -993
- package/data/build/dist/client/account-bar.d.ts +2 -2
- package/data/build/dist/client/account-bar.js +96 -39
- package/data/build/dist/client/bundle.js +13722 -3611
- package/data/build/dist/client/cli-catalog.js +20 -3
- package/data/build/dist/client/cli-slots.d.ts +5 -0
- package/data/build/dist/client/cli-slots.js +57 -7
- package/data/build/dist/client/debug/ui.d.ts +1 -1
- package/data/build/dist/client/debug/ui.js +62 -32
- package/data/build/dist/client/dsh-runtime-version.d.ts +9 -2
- package/data/build/dist/client/dsh-runtime-version.js +24 -2
- package/data/build/dist/client/features/cli-adapters.d.ts +1 -1
- package/data/build/dist/client/features/cli-adapters.js +19 -16
- package/data/build/dist/client/features/debug.js +2 -2
- package/data/build/dist/client/features/index.d.ts +2 -1
- package/data/build/dist/client/features/index.js +3 -1
- package/data/build/dist/client/features/lan-access-panel.d.ts +1 -1
- package/data/build/dist/client/features/lan-access-panel.js +8 -10
- package/data/build/dist/client/features/lan-access.js +1 -1
- package/data/build/dist/client/features/login-protection-panel.d.ts +1 -1
- package/data/build/dist/client/features/login-protection-panel.js +7 -9
- package/data/build/dist/client/features/reverse-proxy-panel.d.ts +1 -1
- package/data/build/dist/client/features/reverse-proxy-panel.js +99 -11
- package/data/build/dist/client/features/reverse-proxy.d.ts +1 -1
- package/data/build/dist/client/features/reverse-proxy.js +19 -18
- package/data/build/dist/client/features/terminal-enhancement-panel.d.ts +1 -1
- package/data/build/dist/client/features/terminal-enhancement-panel.js +62 -55
- package/data/build/dist/client/features/terminal-enhancement.d.ts +1 -1
- package/data/build/dist/client/features/terminal-enhancement.js +3 -3
- package/data/build/dist/client/features/types.d.ts +14 -3
- package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts +1 -1
- package/data/build/dist/client/features/workspace-session-enhancement-panel.js +31 -9
- package/data/build/dist/client/features/workspace-session-enhancement.js +72 -11
- package/data/build/dist/client/git-management.d.ts +16 -0
- package/data/build/dist/client/git-management.js +757 -0
- package/data/build/dist/client/index.d.ts +3 -2
- package/data/build/dist/client/index.js +152 -27
- package/data/build/dist/client/locale.js +91 -29
- package/data/build/dist/client/quick-phrase-layout.d.ts +49 -0
- package/data/build/dist/client/quick-phrase-layout.js +95 -0
- package/data/build/dist/client/quick-phrase-slot.d.ts +7 -0
- package/data/build/dist/client/quick-phrase-slot.js +370 -0
- package/data/build/dist/client/remote-web-context.js +1 -1
- package/data/build/dist/client/settings-bridge.d.ts +9 -6
- package/data/build/dist/client/settings-bridge.js +51 -27
- package/data/build/dist/client/settings-section.d.ts +3 -2
- package/data/build/dist/client/settings-section.js +33 -19
- package/data/build/dist/client/subscription-slot.d.ts +3 -0
- package/data/build/dist/client/subscription-slot.js +108 -16
- package/data/build/dist/client/terminal/model.d.ts +14 -2
- package/data/build/dist/client/terminal/model.js +109 -21
- package/data/build/dist/client/terminal/recovery.d.ts +50 -0
- package/data/build/dist/client/terminal/recovery.js +101 -0
- package/data/build/dist/client/terminal/ui.d.ts +2 -2
- package/data/build/dist/client/terminal/ui.js +49 -11
- package/data/build/dist/client/terminal/xterm-view.d.ts +2 -2
- package/data/build/dist/client/terminal/xterm-view.js +2 -1
- package/data/build/dist/client/theme.d.ts +6 -1
- package/data/build/dist/client/theme.js +32 -1
- package/data/build/dist/client/workspace-session-archive-dom.d.ts +12 -0
- package/data/build/dist/client/workspace-session-archive-dom.js +27 -5
- package/data/build/dist/client/workspace-session-visibility-dom.d.ts +30 -0
- package/data/build/dist/client/workspace-session-visibility-dom.js +604 -0
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.d.ts +50 -0
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +139 -0
- package/data/build/dist/dsh-capabilities/client/conversation-adapter.d.ts +7 -0
- package/data/build/dist/dsh-capabilities/client/conversation-adapter.js +6 -0
- package/data/build/dist/dsh-capabilities/client/locale-adapter.d.ts +9 -0
- package/data/build/dist/dsh-capabilities/client/locale-adapter.js +4 -0
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +10 -0
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +14 -0
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +8 -0
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +16 -0
- package/data/build/dist/dsh-capabilities/client/sidebar-adapter.d.ts +7 -0
- package/data/build/dist/dsh-capabilities/client/sidebar-adapter.js +5 -0
- package/data/build/dist/dsh-capabilities/client/theme-adapter.d.ts +6 -0
- package/data/build/dist/dsh-capabilities/client/theme-adapter.js +4 -0
- package/data/build/dist/dsh-capabilities/client/typert-adapter.d.ts +5 -0
- package/data/build/dist/dsh-capabilities/client/typert-adapter.js +4 -0
- package/data/build/dist/dsh-capabilities/host/config-forms-adapter.d.ts +19 -0
- package/data/build/dist/dsh-capabilities/host/config-forms-adapter.js +45 -0
- package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.d.ts +19 -0
- package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.js +13 -0
- package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.d.ts +8 -0
- package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.js +47 -0
- package/data/build/dist/dsh-capabilities/host/typert-adapter.d.ts +6 -0
- package/data/build/dist/dsh-capabilities/host/typert-adapter.js +4 -0
- package/data/build/dist/dsh-capabilities/index.d.ts +6 -0
- package/data/build/dist/dsh-capabilities/index.js +6 -0
- package/data/build/dist/dsh-capabilities/matrix.d.ts +15 -0
- package/data/build/dist/dsh-capabilities/matrix.js +32 -0
- package/data/build/dist/dsh-capabilities/registry.d.ts +22 -0
- package/data/build/dist/dsh-capabilities/registry.js +196 -0
- package/data/build/dist/dsh-capabilities/routes.d.ts +10 -0
- package/data/build/dist/dsh-capabilities/routes.js +34 -0
- package/data/build/dist/dsh-capabilities/settings-store.d.ts +29 -0
- package/data/build/dist/dsh-capabilities/settings-store.js +5 -0
- package/data/build/dist/dsh-capabilities/types.d.ts +50 -0
- package/data/build/dist/dsh-capabilities/types.js +23 -0
- package/data/build/dist/features/registry.d.ts +8 -2
- package/data/build/dist/features/registry.js +104 -1
- package/data/build/dist/host/auth-session.d.ts +8 -1
- package/data/build/dist/host/auth-session.js +28 -6
- package/data/build/dist/host/cli-adapters/claude-driver.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/claude-driver.js +19 -4
- package/data/build/dist/host/cli-adapters/claude-model-options.d.ts +14 -0
- package/data/build/dist/host/cli-adapters/claude-model-options.js +157 -0
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts +11 -0
- package/data/build/dist/host/cli-adapters/codex-driver.js +166 -14
- package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +15 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.js +120 -29
- package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts +4 -1
- package/data/build/dist/host/cli-adapters/dsh-tool-history.js +25 -7
- package/data/build/dist/host/cli-adapters/feature.js +74 -8
- package/data/build/dist/host/cli-adapters/gemini-driver.js +2 -2
- package/data/build/dist/host/cli-adapters/grok-driver.js +2 -2
- package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +4 -2
- package/data/build/dist/host/cli-adapters/json-rpc-process.js +8 -9
- package/data/build/dist/host/cli-adapters/kimi-driver.js +1 -1
- package/data/build/dist/host/cli-adapters/legacy-session-adapter.d.ts +17 -0
- package/data/build/dist/host/cli-adapters/legacy-session-adapter.js +88 -0
- package/data/build/dist/host/cli-adapters/legacy-session-settings.d.ts +24 -0
- package/data/build/dist/host/cli-adapters/legacy-session-settings.js +162 -0
- package/data/build/dist/host/cli-adapters/official-provider-subscription.d.ts +29 -0
- package/data/build/dist/host/cli-adapters/official-provider-subscription.js +361 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.js +97 -12
- package/data/build/dist/host/cli-adapters/pi-driver.js +69 -23
- package/data/build/dist/host/cli-adapters/provider-registry.d.ts +33 -0
- package/data/build/dist/host/cli-adapters/provider-registry.js +83 -0
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +25 -3
- package/data/build/dist/host/cli-adapters/provider-subscription.js +346 -30
- package/data/build/dist/host/cli-adapters/registry.d.ts +6 -0
- package/data/build/dist/host/cli-adapters/registry.js +170 -13
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +10 -0
- package/data/build/dist/host/cli-adapters/session-store.d.ts +7 -0
- package/data/build/dist/host/cli-adapters/session-store.js +39 -1
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
- package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +3 -0
- package/data/build/dist/host/cli-adapters/stream-normalizer.js +22 -2
- package/data/build/dist/host/control-api-client.d.ts +3 -3
- package/data/build/dist/host/control-api-client.js +2 -1
- package/data/build/dist/host/credential-store.js +11 -1
- package/data/build/dist/host/dsh-device-runtime.d.ts +4 -0
- package/data/build/dist/host/dsh-device-runtime.js +20 -9
- package/data/build/dist/host/dsh-runtime-version.d.ts +3 -2
- package/data/build/dist/host/dsh-runtime-version.js +71 -2
- package/data/build/dist/host/features/auth.js +8 -5
- package/data/build/dist/host/features/debug.js +1 -1
- package/data/build/dist/host/features/git-management.d.ts +5 -0
- package/data/build/dist/host/features/git-management.js +290 -0
- package/data/build/dist/host/features/host-status.js +1 -1
- package/data/build/dist/host/features/index.d.ts +1 -0
- package/data/build/dist/host/features/index.js +3 -0
- package/data/build/dist/host/features/terminal-process.js +1 -1
- package/data/build/dist/host/features/terminal-status.d.ts +3 -1
- package/data/build/dist/host/features/terminal-status.js +3 -1
- package/data/build/dist/host/index.d.ts +7 -3
- package/data/build/dist/host/index.js +79 -15
- package/data/build/dist/host/lan-access-dsh.js +18 -5
- package/data/build/dist/host/native-session-bridge.d.ts +23 -1
- package/data/build/dist/host/native-session-bridge.js +157 -10
- package/data/build/dist/host/rpc-table.d.ts +1 -1
- package/data/build/dist/host/rpc.js +60 -10
- package/data/build/dist/host/session-migration-repair.d.ts +19 -0
- package/data/build/dist/host/session-migration-repair.js +395 -0
- package/data/build/dist/host/settings.d.ts +8 -1
- package/data/build/dist/host/settings.js +94 -3
- package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +1 -0
- package/data/build/dist/host/terminal/backends/tmux-backend.js +4 -2
- package/data/build/dist/host/terminal/controller-factory.d.ts +4 -0
- package/data/build/dist/host/terminal/controller-factory.js +26 -7
- package/data/build/dist/host/terminal/runtime-manager.d.ts +2 -1
- package/data/build/dist/host/terminal/runtime-manager.js +3 -0
- package/data/build/dist/host/terminal/startup.d.ts +2 -0
- package/data/build/dist/host/terminal/startup.js +2 -1
- package/data/build/dist/host/terminal/terminal-controller.d.ts +10 -3
- package/data/build/dist/host/terminal/terminal-controller.js +101 -15
- package/data/build/dist/host/terminal/terminal-process-service.js +3 -1
- package/data/build/dist/host/terminal/terminal-service.d.ts +2 -0
- package/data/build/dist/host/terminal/terminal-service.js +10 -0
- package/data/build/dist/index.d.ts +6 -2
- package/data/build/dist/index.js +8 -2
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +14 -0
- package/data/build/dist/shared/contracts/config.d.ts +31 -1
- package/data/build/dist/shared/contracts/config.js +25 -0
- package/data/build/dist/shared/contracts/dsh-device.d.ts +13 -1
- package/data/build/dist/shared/contracts/errors.d.ts +1 -1
- package/data/build/dist/shared/contracts/feature.d.ts +6 -0
- package/data/build/dist/shared/contracts/git.d.ts +82 -0
- package/data/build/dist/shared/contracts/git.js +3 -0
- package/data/build/dist/shared/contracts/subscription.d.ts +46 -0
- package/data/build/dist/shared/contracts/terminal.d.ts +4 -1
- package/data/build/dist/shared/contracts/version.d.ts +2 -2
- package/data/build/dist/shared/contracts/version.js +4 -6
- package/data/build/dist/shared/debug.d.ts +14 -0
- package/data/build/dist/shared/debug.js +53 -0
- package/data/build/dist/shared/index.d.ts +7 -4
- package/data/build/dist/shared/index.js +2 -1
- package/data/build/dist/transport/debug.d.ts +1 -1
- package/data/build/dist/transport/debug.js +3 -30
- package/data/build/dist/transport/dsh-session.js +3 -3
- package/data/build/dist/typert.host.d.ts +1 -1
- package/data/build/dist/typert.host.js +6 -3
- package/data/build/dist/typert.remote-client.d.ts +11 -0
- package/data/build/dist/typert.remote-client.js +7 -0
- package/dsh.bundle.patch +1 -1
- package/package.json +13 -5
- package/profile/README.md +4 -4
- package/profile/package.json +6 -4
- package/profile/scripts/check-dsh-install.mjs +2 -2
- package/profile/version.json +2 -2
- package/scripts/check-dsh-install.mjs +2 -2
- package/version.json +2 -2
|
@@ -5,6 +5,7 @@ import { cliAdaptersFeature } from './cli-adapters.js';
|
|
|
5
5
|
import { terminalEnhancementFeature } from './terminal-enhancement.js';
|
|
6
6
|
import { workspaceSessionEnhancementFeature } from './workspace-session-enhancement.js';
|
|
7
7
|
import { debugFeature } from './debug.js';
|
|
8
|
+
import { gitManagementFeature } from '../git-management.js';
|
|
8
9
|
/** Client 侧功能模块清单:新增模块在这里登记一行,不需要改动设置页和入口。 */
|
|
9
10
|
export const CLIENT_FEATURES = [
|
|
10
11
|
lanAccessFeature,
|
|
@@ -14,6 +15,7 @@ export const CLIENT_FEATURES = [
|
|
|
14
15
|
workspaceSessionEnhancementFeature,
|
|
15
16
|
terminalEnhancementFeature,
|
|
16
17
|
debugFeature,
|
|
18
|
+
gitManagementFeature,
|
|
17
19
|
];
|
|
18
20
|
/**
|
|
19
21
|
* 汇总需要在设置页显示的模块,按 ui.order 升序。
|
|
@@ -33,7 +35,7 @@ export function settingsModules(registry) {
|
|
|
33
35
|
entries.sort((left, right) => (left.ui.order ?? 0) - (right.ui.order ?? 0));
|
|
34
36
|
return entries;
|
|
35
37
|
}
|
|
36
|
-
export { lanAccessFeature, loginProtectionFeature, reverseProxyFeature, cliAdaptersFeature, workspaceSessionEnhancementFeature, terminalEnhancementFeature, debugFeature };
|
|
38
|
+
export { lanAccessFeature, loginProtectionFeature, reverseProxyFeature, cliAdaptersFeature, workspaceSessionEnhancementFeature, terminalEnhancementFeature, debugFeature, gitManagementFeature };
|
|
37
39
|
export { startBrowserRelayConnection } from './reverse-proxy.js';
|
|
38
40
|
export { LanAccessPanel } from './lan-access-panel.js';
|
|
39
41
|
export { LoginProtectionPanel } from './login-protection-panel.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactElement } from 'react';
|
|
2
2
|
import type { FeaturePanelProps } from './types.js';
|
|
3
3
|
/** “局域网访问DSH”设置卡片:只配置一条监听并转发到当前 DSH Web。 */
|
|
4
|
-
export declare function LanAccessPanel({ services, enabled, snapshot: settingsSnapshot }: FeaturePanelProps): ReactElement;
|
|
4
|
+
export declare function LanAccessPanel({ services, enabled, snapshot: settingsSnapshot, notify }: FeaturePanelProps): ReactElement;
|
|
5
5
|
//# sourceMappingURL=lan-access-panel.d.ts.map
|
|
@@ -3,7 +3,7 @@ import { CODINGNS_RPC_CHANNEL } from '../../shared/contracts/transport.js';
|
|
|
3
3
|
import { dshFormRootStyle, dshSettingsButtonStyle, dshSettingsFieldLabelStyle, dshSettingsFieldStyle, dshSettingsNoteStyle, dshSettingsPrimaryButtonStyle, dshSettingsRowStyle, dshThemeColor, } from '../theme.js';
|
|
4
4
|
import { useCodingNsTranslator } from '../locale.js';
|
|
5
5
|
/** “局域网访问DSH”设置卡片:只配置一条监听并转发到当前 DSH Web。 */
|
|
6
|
-
export function LanAccessPanel({ services, enabled, snapshot: settingsSnapshot }) {
|
|
6
|
+
export function LanAccessPanel({ services, enabled, snapshot: settingsSnapshot, notify }) {
|
|
7
7
|
const { rpc, settings } = services;
|
|
8
8
|
const t = useCodingNsTranslator(services.locale);
|
|
9
9
|
const [savedSettings, setSavedSettings] = useState();
|
|
@@ -17,7 +17,6 @@ export function LanAccessPanel({ services, enabled, snapshot: settingsSnapshot }
|
|
|
17
17
|
const [detectedDshPorts, setDetectedDshPorts] = useState([]);
|
|
18
18
|
const [snapshot, setSnapshot] = useState(null);
|
|
19
19
|
const [busy, setBusy] = useState(false);
|
|
20
|
-
const [message, setMessage] = useState('');
|
|
21
20
|
useEffect(() => {
|
|
22
21
|
if (disabled)
|
|
23
22
|
return;
|
|
@@ -53,12 +52,11 @@ export function LanAccessPanel({ services, enabled, snapshot: settingsSnapshot }
|
|
|
53
52
|
}, [savedSettings]);
|
|
54
53
|
const run = async (operation) => {
|
|
55
54
|
setBusy(true);
|
|
56
|
-
setMessage('');
|
|
57
55
|
try {
|
|
58
56
|
await operation();
|
|
59
57
|
}
|
|
60
58
|
catch (error) {
|
|
61
|
-
|
|
59
|
+
notify({ kind: 'error', message: error instanceof Error ? error.message : String(error) });
|
|
62
60
|
}
|
|
63
61
|
finally {
|
|
64
62
|
setBusy(false);
|
|
@@ -69,7 +67,7 @@ export function LanAccessPanel({ services, enabled, snapshot: settingsSnapshot }
|
|
|
69
67
|
setDetectedDshPorts(result.ports);
|
|
70
68
|
if (result.ports.length === 1)
|
|
71
69
|
setDshPort(String(result.ports[0]));
|
|
72
|
-
|
|
70
|
+
notify({ kind: 'info', message: result.ports.length === 0 ? t('lan.detectNone') : t('lan.detected', { ports: result.ports.join('、') }) });
|
|
73
71
|
});
|
|
74
72
|
const saveMapping = async (nextAutoStart = autoStart) => {
|
|
75
73
|
const saved = await callRpc(rpc, 'lanAccessDsh/settings/set', readMapping(listenHost, listenPort, dshPort, nextAutoStart));
|
|
@@ -79,7 +77,7 @@ export function LanAccessPanel({ services, enabled, snapshot: settingsSnapshot }
|
|
|
79
77
|
if (!autoStart)
|
|
80
78
|
return;
|
|
81
79
|
void saveMapping().catch((error) => {
|
|
82
|
-
|
|
80
|
+
notify({ kind: 'error', message: error instanceof Error ? error.message : String(error) });
|
|
83
81
|
});
|
|
84
82
|
};
|
|
85
83
|
const start = () => run(async () => {
|
|
@@ -94,22 +92,22 @@ export function LanAccessPanel({ services, enabled, snapshot: settingsSnapshot }
|
|
|
94
92
|
setSnapshot(current);
|
|
95
93
|
setDshPort(String(current.dshPort));
|
|
96
94
|
setListenPort(String(current.listenPort));
|
|
97
|
-
|
|
95
|
+
notify({ kind: 'success', message: t('lan.started', { host: current.listenHost, port: current.actualListenPort ?? current.listenPort, dshPort: current.dshPort }) });
|
|
98
96
|
});
|
|
99
97
|
const stop = () => run(async () => {
|
|
100
98
|
await callRpc(rpc, 'lanAccessDsh/stop', {});
|
|
101
99
|
setSnapshot(null);
|
|
102
|
-
|
|
100
|
+
notify({ kind: 'success', message: t('lan.stopped') });
|
|
103
101
|
});
|
|
104
102
|
const toggleAutoStart = () => run(async () => {
|
|
105
103
|
const next = !autoStart;
|
|
106
104
|
await saveMapping(next);
|
|
107
105
|
setAutoStart(next);
|
|
108
|
-
|
|
106
|
+
notify({ kind: 'success', message: next ? t('lan.autoStartOn') : t('lan.autoStartOff') });
|
|
109
107
|
});
|
|
110
108
|
const fieldStyle = dshSettingsFieldStyle;
|
|
111
109
|
const buttonStyle = dshSettingsButtonStyle;
|
|
112
|
-
return createElement('div', { 'aria-disabled': controlsDisabled, style: { ...dshFormRootStyle, display: 'flex', flexDirection: 'column', gap: 14, opacity: controlsDisabled ? 0.5 : 1, pointerEvents: controlsDisabled ? 'none' : 'auto' } }, createElement('p', { style: { margin: 0, color: dshThemeColor.labelSecondary, fontSize: 13, lineHeight: 1.5 } }, t('lan.description')), createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, createElement('span', { style: dshSettingsFieldLabelStyle }, t('lan.listenHost')), createElement('select', { value: listenHost, disabled: controlsDisabled || busy, onChange: (event) => setListenHost(event.currentTarget.value), onBlur: saveMappingOnBlur, style: fieldStyle }, ...listenHosts.map((host) => createElement('option', { key: host, value: host }, host === '0.0.0.0' ? t('lan.allInterfaces') : host)))), createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, createElement('span', { style: dshSettingsFieldLabelStyle }, t('lan.listenPort')), createElement('input', { type: 'number', min: 0, max: 65535, placeholder: t('lan.listenPort'), value: listenPort, disabled: controlsDisabled || busy, onChange: (event) => setListenPort(event.currentTarget.value), onBlur: saveMappingOnBlur, style: fieldStyle })), createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, createElement('span', { style: dshSettingsFieldLabelStyle }, t('lan.dshPort')), createElement('div', { style: dshSettingsRowStyle }, createElement('input', { type: 'number', min: 1, max: 65535, placeholder: t('lan.dshPort'), value: dshPort, disabled: controlsDisabled || busy, onChange: (event) => setDshPort(event.currentTarget.value), onBlur: saveMappingOnBlur, style: { ...fieldStyle, flex: 1, minWidth: 0 } }), createElement('button', { type: 'button', disabled: controlsDisabled || busy, onClick: () => void detect(), style: buttonStyle }, t('lan.detect')))), detectedDshPorts.length > 1 && createElement('div', { style: dshSettingsNoteStyle }, t('lan.detectMultiple', { ports: detectedDshPorts.join('、') })), createElement('label', { style: { display: 'flex', alignItems: 'center', gap: 8, cursor: controlsDisabled || busy ? 'not-allowed' : 'pointer', color: dshThemeColor.labelSecondary, fontSize: 13 } }, createElement('input', { type: 'checkbox', checked: autoStart, disabled: controlsDisabled || busy, onChange: () => void toggleAutoStart(), style: { accentColor: dshThemeColor.accent } }), createElement('span', undefined, t('lan.autoStart'))), createElement('div', { style: dshSettingsRowStyle }, createElement('button', { type: 'button', disabled: controlsDisabled || busy || !listenPort, onClick: () => void start(), style: { ...dshSettingsPrimaryButtonStyle, flex: 1 } }, busy ? t('lan.processing') : snapshot ? t('lan.update') : t('lan.start')), snapshot && createElement('button', { type: 'button', disabled: controlsDisabled || busy, onClick: () => void stop(), style: buttonStyle }, t('lan.stop'))), snapshot && createElement('div', { role: 'status', style: dshSettingsNoteStyle }, t('lan.forwarding', { host: snapshot.listenHost, port: snapshot.actualListenPort ?? snapshot.listenPort, dshPort: snapshot.dshPort }))
|
|
110
|
+
return createElement('div', { 'aria-disabled': controlsDisabled, style: { ...dshFormRootStyle, display: 'flex', flexDirection: 'column', gap: 14, opacity: controlsDisabled ? 0.5 : 1, pointerEvents: controlsDisabled ? 'none' : 'auto' } }, createElement('p', { style: { margin: 0, color: dshThemeColor.labelSecondary, fontSize: 13, lineHeight: 1.5 } }, t('lan.description')), createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, createElement('span', { style: dshSettingsFieldLabelStyle }, t('lan.listenHost')), createElement('select', { value: listenHost, disabled: controlsDisabled || busy, onChange: (event) => setListenHost(event.currentTarget.value), onBlur: saveMappingOnBlur, style: fieldStyle }, ...listenHosts.map((host) => createElement('option', { key: host, value: host }, host === '0.0.0.0' ? t('lan.allInterfaces') : host)))), createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, createElement('span', { style: dshSettingsFieldLabelStyle }, t('lan.listenPort')), createElement('input', { type: 'number', min: 0, max: 65535, placeholder: t('lan.listenPort'), value: listenPort, disabled: controlsDisabled || busy, onChange: (event) => setListenPort(event.currentTarget.value), onBlur: saveMappingOnBlur, style: fieldStyle })), createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, createElement('span', { style: dshSettingsFieldLabelStyle }, t('lan.dshPort')), createElement('div', { style: dshSettingsRowStyle }, createElement('input', { type: 'number', min: 1, max: 65535, placeholder: t('lan.dshPort'), value: dshPort, disabled: controlsDisabled || busy, onChange: (event) => setDshPort(event.currentTarget.value), onBlur: saveMappingOnBlur, style: { ...fieldStyle, flex: 1, minWidth: 0 } }), createElement('button', { type: 'button', disabled: controlsDisabled || busy, onClick: () => void detect(), style: buttonStyle }, t('lan.detect')))), detectedDshPorts.length > 1 && createElement('div', { style: dshSettingsNoteStyle }, t('lan.detectMultiple', { ports: detectedDshPorts.join('、') })), createElement('label', { style: { display: 'flex', alignItems: 'center', gap: 8, cursor: controlsDisabled || busy ? 'not-allowed' : 'pointer', color: dshThemeColor.labelSecondary, fontSize: 13 } }, createElement('input', { type: 'checkbox', checked: autoStart, disabled: controlsDisabled || busy, onChange: () => void toggleAutoStart(), style: { accentColor: dshThemeColor.accent } }), createElement('span', undefined, t('lan.autoStart'))), createElement('div', { style: dshSettingsRowStyle }, createElement('button', { type: 'button', disabled: controlsDisabled || busy || !listenPort, onClick: () => void start(), style: { ...dshSettingsPrimaryButtonStyle, flex: 1 } }, busy ? t('lan.processing') : snapshot ? t('lan.update') : t('lan.start')), snapshot && createElement('button', { type: 'button', disabled: controlsDisabled || busy, onClick: () => void stop(), style: buttonStyle }, t('lan.stop'))), snapshot && createElement('div', { role: 'status', style: dshSettingsNoteStyle }, t('lan.forwarding', { host: snapshot.listenHost, port: snapshot.actualListenPort ?? snapshot.listenPort, dshPort: snapshot.dshPort })));
|
|
113
111
|
}
|
|
114
112
|
function readMapping(listenHost, listenPort, dshPort, autoStart) {
|
|
115
113
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactElement } from 'react';
|
|
2
2
|
import type { FeaturePanelProps } from './types.js';
|
|
3
3
|
/** 独立的登录保护设置卡片,凭据实际由 Host 保存和校验。 */
|
|
4
|
-
export declare function LoginProtectionPanel({ services, enabled, snapshot }: FeaturePanelProps): ReactElement;
|
|
4
|
+
export declare function LoginProtectionPanel({ services, enabled, snapshot, notify }: FeaturePanelProps): ReactElement;
|
|
5
5
|
//# sourceMappingURL=login-protection-panel.d.ts.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createElement, useEffect, useState } from 'react';
|
|
2
2
|
import { CODINGNS_RPC_CHANNEL } from '../../shared/contracts/transport.js';
|
|
3
|
-
import { dshFormRootStyle, dshSettingsButtonStyle, dshSettingsFieldLabelStyle, dshSettingsFieldStyle,
|
|
3
|
+
import { dshFormRootStyle, dshSettingsButtonStyle, dshSettingsFieldLabelStyle, dshSettingsFieldStyle, dshSettingsPrimaryButtonStyle, dshThemeColor, } from '../theme.js';
|
|
4
4
|
import { writeLoginProtectionSession } from './login-protection-session.js';
|
|
5
5
|
const DEFAULT_SCOPES = { lan: true, relay: true };
|
|
6
6
|
/** 独立的登录保护设置卡片,凭据实际由 Host 保存和校验。 */
|
|
7
|
-
export function LoginProtectionPanel({ services, enabled, snapshot }) {
|
|
7
|
+
export function LoginProtectionPanel({ services, enabled, snapshot, notify }) {
|
|
8
8
|
const { rpc } = services;
|
|
9
9
|
const controlsDisabled = !enabled || snapshot.status === 'loading' || !snapshot.writable;
|
|
10
10
|
const [saved, setSaved] = useState({ enabled: false, username: '', passwordConfigured: false, timeoutSeconds: 1800, scopes: DEFAULT_SCOPES });
|
|
@@ -14,7 +14,6 @@ export function LoginProtectionPanel({ services, enabled, snapshot }) {
|
|
|
14
14
|
const [timeoutSeconds, setTimeoutSeconds] = useState('1800');
|
|
15
15
|
const [scopes, setScopes] = useState(DEFAULT_SCOPES);
|
|
16
16
|
const [busy, setBusy] = useState(false);
|
|
17
|
-
const [message, setMessage] = useState('');
|
|
18
17
|
useEffect(() => {
|
|
19
18
|
if (!enabled)
|
|
20
19
|
return;
|
|
@@ -26,11 +25,10 @@ export function LoginProtectionPanel({ services, enabled, snapshot }) {
|
|
|
26
25
|
setTimeoutSeconds(String(value.timeoutSeconds));
|
|
27
26
|
setScopes(value.scopes ?? DEFAULT_SCOPES);
|
|
28
27
|
})
|
|
29
|
-
.catch((error) =>
|
|
28
|
+
.catch((error) => notify({ kind: 'error', message: error instanceof Error ? error.message : String(error) }));
|
|
30
29
|
}, [enabled, rpc]);
|
|
31
30
|
const save = async () => {
|
|
32
31
|
setBusy(true);
|
|
33
|
-
setMessage('');
|
|
34
32
|
try {
|
|
35
33
|
const parsedTimeout = Number(timeoutSeconds);
|
|
36
34
|
if (!Number.isInteger(parsedTimeout) || parsedTimeout < 60 || parsedTimeout > 604800)
|
|
@@ -53,10 +51,10 @@ export function LoginProtectionPanel({ services, enabled, snapshot }) {
|
|
|
53
51
|
writeLoginProtectionSession(undefined);
|
|
54
52
|
}
|
|
55
53
|
setPassword('');
|
|
56
|
-
|
|
54
|
+
notify({ kind: 'success', message: value.enabled && value.scopes.relay && password === '' ? '设置已保存;中继访问需要重新输入密码以建立当前标签页会话' : '登录保护设置已保存' });
|
|
57
55
|
}
|
|
58
56
|
catch (error) {
|
|
59
|
-
|
|
57
|
+
notify({ kind: 'error', message: error instanceof Error ? error.message : String(error) });
|
|
60
58
|
}
|
|
61
59
|
finally {
|
|
62
60
|
setBusy(false);
|
|
@@ -70,8 +68,8 @@ export function LoginProtectionPanel({ services, enabled, snapshot }) {
|
|
|
70
68
|
'aria-disabled': controlsDisabled,
|
|
71
69
|
}, createElement('p', { style: { margin: 0, color: dshThemeColor.labelSecondary, fontSize: 13, lineHeight: 1.5 } }, '为局域网和中继访问统一增加本地账号验证。本机 127.0.0.1 / ::1 永远不受此保护。密码只在 Host 侧验证,不会进入浏览器设置或 URL。'), createElement('label', { style: { display: 'flex', alignItems: 'center', gap: 8, color: dshThemeColor.labelSecondary, fontSize: 13 } }, createElement('input', { type: 'checkbox', checked: active, disabled: controlsDisabled || busy, onChange: (event) => setActive(event.currentTarget.checked), style: { accentColor: dshThemeColor.accent } }), createElement('span', undefined, '启用登录保护')), createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, createElement('span', { style: dshSettingsFieldLabelStyle }, '本地用户名'), createElement('input', { value: username, disabled: controlsDisabled || busy || !active, autoComplete: 'username', onChange: (event) => setUsername(event.currentTarget.value), style: field })), createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, createElement('span', { style: dshSettingsFieldLabelStyle }, saved.passwordConfigured ? '新密码(留空保持不变)' : '密码(至少 8 位)'), createElement('input', { type: 'password', value: password, disabled: controlsDisabled || busy || !active, autoComplete: 'new-password', onChange: (event) => setPassword(event.currentTarget.value), style: field })), createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, createElement('span', { style: dshSettingsFieldLabelStyle }, '会话超时(秒)'), createElement('input', { type: 'number', min: 60, max: 604800, value: timeoutSeconds, disabled: controlsDisabled || busy || !active, onChange: (event) => setTimeoutSeconds(event.currentTarget.value), style: field })), createElement('fieldset', { disabled: controlsDisabled || busy || !active, style: { display: 'flex', flexDirection: 'column', gap: 8, margin: 0, padding: 12, border: `1px solid ${dshThemeColor.border}`, borderRadius: 6 } }, createElement('legend', { style: dshSettingsFieldLabelStyle }, '应用范围'), ...[
|
|
72
70
|
['lan', '局域网访问', '局域网网卡入口'],
|
|
73
|
-
['relay', '中继访问', '
|
|
74
|
-
].map(([key, label, hint]) => createElement('label', { key, style: { display: 'flex', alignItems: 'center', gap: 8, color: dshThemeColor.labelSecondary, fontSize: 13 } }, createElement('input', { type: 'checkbox', checked: scopes[key], onChange: () => toggleScope(key), style: { accentColor: dshThemeColor.accent } }), createElement('span', undefined, `${label}(${hint})`)))), createElement('button', { type: 'button', disabled: controlsDisabled || busy, onClick: () => void save(), style: dshSettingsPrimaryButtonStyle }, busy ? '保存中…' : '保存登录保护')
|
|
71
|
+
['relay', '中继访问', 'Codingns4DSH 中继'],
|
|
72
|
+
].map(([key, label, hint]) => createElement('label', { key, style: { display: 'flex', alignItems: 'center', gap: 8, color: dshThemeColor.labelSecondary, fontSize: 13 } }, createElement('input', { type: 'checkbox', checked: scopes[key], onChange: () => toggleScope(key), style: { accentColor: dshThemeColor.accent } }), createElement('span', undefined, `${label}(${hint})`)))), createElement('button', { type: 'button', disabled: controlsDisabled || busy, onClick: () => void save(), style: dshSettingsPrimaryButtonStyle }, busy ? '保存中…' : '保存登录保护'));
|
|
75
73
|
}
|
|
76
74
|
async function callRpc(rpc, endpoint, payload) {
|
|
77
75
|
let response;
|
|
@@ -5,5 +5,5 @@ import type { FeaturePanelProps } from './types.js';
|
|
|
5
5
|
*
|
|
6
6
|
* 密码只在单次 RPC 中经过 Host,表单不保存它;refresh token 只存在于 Host。
|
|
7
7
|
*/
|
|
8
|
-
export declare function ReverseProxyPanel({ services, enabled, snapshot }: FeaturePanelProps): ReactElement;
|
|
8
|
+
export declare function ReverseProxyPanel({ services, enabled, snapshot, notify }: FeaturePanelProps): ReactElement;
|
|
9
9
|
//# sourceMappingURL=reverse-proxy-panel.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createElement, useEffect, useState } from 'react';
|
|
2
|
-
import { CODINGNS_CONTROL_BASE_URL_FIELD, CODINGNS_CONTROL_BASE_URLS_FIELD, DEFAULT_CODINGNS_CONTROL_BASE_URLS, } from '../../shared/contracts/config.js';
|
|
2
|
+
import { CODINGNS_CONTROL_STATION_URL, CODINGNS_CONTROL_BASE_URL_FIELD, CODINGNS_CONTROL_BASE_URLS_FIELD, CODINGNS_H5_LOGIN_URL, DEFAULT_CODINGNS_CONTROL_BASE_URLS, } from '../../shared/contracts/config.js';
|
|
3
3
|
import { CODINGNS_RPC_CHANNEL } from '../../shared/contracts/transport.js';
|
|
4
4
|
import { dshFormRootStyle, dshPopupSurfaceStyle, dshSettingsButtonStyle, dshSettingsFieldLabelStyle, dshSettingsFieldStyle, dshSettingsNoteStyle, dshSettingsPrimaryButtonStyle, dshSettingsRowStyle, dshThemeColor, } from '../theme.js';
|
|
5
5
|
import { useCodingNsTranslator } from '../locale.js';
|
|
@@ -8,7 +8,7 @@ import { useCodingNsTranslator } from '../locale.js';
|
|
|
8
8
|
*
|
|
9
9
|
* 密码只在单次 RPC 中经过 Host,表单不保存它;refresh token 只存在于 Host。
|
|
10
10
|
*/
|
|
11
|
-
export function ReverseProxyPanel({ services, enabled, snapshot }) {
|
|
11
|
+
export function ReverseProxyPanel({ services, enabled, snapshot, notify }) {
|
|
12
12
|
const { settings, rpc } = services;
|
|
13
13
|
const t = useCodingNsTranslator(services.locale);
|
|
14
14
|
const disabled = !enabled;
|
|
@@ -24,7 +24,7 @@ export function ReverseProxyPanel({ services, enabled, snapshot }) {
|
|
|
24
24
|
const [devices, setDevices] = useState(null);
|
|
25
25
|
const [selectedDeviceId, setSelectedDeviceId] = useState('');
|
|
26
26
|
const [busy, setBusy] = useState(false);
|
|
27
|
-
const [
|
|
27
|
+
const [h5UrlCopied, setH5UrlCopied] = useState(false);
|
|
28
28
|
useEffect(() => {
|
|
29
29
|
const saved = snapshot.value?.controlBaseUrl;
|
|
30
30
|
const resolved = resolveControlBaseUrl(saved);
|
|
@@ -45,17 +45,16 @@ export function ReverseProxyPanel({ services, enabled, snapshot }) {
|
|
|
45
45
|
if (!enabled || auth.status !== 'authenticated')
|
|
46
46
|
return;
|
|
47
47
|
void loadDevicesInternal().catch((error) => {
|
|
48
|
-
|
|
48
|
+
notify({ kind: 'error', message: error instanceof Error ? error.message : String(error) });
|
|
49
49
|
});
|
|
50
50
|
}, [auth.status, enabled, rpc]);
|
|
51
51
|
const run = async (operation) => {
|
|
52
52
|
setBusy(true);
|
|
53
|
-
setMessage('');
|
|
54
53
|
try {
|
|
55
54
|
await operation();
|
|
56
55
|
}
|
|
57
56
|
catch (error) {
|
|
58
|
-
|
|
57
|
+
notify({ kind: 'error', message: error instanceof Error ? error.message : String(error) });
|
|
59
58
|
}
|
|
60
59
|
finally {
|
|
61
60
|
setBusy(false);
|
|
@@ -73,15 +72,34 @@ export function ReverseProxyPanel({ services, enabled, snapshot }) {
|
|
|
73
72
|
});
|
|
74
73
|
setPassword('');
|
|
75
74
|
setAuth(next);
|
|
76
|
-
|
|
75
|
+
setH5UrlCopied(false);
|
|
76
|
+
notify({ kind: 'success', message: t('relay.loginSuccess') });
|
|
77
77
|
});
|
|
78
78
|
const logout = () => run(async () => {
|
|
79
79
|
await callCodingNsRpc(rpc, 'auth/logout', {});
|
|
80
80
|
setAuth(loggedOutSnapshot());
|
|
81
81
|
setDevices(null);
|
|
82
82
|
setSelectedDeviceId('');
|
|
83
|
-
|
|
83
|
+
setH5UrlCopied(false);
|
|
84
|
+
notify({ kind: 'success', message: t('relay.loggedOut') });
|
|
84
85
|
});
|
|
86
|
+
const copyH5LoginUrl = async () => {
|
|
87
|
+
try {
|
|
88
|
+
await copyText(CODINGNS_H5_LOGIN_URL);
|
|
89
|
+
setH5UrlCopied(true);
|
|
90
|
+
notify({ kind: 'success', message: t('relay.copySuccess') });
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
setH5UrlCopied(false);
|
|
94
|
+
notify({ kind: 'error', message: error instanceof Error ? error.message : t('relay.copyFailed') });
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const handleH5AddressKeyDown = (event) => {
|
|
98
|
+
if (event.key !== 'Enter' && event.key !== ' ')
|
|
99
|
+
return;
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
void copyH5LoginUrl();
|
|
102
|
+
};
|
|
85
103
|
const loadDevices = () => run(async () => {
|
|
86
104
|
await loadDevicesInternal();
|
|
87
105
|
});
|
|
@@ -103,7 +121,7 @@ export function ReverseProxyPanel({ services, enabled, snapshot }) {
|
|
|
103
121
|
setControlBaseUrl(addedUrl);
|
|
104
122
|
setNewControlBaseUrl('');
|
|
105
123
|
setAddAddressOpen(false);
|
|
106
|
-
|
|
124
|
+
notify({ kind: 'success', message: t('relay.add') });
|
|
107
125
|
}
|
|
108
126
|
catch (error) {
|
|
109
127
|
setAddressError(error instanceof Error ? error.message : String(error));
|
|
@@ -115,13 +133,60 @@ export function ReverseProxyPanel({ services, enabled, snapshot }) {
|
|
|
115
133
|
const chooseControlBaseUrl = (value) => {
|
|
116
134
|
setControlBaseUrl(value);
|
|
117
135
|
void settings.set(CODINGNS_CONTROL_BASE_URL_FIELD, value).catch((error) => {
|
|
118
|
-
|
|
136
|
+
notify({ kind: 'error', message: error instanceof Error ? error.message : String(error) });
|
|
119
137
|
});
|
|
120
138
|
};
|
|
121
139
|
const fieldStyle = dshSettingsFieldStyle;
|
|
122
140
|
const buttonStyle = dshSettingsButtonStyle;
|
|
123
141
|
const authenticated = auth.status === 'authenticated';
|
|
124
|
-
|
|
142
|
+
const selectedDevice = devices?.devices.find((device) => device.dshDeviceId === selectedDeviceId);
|
|
143
|
+
const deviceStatus = selectedDevice === undefined
|
|
144
|
+
? { label: t('relay.unknown'), color: dshThemeColor.labelTertiary }
|
|
145
|
+
: selectedDevice.online && selectedDevice.status === 'active'
|
|
146
|
+
? { label: t('relay.online'), color: dshThemeColor.success }
|
|
147
|
+
: { label: t('relay.offline'), color: dshThemeColor.error };
|
|
148
|
+
return createElement('div', { 'aria-disabled': controlsDisabled, style: { ...dshFormRootStyle, display: 'flex', flexDirection: 'column', gap: 16, opacity: controlsDisabled ? 0.5 : 1, pointerEvents: controlsDisabled ? 'none' : 'auto' } }, createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, createElement('span', { style: dshSettingsFieldLabelStyle }, t('relay.controlApi')), createElement('div', { style: dshSettingsRowStyle }, createElement('select', { value: controlBaseUrl, disabled: controlsDisabled || busy, onChange: (event) => chooseControlBaseUrl(event.currentTarget.value), style: { ...fieldStyle, flex: 1, minWidth: 0 } }, ...controlBaseUrls.map((url) => createElement('option', { key: url, value: url }, url))), createElement('button', { type: 'button', 'aria-haspopup': 'dialog', disabled: controlsDisabled || busy, onClick: () => { setAddressError(''); setAddAddressOpen(true); }, style: { ...buttonStyle, flex: '0 0 auto' } }, t('relay.add')))), addAddressOpen && createElement('div', { role: 'dialog', 'aria-modal': true, 'aria-labelledby': 'codingns-add-address-title', style: { position: 'fixed', inset: 0, zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24, background: dshThemeColor.overlay } }, createElement('div', { style: { ...dshPopupSurfaceStyle, width: 'min(100%, 480px)', boxSizing: 'border-box', padding: 24, borderRadius: 8 } }, createElement('h3', { id: 'codingns-add-address-title', style: { margin: 0, fontSize: 18 } }, t('relay.addServer')), createElement('p', { style: { margin: '8px 0 16px', opacity: 0.7 } }, t('relay.addServerHint')), createElement('input', { type: 'url', autoFocus: true, value: newControlBaseUrl, placeholder: 'https://example.com:1443', disabled: disabled || busy, onChange: (event) => setNewControlBaseUrl(event.currentTarget.value), style: fieldStyle }), addressError && createElement('div', { role: 'alert', style: { marginTop: 8, color: dshThemeColor.error } }, addressError), createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 20 } }, createElement('button', { type: 'button', disabled: busy, onClick: () => { setAddAddressOpen(false); setAddressError(''); }, style: buttonStyle }, t('relay.cancel')), createElement('button', { type: 'button', disabled: busy || !newControlBaseUrl.trim(), onClick: () => void addControlBaseUrl(), style: buttonStyle }, busy ? t('relay.adding') : t('relay.add'))))), !authenticated && createElement('form', { onSubmit: (event) => { event.preventDefault(); void login(); }, style: { display: 'flex', flexDirection: 'column', gap: 12 } }, createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, createElement('span', { style: dshSettingsFieldLabelStyle }, t('relay.email')), createElement('input', { type: 'email', autoComplete: 'username', value: email, disabled: controlsDisabled || busy, onChange: (event) => setEmail(event.currentTarget.value), style: fieldStyle })), createElement('label', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, createElement('span', { style: dshSettingsFieldLabelStyle }, t('relay.password')), createElement('input', { type: 'password', autoComplete: 'current-password', value: password, disabled: controlsDisabled || busy, onChange: (event) => setPassword(event.currentTarget.value), style: fieldStyle })), createElement('button', { type: 'submit', disabled: controlsDisabled || busy || !controlBaseUrl || !email || !password, style: dshSettingsPrimaryButtonStyle }, busy ? t('relay.loggingIn') : t('relay.login')), createElement('p', { style: { margin: 0, color: dshThemeColor.labelSecondary, fontSize: 13, lineHeight: 1.5 } }, t('relay.noAccountPrefix'), createElement('a', { href: CODINGNS_CONTROL_STATION_URL, target: '_blank', rel: 'noreferrer', style: { color: dshThemeColor.accent } }, t('relay.register')), t('relay.noAccountSuffix'))), authenticated && createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } }, createElement('div', { style: dshSettingsNoteStyle }, createElement('strong', undefined, auth.account?.email ?? t('relay.loggedIn')), createElement('div', { style: { marginTop: 6, opacity: 0.7 } }, t('relay.device', { value: selectedDevice?.displayName ?? t('relay.unrecognized') })), createElement('div', { style: { marginTop: 4, opacity: 0.7 } }, t('relay.dshVersion', { value: selectedDevice?.dshVersion ?? t('relay.unknown') })), createElement('div', { style: { marginTop: 4, opacity: 0.7 } }, t('relay.computerName', { value: selectedDevice?.computerName ?? t('relay.unknown') })), createElement('div', { style: { marginTop: 4, opacity: 0.7 } }, t('relay.host', { value: selectedDeviceId || t('relay.unbound') }))), createElement('div', { style: dshSettingsNoteStyle }, createElement('strong', undefined, t('relay.h5LoginAddress')), createElement('div', {
|
|
149
|
+
role: 'button',
|
|
150
|
+
tabIndex: controlsDisabled || busy ? -1 : 0,
|
|
151
|
+
'aria-disabled': controlsDisabled || busy,
|
|
152
|
+
'aria-label': t('relay.copyAddressHint'),
|
|
153
|
+
title: t('relay.copyAddressHint'),
|
|
154
|
+
onClick: () => { if (!controlsDisabled && !busy)
|
|
155
|
+
void copyH5LoginUrl(); },
|
|
156
|
+
onKeyDown: (event) => { if (!controlsDisabled && !busy)
|
|
157
|
+
handleH5AddressKeyDown(event); },
|
|
158
|
+
style: {
|
|
159
|
+
display: 'flex',
|
|
160
|
+
alignItems: 'center',
|
|
161
|
+
gap: 12,
|
|
162
|
+
marginTop: 8,
|
|
163
|
+
minWidth: 0,
|
|
164
|
+
padding: '10px 12px',
|
|
165
|
+
border: `1px solid ${h5UrlCopied ? dshThemeColor.success : dshThemeColor.border}`,
|
|
166
|
+
borderRadius: 8,
|
|
167
|
+
color: dshThemeColor.accent,
|
|
168
|
+
background: dshThemeColor.pageBackground,
|
|
169
|
+
cursor: controlsDisabled || busy ? 'default' : 'copy',
|
|
170
|
+
transition: 'border-color 160ms ease, background 160ms ease',
|
|
171
|
+
},
|
|
172
|
+
}, createElement('span', { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', fontSize: 13, fontWeight: 600 } }, CODINGNS_H5_LOGIN_URL))), createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } }, createElement('button', { type: 'button', disabled: controlsDisabled || busy, onClick: () => void loadDevices(), style: buttonStyle }, t('relay.refreshDevices')), createElement('button', { type: 'button', disabled: controlsDisabled || busy, onClick: () => void logout(), style: buttonStyle }, t('relay.logout'))), devices && createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 8 } }, createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, minWidth: 0 } }, createElement('strong', { style: dshSettingsFieldLabelStyle }, t('relay.dshDevices')), createElement('span', {
|
|
173
|
+
role: 'status',
|
|
174
|
+
'aria-label': t('relay.deviceStatus', { value: deviceStatus.label }),
|
|
175
|
+
style: {
|
|
176
|
+
display: 'inline-flex',
|
|
177
|
+
alignItems: 'center',
|
|
178
|
+
gap: 6,
|
|
179
|
+
flex: '0 0 auto',
|
|
180
|
+
padding: '4px 9px',
|
|
181
|
+
border: `1px solid ${deviceStatus.color}`,
|
|
182
|
+
borderRadius: 999,
|
|
183
|
+
color: deviceStatus.color,
|
|
184
|
+
background: dshThemeColor.surfaceSubtle,
|
|
185
|
+
fontSize: 12,
|
|
186
|
+
lineHeight: 1.2,
|
|
187
|
+
fontWeight: 600,
|
|
188
|
+
},
|
|
189
|
+
}, createElement('span', { 'aria-hidden': true, style: { width: 7, height: 7, flex: '0 0 auto', borderRadius: '50%', background: deviceStatus.color } }), deviceStatus.label)), createElement('select', { value: selectedDeviceId, disabled: controlsDisabled || busy, onChange: (event) => setSelectedDeviceId(event.currentTarget.value), style: fieldStyle }, createElement('option', { value: '' }, t('relay.selectDevice')), ...devices.devices.map((device) => createElement('option', { key: device.dshDeviceId, value: device.dshDeviceId, disabled: !device.online || device.status !== 'active' }, `${device.displayName} · ${device.computerName ?? t('relay.unknown')} · ${device.dshVersion ?? t('relay.unknown')} · ${device.online ? t('relay.online') : t('relay.offline')}`))), createElement('div', { style: { fontSize: 13, opacity: 0.75 } }, t('relay.devicesSummary', { current: selectedDeviceId || t('relay.unknown'), count: devices.devices.length })))));
|
|
125
190
|
}
|
|
126
191
|
async function callCodingNsRpc(rpc, endpoint, payload) {
|
|
127
192
|
let response;
|
|
@@ -156,4 +221,27 @@ function normalizeControlBaseUrl(value) {
|
|
|
156
221
|
throw new TypeError('Control API 地址必须使用 HTTP(S)');
|
|
157
222
|
return parsed.toString().replace(/\/+$/u, '');
|
|
158
223
|
}
|
|
224
|
+
/** 复制 H5 地址;非安全上下文下回退到传统 DOM 复制接口。 */
|
|
225
|
+
async function copyText(value) {
|
|
226
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
|
227
|
+
await navigator.clipboard.writeText(value);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (typeof document === 'undefined')
|
|
231
|
+
throw new Error('当前环境不支持复制');
|
|
232
|
+
const input = document.createElement('textarea');
|
|
233
|
+
input.value = value;
|
|
234
|
+
input.setAttribute('readonly', '');
|
|
235
|
+
input.style.position = 'fixed';
|
|
236
|
+
input.style.opacity = '0';
|
|
237
|
+
document.body.appendChild(input);
|
|
238
|
+
input.select();
|
|
239
|
+
try {
|
|
240
|
+
if (!document.execCommand('copy'))
|
|
241
|
+
throw new Error('当前环境不支持复制');
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
input.remove();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
159
247
|
//# sourceMappingURL=reverse-proxy-panel.js.map
|
|
@@ -3,7 +3,7 @@ import type { CodingNsRpcClient } from './types.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* 中转访问服务模块。
|
|
5
5
|
*
|
|
6
|
-
* 它把 DSH 页面接入
|
|
6
|
+
* 它把 DSH 页面接入 Codingns4DSH 独立设备隧道。登录和设备列表由 Host RPC 提供,
|
|
7
7
|
* 连接状态和设备选择由设置面板承载;隧道本身在模块启用时建立。
|
|
8
8
|
*/
|
|
9
9
|
export declare const reverseProxyFeature: CodingNsClientFeatureModule;
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { ReverseProxyPanel } from './reverse-proxy-panel.js';
|
|
2
2
|
import { startDshH5Bootstrap } from '../dsh-h5-bootstrap.js';
|
|
3
|
+
import { callCodingNsRpc } from '../settings-bridge.js';
|
|
3
4
|
import { LOGIN_PROTECTION_SESSION_EVENT, readLoginProtectionSession } from './login-protection-session.js';
|
|
4
|
-
import { CODINGNS_RPC_CHANNEL } from '../../shared/contracts/transport.js';
|
|
5
5
|
function isRemoteWebContext() {
|
|
6
6
|
return globalThis.__CODINGNS4DSH_REMOTE_WEB_CONTEXT__ === true;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* 中转访问服务模块。
|
|
10
10
|
*
|
|
11
|
-
* 它把 DSH 页面接入
|
|
11
|
+
* 它把 DSH 页面接入 Codingns4DSH 独立设备隧道。登录和设备列表由 Host RPC 提供,
|
|
12
12
|
* 连接状态和设备选择由设置面板承载;隧道本身在模块启用时建立。
|
|
13
13
|
*/
|
|
14
14
|
export const reverseProxyFeature = {
|
|
15
15
|
descriptor: {
|
|
16
16
|
name: 'reverseProxy',
|
|
17
|
-
version: '0.1.
|
|
17
|
+
version: '0.1.1',
|
|
18
18
|
enabledByDefault: false,
|
|
19
19
|
dependencies: [],
|
|
20
20
|
runtime: 'client',
|
|
21
21
|
ui: {
|
|
22
22
|
label: '中转访问服务',
|
|
23
|
-
description: '通过
|
|
23
|
+
description: '通过 Codingns4DSH 独立设备隧道访问 Host。',
|
|
24
24
|
labelKey: 'feature.reverseProxy.label',
|
|
25
25
|
descriptionKey: 'feature.reverseProxy.description',
|
|
26
26
|
order: 20,
|
|
@@ -51,6 +51,11 @@ export const reverseProxyFeature = {
|
|
|
51
51
|
if (stopped || waitingForLogin)
|
|
52
52
|
return;
|
|
53
53
|
try {
|
|
54
|
+
const auth = await readAuthSnapshot(context.services.rpc);
|
|
55
|
+
if (auth.status !== 'authenticated') {
|
|
56
|
+
scheduleRetry();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
54
59
|
const protection = await readLoginProtectionSettings(context.services.rpc);
|
|
55
60
|
const loginProtectionToken = readLoginProtectionSession();
|
|
56
61
|
if (protection.enabled && protection.scopes.relay && loginProtectionToken === undefined) {
|
|
@@ -68,9 +73,14 @@ export const reverseProxyFeature = {
|
|
|
68
73
|
if (stopped || abort.signal.aborted)
|
|
69
74
|
return;
|
|
70
75
|
console.error('codingns4dsh: 中继连接建立失败,将在稍后重试', error);
|
|
71
|
-
|
|
76
|
+
scheduleRetry();
|
|
72
77
|
}
|
|
73
78
|
};
|
|
79
|
+
const scheduleRetry = () => {
|
|
80
|
+
if (retryTimer !== undefined || stopped || abort.signal.aborted)
|
|
81
|
+
return;
|
|
82
|
+
retryTimer = setTimeout(() => { retryTimer = undefined; void attempt(); }, 5_000);
|
|
83
|
+
};
|
|
74
84
|
void attempt();
|
|
75
85
|
return async () => {
|
|
76
86
|
stopped = true;
|
|
@@ -95,18 +105,9 @@ export async function startBrowserRelayConnection(rpc, signal, loginProtectionTo
|
|
|
95
105
|
};
|
|
96
106
|
}
|
|
97
107
|
async function readLoginProtectionSettings(rpc) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
104
|
-
if (!/HTTP (?:404|405)\b/u.test(message))
|
|
105
|
-
throw error;
|
|
106
|
-
response = await rpc.call('/api', 'codingns/lanAccessDsh/login/get', {});
|
|
107
|
-
}
|
|
108
|
-
if (!response.ok)
|
|
109
|
-
throw new Error(response.error.message);
|
|
110
|
-
return response.value;
|
|
108
|
+
return callCodingNsRpc(rpc, 'lanAccessDsh/login/get', {});
|
|
109
|
+
}
|
|
110
|
+
async function readAuthSnapshot(rpc) {
|
|
111
|
+
return callCodingNsRpc(rpc, 'auth/snapshot', {});
|
|
111
112
|
}
|
|
112
113
|
//# sourceMappingURL=reverse-proxy.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactElement } from 'react';
|
|
2
2
|
import type { FeaturePanelProps } from './types.js';
|
|
3
3
|
/** 终端默认行为与外观设置;终端 UI 由独立 Sidebar 模块负责。 */
|
|
4
|
-
export declare function TerminalEnhancementPanel({ services, enabled, snapshot }: FeaturePanelProps): ReactElement;
|
|
4
|
+
export declare function TerminalEnhancementPanel({ services, enabled, snapshot, notify }: FeaturePanelProps): ReactElement;
|
|
5
5
|
//# sourceMappingURL=terminal-enhancement-panel.d.ts.map
|