dsh-zen-remote 2.2.9 → 2.3.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.md +2 -2
- package/README.zh-CN.md +2 -2
- package/docs/interface.md +1 -1
- package/docs/remote-access.md +20 -0
- package/lan-gate.mjs +11 -1
- package/lib/adapters/dsh-better-sidebar/index.js +6 -2
- package/lib/adapters/dsh-better-sidebar/index.js.map +1 -1
- package/lib/adapters/dsh-better-sidebar/methods.js +3 -0
- package/lib/adapters/dsh-better-sidebar/methods.js.map +1 -1
- package/lib/admin-routes.js +33 -2
- package/lib/admin-routes.js.map +1 -1
- package/lib/client.js +3289 -6
- package/lib/config.js +70 -0
- package/lib/config.js.map +1 -1
- package/lib/index.js +232 -2
- package/lib/index.js.map +1 -1
- package/lib/intercept.js +43 -3
- package/lib/intercept.js.map +1 -1
- package/lib/lan-gate-server.cjs +29 -3
- package/lib/merge-streams.js +42 -12
- package/lib/merge-streams.js.map +1 -1
- package/lib/types/adapters/dsh-better-sidebar/index.d.ts +1 -1
- package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -1
- package/lib/types/adapters/dsh-better-sidebar/methods.d.ts.map +1 -1
- package/lib/types/admin-routes.d.ts +13 -0
- package/lib/types/admin-routes.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +204 -1
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/wizard/GatewayWizard.d.ts +29 -0
- package/lib/types/client/wizard/GatewayWizard.d.ts.map +1 -0
- package/lib/types/client/wizard/WizardCard.d.ts +30 -0
- package/lib/types/client/wizard/WizardCard.d.ts.map +1 -0
- package/lib/types/client/wizard/cards/MigrateCard.d.ts +15 -0
- package/lib/types/client/wizard/cards/MigrateCard.d.ts.map +1 -0
- package/lib/types/client/wizard/cards/PortForwardCard.d.ts +15 -0
- package/lib/types/client/wizard/cards/PortForwardCard.d.ts.map +1 -0
- package/lib/types/client/wizard/cards/ProxyCard.d.ts +19 -0
- package/lib/types/client/wizard/cards/ProxyCard.d.ts.map +1 -0
- package/lib/types/client/wizard/cards/TokenCard.d.ts +20 -0
- package/lib/types/client/wizard/cards/TokenCard.d.ts.map +1 -0
- package/lib/types/client/wizard/register-wizard.d.ts +51 -0
- package/lib/types/client/wizard/register-wizard.d.ts.map +1 -0
- package/lib/types/client/wizard/wizard-css.d.ts +10 -0
- package/lib/types/client/wizard/wizard-css.d.ts.map +1 -0
- package/lib/types/client-data/qr.d.ts +20 -0
- package/lib/types/client-data/qr.d.ts.map +1 -0
- package/lib/types/client-data/settings-form.d.ts +7 -2
- package/lib/types/client-data/settings-form.d.ts.map +1 -1
- package/lib/types/client-data/wizard.d.ts +247 -0
- package/lib/types/client-data/wizard.d.ts.map +1 -0
- package/lib/types/config.d.ts +27 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/intercept.d.ts.map +1 -1
- package/lib/types/merge-streams.d.ts.map +1 -1
- package/lib/types/wizard/binary-io.d.ts +111 -0
- package/lib/types/wizard/binary-io.d.ts.map +1 -0
- package/lib/types/wizard/binary.d.ts +118 -0
- package/lib/types/wizard/binary.d.ts.map +1 -0
- package/lib/types/wizard/caddy-process.d.ts +101 -0
- package/lib/types/wizard/caddy-process.d.ts.map +1 -0
- package/lib/types/wizard/caddy.d.ts +53 -0
- package/lib/types/wizard/caddy.d.ts.map +1 -0
- package/lib/types/wizard/cloudflare.d.ts +112 -0
- package/lib/types/wizard/cloudflare.d.ts.map +1 -0
- package/lib/types/wizard/cloudflared.d.ts +71 -0
- package/lib/types/wizard/cloudflared.d.ts.map +1 -0
- package/lib/types/wizard/probe.d.ts +66 -0
- package/lib/types/wizard/probe.d.ts.map +1 -0
- package/lib/types/wizard/proxy-snippets.d.ts +60 -0
- package/lib/types/wizard/proxy-snippets.d.ts.map +1 -0
- package/lib/types/wizard/routes.d.ts +152 -0
- package/lib/types/wizard/routes.d.ts.map +1 -0
- package/lib/types/wizard/source-detect.d.ts +53 -0
- package/lib/types/wizard/source-detect.d.ts.map +1 -0
- package/lib/types/wizard/status.d.ts +115 -0
- package/lib/types/wizard/status.d.ts.map +1 -0
- package/lib/types/wizard/token-store.d.ts +27 -0
- package/lib/types/wizard/token-store.d.ts.map +1 -0
- package/lib/types/wizard/tunnel-orchestrator.d.ts +90 -0
- package/lib/types/wizard/tunnel-orchestrator.d.ts.map +1 -0
- package/lib/types/wizard/verify.d.ts +97 -0
- package/lib/types/wizard/verify.d.ts.map +1 -0
- package/lib/wizard/binary-io.js +386 -0
- package/lib/wizard/binary-io.js.map +1 -0
- package/lib/wizard/binary.js +138 -0
- package/lib/wizard/binary.js.map +1 -0
- package/lib/wizard/caddy-process.js +162 -0
- package/lib/wizard/caddy-process.js.map +1 -0
- package/lib/wizard/caddy.js +80 -0
- package/lib/wizard/caddy.js.map +1 -0
- package/lib/wizard/cloudflare.js +334 -0
- package/lib/wizard/cloudflare.js.map +1 -0
- package/lib/wizard/cloudflared.js +158 -0
- package/lib/wizard/cloudflared.js.map +1 -0
- package/lib/wizard/probe.js +118 -0
- package/lib/wizard/probe.js.map +1 -0
- package/lib/wizard/proxy-snippets.js +112 -0
- package/lib/wizard/proxy-snippets.js.map +1 -0
- package/lib/wizard/routes.js +462 -0
- package/lib/wizard/routes.js.map +1 -0
- package/lib/wizard/source-detect.js +77 -0
- package/lib/wizard/source-detect.js.map +1 -0
- package/lib/wizard/status.js +94 -0
- package/lib/wizard/status.js.map +1 -0
- package/lib/wizard/token-store.js +71 -0
- package/lib/wizard/token-store.js.map +1 -0
- package/lib/wizard/tunnel-orchestrator.js +155 -0
- package/lib/wizard/tunnel-orchestrator.js.map +1 -0
- package/lib/wizard/verify.js +171 -0
- package/lib/wizard/verify.js.map +1 -0
- package/package.json +37 -37
- package/scripts/check-settings-form.mjs +5 -4
- package/scripts/check-wizard-ui.mjs +690 -0
- package/src/adapters/dsh-better-sidebar/index.ts +6 -2
- package/src/adapters/dsh-better-sidebar/methods.ts +3 -0
- package/src/admin-routes.ts +44 -2
- package/src/client/index.tsx +9 -0
- package/src/client/locales.ts +410 -2
- package/src/client/wizard/GatewayWizard.tsx +2220 -0
- package/src/client/wizard/WizardCard.tsx +81 -0
- package/src/client/wizard/cards/MigrateCard.tsx +42 -0
- package/src/client/wizard/cards/PortForwardCard.tsx +37 -0
- package/src/client/wizard/cards/ProxyCard.tsx +56 -0
- package/src/client/wizard/cards/TokenCard.tsx +81 -0
- package/src/client/wizard/register-wizard.ts +99 -0
- package/src/client/wizard/wizard-css.ts +948 -0
- package/src/client-data/qr.ts +284 -0
- package/src/client-data/settings-form.ts +12 -3
- package/src/client-data/wizard.ts +432 -0
- package/src/config.ts +87 -0
- package/src/index.ts +227 -2
- package/src/intercept.ts +43 -3
- package/src/merge-streams.ts +41 -12
- package/src/wizard/binary-io.ts +449 -0
- package/src/wizard/binary.ts +177 -0
- package/src/wizard/caddy-process.ts +228 -0
- package/src/wizard/caddy.ts +97 -0
- package/src/wizard/cloudflare.ts +413 -0
- package/src/wizard/cloudflared.ts +196 -0
- package/src/wizard/probe.ts +161 -0
- package/src/wizard/proxy-snippets.ts +128 -0
- package/src/wizard/routes.ts +537 -0
- package/src/wizard/source-detect.ts +87 -0
- package/src/wizard/status.ts +180 -0
- package/src/wizard/token-store.ts +73 -0
- package/src/wizard/tunnel-orchestrator.ts +197 -0
- package/src/wizard/verify.ts +236 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WizardCard (GW-10): the modal popup for steps the user must perform
|
|
3
|
+
* ELSEWHERE — creating the Cloudflare token, configuring their own
|
|
4
|
+
* reverse proxy. Numbered steps in the page locale, copy-to-clipboard for
|
|
5
|
+
* the value or snippet at hand, Esc / backdrop / button to close. Scoped
|
|
6
|
+
* under `[data-zen-remote="wizard-card"]`.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately NO react-dom portal: `react-dom` is not a seed word of the
|
|
9
|
+
* host browser module table (GW-fix-4 — `check-client-externals` gates the
|
|
10
|
+
* allowlist), so the overlay renders IN PLACE as a `position: fixed`
|
|
11
|
+
* layer. That keeps the plugin version-agnostic; the z-index floor of
|
|
12
|
+
* 1000 clears the Plugins page chrome, and no ancestor of the detail page
|
|
13
|
+
* establishes a containing block that could clip it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { useEffect, useRef, useState } from 'react'
|
|
17
|
+
import type { ReactNode } from 'react'
|
|
18
|
+
|
|
19
|
+
export interface WizardCardProps {
|
|
20
|
+
title: string
|
|
21
|
+
/** The card's body — numbered steps and/or a config snippet. */
|
|
22
|
+
children: ReactNode
|
|
23
|
+
/** The copy button's payload; absent means no copy button. */
|
|
24
|
+
copyText?: string | null
|
|
25
|
+
copyLabel: string
|
|
26
|
+
copiedLabel: string
|
|
27
|
+
closeLabel: string
|
|
28
|
+
/** Optional primary action button rendered alongside the close button. */
|
|
29
|
+
primaryAction?: ReactNode
|
|
30
|
+
onClose: () => void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function WizardCard({ title, children, copyText, copyLabel, copiedLabel, closeLabel, primaryAction, onClose }: WizardCardProps): ReactNode {
|
|
34
|
+
const [copied, setCopied] = useState(false)
|
|
35
|
+
const copiedTimer = useRef<ReturnType<typeof setTimeout>>()
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const onKey = (event: KeyboardEvent): void => {
|
|
39
|
+
if (event.key === 'Escape') onClose()
|
|
40
|
+
}
|
|
41
|
+
document.addEventListener('keydown', onKey)
|
|
42
|
+
return () => {
|
|
43
|
+
document.removeEventListener('keydown', onKey)
|
|
44
|
+
if (copiedTimer.current !== undefined) clearTimeout(copiedTimer.current)
|
|
45
|
+
}
|
|
46
|
+
}, [onClose])
|
|
47
|
+
|
|
48
|
+
const copy = async (): Promise<void> => {
|
|
49
|
+
if (copyText === undefined || copyText === null || copyText === '') return
|
|
50
|
+
try {
|
|
51
|
+
await navigator.clipboard.writeText(copyText)
|
|
52
|
+
setCopied(true)
|
|
53
|
+
if (copiedTimer.current !== undefined) clearTimeout(copiedTimer.current)
|
|
54
|
+
copiedTimer.current = setTimeout(() => setCopied(false), 2000)
|
|
55
|
+
} catch {
|
|
56
|
+
// Clipboard denial (permissions, non-secure context): the snippet is
|
|
57
|
+
// selectable text anyway.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div data-zen-remote="wizard-card" onClick={onClose}>
|
|
63
|
+
<div className="zr-wizard-cardPanel" onClick={(event) => event.stopPropagation()}>
|
|
64
|
+
<div className="zr-wizard-cardHeader">
|
|
65
|
+
<p className="zr-wizard-cardTitle">{title}</p>
|
|
66
|
+
<button type="button" className="zr-wizard-cardCloseIcon" aria-label={closeLabel} onClick={onClose}>×</button>
|
|
67
|
+
</div>
|
|
68
|
+
<div className="zr-wizard-cardBody">
|
|
69
|
+
{children}
|
|
70
|
+
</div>
|
|
71
|
+
<div className="zr-wizard-cardActions">
|
|
72
|
+
<button type="button" className="zr-wizard-btn" onClick={onClose}>{closeLabel}</button>
|
|
73
|
+
{copyText !== undefined && copyText !== null && copyText !== '' ? (
|
|
74
|
+
<button type="button" className="zr-wizard-btn" onClick={() => { void copy() }}>{copied ? copiedLabel : copyLabel}</button>
|
|
75
|
+
) : null}
|
|
76
|
+
{primaryAction}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MigrateCard (GW-11): the six-step domain-to-Cloudflare walkthrough for
|
|
3
|
+
* the tunnel path's precondition, with the 「重新检查」 button that closes
|
|
4
|
+
* the card so the user re-runs the form's own check.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ReactNode } from 'react'
|
|
8
|
+
import type { WizardT } from './TokenCard.tsx'
|
|
9
|
+
import { WizardCard } from '../WizardCard.tsx'
|
|
10
|
+
|
|
11
|
+
export interface MigrateCardProps {
|
|
12
|
+
t: WizardT
|
|
13
|
+
/** Closes the card so the caller can re-run its precondition check. */
|
|
14
|
+
onRecheck: () => void
|
|
15
|
+
onClose: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const STEPS = ['migrateStep1', 'migrateStep2', 'migrateStep3', 'migrateStep4', 'migrateStep5', 'migrateStep6'] as const
|
|
19
|
+
|
|
20
|
+
export function MigrateCard({ t, onRecheck, onClose }: MigrateCardProps): ReactNode {
|
|
21
|
+
return (
|
|
22
|
+
<WizardCard
|
|
23
|
+
title={t('wizard.card.migrateTitle')}
|
|
24
|
+
copyText={null}
|
|
25
|
+
copyLabel={t('wizard.card.copy')}
|
|
26
|
+
copiedLabel={t('wizard.card.copied')}
|
|
27
|
+
closeLabel={t('wizard.card.close')}
|
|
28
|
+
primaryAction={(
|
|
29
|
+
<button type="button" className="zr-wizard-btn zr-wizard-btnPrimary" onClick={onRecheck}>
|
|
30
|
+
{t('wizard.card.migrateRecheck')}
|
|
31
|
+
</button>
|
|
32
|
+
)}
|
|
33
|
+
onClose={onClose}
|
|
34
|
+
>
|
|
35
|
+
<div className="zr-wizard-cardSteps">
|
|
36
|
+
{STEPS.map((key, index) => (
|
|
37
|
+
<p key={key} className="zr-wizard-cardStep">{`${index + 1}. ${t(`wizard.card.${key}`)}`}</p>
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
40
|
+
</WizardCard>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PortForwardCard (GW-11): the router port-forwarding steps for path A on
|
|
3
|
+
* a non-443 HTTPS port — the certificate rides DNS-01 (no inbound needed
|
|
4
|
+
* for issuance), but the HTTPS service port itself must be reachable.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ReactNode } from 'react'
|
|
8
|
+
import type { WizardT } from './TokenCard.tsx'
|
|
9
|
+
import { WizardCard } from '../WizardCard.tsx'
|
|
10
|
+
|
|
11
|
+
export interface PortForwardCardProps {
|
|
12
|
+
t: WizardT
|
|
13
|
+
/** The HTTPS port to forward. */
|
|
14
|
+
port: number
|
|
15
|
+
onClose: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const STEPS = ['portForwardStep1', 'portForwardStep2', 'portForwardStep3', 'portForwardStep4'] as const
|
|
19
|
+
|
|
20
|
+
export function PortForwardCard({ t, port, onClose }: PortForwardCardProps): ReactNode {
|
|
21
|
+
return (
|
|
22
|
+
<WizardCard
|
|
23
|
+
title={t('wizard.card.portForwardTitle')}
|
|
24
|
+
copyText={null}
|
|
25
|
+
copyLabel={t('wizard.card.copy')}
|
|
26
|
+
copiedLabel={t('wizard.card.copied')}
|
|
27
|
+
closeLabel={t('wizard.card.close')}
|
|
28
|
+
onClose={onClose}
|
|
29
|
+
>
|
|
30
|
+
<div className="zr-wizard-cardSteps">
|
|
31
|
+
{STEPS.map((key, index) => (
|
|
32
|
+
<p key={key} className="zr-wizard-cardStep">{`${index + 1}. ${t(`wizard.card.${key}`, { port: String(port) })}`}</p>
|
|
33
|
+
))}
|
|
34
|
+
</div>
|
|
35
|
+
</WizardCard>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProxyCard (GW-11): the reverse-proxy snippets for the user's OWN proxy,
|
|
3
|
+
* tabbed nginx / Caddy / Lucky, with the snippet copy button and the
|
|
4
|
+
* 「我配好了,检查」 button that hands control back to the wizard's
|
|
5
|
+
* verification step.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState } from 'react'
|
|
9
|
+
import type { ReactNode } from 'react'
|
|
10
|
+
import { caddySnippetText, luckyStepTexts, nginxSnippetText, SNIPPET_KINDS, type SnippetKind } from '../../../client-data/wizard.ts'
|
|
11
|
+
import type { WizardT } from './TokenCard.tsx'
|
|
12
|
+
import { WizardCard } from '../WizardCard.tsx'
|
|
13
|
+
|
|
14
|
+
export interface ProxyCardProps {
|
|
15
|
+
t: WizardT
|
|
16
|
+
/** Snippet inputs — the domain as typed, the gateway's ACTUAL port. */
|
|
17
|
+
domain: string
|
|
18
|
+
upstreamPort: number
|
|
19
|
+
/** 「我配好了,检查」: closes the card and starts the verification. */
|
|
20
|
+
onCheck: () => void
|
|
21
|
+
onClose: () => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ProxyCard({ t, domain, upstreamPort, onCheck, onClose }: ProxyCardProps): ReactNode {
|
|
25
|
+
const [tab, setTab] = useState<SnippetKind>('nginx')
|
|
26
|
+
const input = { domain, upstreamHost: '127.0.0.1', upstreamPort }
|
|
27
|
+
const text = tab === 'nginx'
|
|
28
|
+
? nginxSnippetText(input)
|
|
29
|
+
: tab === 'caddy' ? caddySnippetText(input) : (luckyStepTexts(input) ?? []).join('\n')
|
|
30
|
+
return (
|
|
31
|
+
<WizardCard
|
|
32
|
+
title={t('wizard.card.proxyTitle')}
|
|
33
|
+
copyText={text}
|
|
34
|
+
copyLabel={t('wizard.card.copy')}
|
|
35
|
+
copiedLabel={t('wizard.card.copied')}
|
|
36
|
+
closeLabel={t('wizard.card.close')}
|
|
37
|
+
primaryAction={(
|
|
38
|
+
<button type="button" className="zr-wizard-btn zr-wizard-btnPrimary" onClick={() => { onCheck(); onClose() }}>
|
|
39
|
+
{t('wizard.card.proxyCheck')}
|
|
40
|
+
</button>
|
|
41
|
+
)}
|
|
42
|
+
onClose={onClose}
|
|
43
|
+
>
|
|
44
|
+
<div className="zr-wizard-cardTabs">
|
|
45
|
+
{SNIPPET_KINDS.map((kind) => (
|
|
46
|
+
<button key={kind} type="button" className="zr-wizard-cardTab" data-selected={tab === kind} onClick={() => setTab(kind)}>
|
|
47
|
+
{kind}
|
|
48
|
+
</button>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
{text === null
|
|
52
|
+
? <p className="zr-wizard-cardStep">{t('wizard.badDomain')}</p>
|
|
53
|
+
: <pre className="zr-wizard-cardCode">{text}</pre>}
|
|
54
|
+
</WizardCard>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TokenCard (GW-11): the Cloudflare API-token creation walkthrough — eight
|
|
3
|
+
* numbered steps, the permission checklist as a small table, and a
|
|
4
|
+
* paste-in box that hands the token back to the wizard's form (`onUse`).
|
|
5
|
+
* The token never persists in component state beyond the box and never
|
|
6
|
+
* leaves this machine except inside the wizard's own POST.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useState } from 'react'
|
|
10
|
+
import type { ReactNode } from 'react'
|
|
11
|
+
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'
|
|
12
|
+
import { NS } from '../../locales.ts'
|
|
13
|
+
import { WizardCard } from '../WizardCard.tsx'
|
|
14
|
+
|
|
15
|
+
/** The wizard's translate function, straight from the shared locale contract. */
|
|
16
|
+
export type WizardT = PropsLocale<typeof NS>['t']
|
|
17
|
+
|
|
18
|
+
export interface TokenCardProps {
|
|
19
|
+
t: WizardT
|
|
20
|
+
/** Hands the pasted token back to the wizard's form field. */
|
|
21
|
+
onUse: (token: string) => void
|
|
22
|
+
onClose: () => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const STEPS = ['tokenStep1', 'tokenStep2', 'tokenStep3', 'tokenStep4', 'tokenStep5', 'tokenStep6', 'tokenStep7', 'tokenStep8'] as const
|
|
26
|
+
|
|
27
|
+
export function TokenCard({ t, onUse, onClose }: TokenCardProps): ReactNode {
|
|
28
|
+
const [pasted, setPasted] = useState('')
|
|
29
|
+
return (
|
|
30
|
+
<WizardCard
|
|
31
|
+
title={t('wizard.card.tokenTitle')}
|
|
32
|
+
copyText={null}
|
|
33
|
+
copyLabel={t('wizard.card.copy')}
|
|
34
|
+
copiedLabel={t('wizard.card.copied')}
|
|
35
|
+
closeLabel={t('wizard.card.close')}
|
|
36
|
+
primaryAction={(
|
|
37
|
+
<button type="button" className="zr-wizard-btn zr-wizard-btnPrimary" disabled={pasted.trim() === ''} onClick={() => onUse(pasted.trim())}>
|
|
38
|
+
{t('wizard.card.tokenUse')}
|
|
39
|
+
</button>
|
|
40
|
+
)}
|
|
41
|
+
onClose={onClose}
|
|
42
|
+
>
|
|
43
|
+
<div className="zr-wizard-cardSteps">
|
|
44
|
+
{STEPS.map((key, index) => (
|
|
45
|
+
<p key={key} className="zr-wizard-cardStep">{`${index + 1}. ${t(`wizard.card.${key}`)}`}</p>
|
|
46
|
+
))}
|
|
47
|
+
</div>
|
|
48
|
+
<p className="zr-wizard-cardSubhead">{t('wizard.card.tokenPerms')}</p>
|
|
49
|
+
<table className="zr-wizard-cardTable">
|
|
50
|
+
<tbody>
|
|
51
|
+
<tr>
|
|
52
|
+
<td className="zr-wizard-cardPerm">{t('wizard.card.tokenPermDns')}</td>
|
|
53
|
+
<td className="zr-wizard-cardWhy">{t('wizard.card.tokenPermDnsWhy')}</td>
|
|
54
|
+
</tr>
|
|
55
|
+
<tr>
|
|
56
|
+
<td className="zr-wizard-cardPerm">{t('wizard.card.tokenPermTunnel')}</td>
|
|
57
|
+
<td className="zr-wizard-cardWhy">{t('wizard.card.tokenPermTunnelWhy')}</td>
|
|
58
|
+
</tr>
|
|
59
|
+
<tr>
|
|
60
|
+
<td className="zr-wizard-cardPerm">{t('wizard.card.tokenPermAccount')}</td>
|
|
61
|
+
<td className="zr-wizard-cardWhy">{t('wizard.card.tokenPermAccountWhy')}</td>
|
|
62
|
+
</tr>
|
|
63
|
+
<tr>
|
|
64
|
+
<td className="zr-wizard-cardPerm">{t('wizard.card.tokenPermZoneRead')}</td>
|
|
65
|
+
<td className="zr-wizard-cardWhy">{t('wizard.card.tokenPermZoneReadWhy')}</td>
|
|
66
|
+
</tr>
|
|
67
|
+
</tbody>
|
|
68
|
+
</table>
|
|
69
|
+
<div className="zr-wizard-field">
|
|
70
|
+
<label className="zr-wizard-label" htmlFor="zr-wizard-token-paste">{t('wizard.card.tokenPaste')}</label>
|
|
71
|
+
<input
|
|
72
|
+
id="zr-wizard-token-paste"
|
|
73
|
+
className="zr-wizard-input"
|
|
74
|
+
type="password"
|
|
75
|
+
value={pasted}
|
|
76
|
+
onChange={(event) => setPasted(event.target.value)}
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
</WizardCard>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard registration for the plugin DETAIL page — the piece that must
|
|
3
|
+
* run BEFORE apply's desktop-shell gate (same discipline as
|
|
4
|
+
* register-settings.ts, and for the same reason: the desktop app IS the
|
|
5
|
+
* main server, and the wizard is exactly where it is most useful).
|
|
6
|
+
*
|
|
7
|
+
* Registered on `plugins.bundle.config` (keyed by `'dsh-zen-remote'`), which
|
|
8
|
+
* `PackageDetail` renders as the FIRST section at the top of the bundle
|
|
9
|
+
* detail page (above `RowsSection`「插件组件」and `SourceSection`「来源」),
|
|
10
|
+
* and which `RowDetail` (the component detail page) never renders.
|
|
11
|
+
*
|
|
12
|
+
* `configForms` never joins the top-level inject: a composition without
|
|
13
|
+
* the settings service keeps the whole browser half loadable, and the lazy
|
|
14
|
+
* `ctx.inject(['configForms'], …)` below simply never fires there — a
|
|
15
|
+
* composition that cannot stage row edits has no use for a wizard that
|
|
16
|
+
* writes rows.
|
|
17
|
+
*/
|
|
18
|
+
import type { ClientContext } from '../compat/types.ts'
|
|
19
|
+
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
20
|
+
// Type-only: the renderer declares `ctx.slots` on the cordis Context.
|
|
21
|
+
import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
|
|
22
|
+
import { DEVICE_TOKEN_FIELD, ZenRemoteSettingsForm } from '../../client-data/settings-form.ts'
|
|
23
|
+
import { NS } from '../locales.ts'
|
|
24
|
+
import { SETTINGS_ENTRY_ID } from '../settings/register-settings.ts'
|
|
25
|
+
import { WIZARD_CSS } from './wizard-css.ts'
|
|
26
|
+
import type { GatewayWizard } from './GatewayWizard.tsx'
|
|
27
|
+
|
|
28
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
29
|
+
interface SlotMap {
|
|
30
|
+
/**
|
|
31
|
+
* One bundle's top configuration block on the plugin DETAIL page — a
|
|
32
|
+
* keyed seat (`key = pkg.name`) rendered at the top of `PackageDetail`
|
|
33
|
+
* above the component rows and source sections, and absent from
|
|
34
|
+
* `RowDetail` (structural mirror of the plugin-manager's declaration).
|
|
35
|
+
*/
|
|
36
|
+
'plugins.bundle.config': {
|
|
37
|
+
kind: 'keyed'
|
|
38
|
+
scope: 'root'
|
|
39
|
+
owner: { readonly view: 'page' }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** `plugins.bundle.config` key: the bundle's package name. */
|
|
45
|
+
export const WIZARD_BUNDLE_KEY = 'dsh-zen-remote'
|
|
46
|
+
|
|
47
|
+
export interface SaveRowFieldsOptions {
|
|
48
|
+
/** Field names to remove (`op: 'unset'`) in the same atomic mutation. */
|
|
49
|
+
unset?: readonly string[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Register the wizard stylesheet and (lazily, only where the settings
|
|
54
|
+
* service exists) the bundle detail-page top block.
|
|
55
|
+
* @param ctx - client root context.
|
|
56
|
+
* @param section - the block component; a parameter so this module stays
|
|
57
|
+
* JSX-free and importable under Node's type stripping.
|
|
58
|
+
*/
|
|
59
|
+
export function registerGatewayWizard(ctx: ClientContext, section: typeof GatewayWizard): void {
|
|
60
|
+
ctx.effect(() => {
|
|
61
|
+
const tag = document.createElement('style')
|
|
62
|
+
tag.dataset.plugin = 'dsh-zen-remote-wizard'
|
|
63
|
+
tag.textContent = WIZARD_CSS
|
|
64
|
+
document.head.appendChild(tag)
|
|
65
|
+
return () => { tag.remove() }
|
|
66
|
+
}, 'dsh-zen-remote: wizard styles')
|
|
67
|
+
|
|
68
|
+
ctx.inject(['configForms'], (formsCtx) => {
|
|
69
|
+
const scope = formsCtx.configForms.get(SETTINGS_ENTRY_ID)
|
|
70
|
+
// Follow the describe sidecar when available so client-mode pairing can
|
|
71
|
+
// see whether `deviceToken` is configured (degrades to false in minimal
|
|
72
|
+
// test harnesses that omit `describe`).
|
|
73
|
+
const describe = typeof formsCtx.configForms.describe === 'function' ? formsCtx.configForms.describe() : undefined
|
|
74
|
+
const secretConfigured = (): boolean => {
|
|
75
|
+
const namespaces = describe?.getSnapshot().view?.namespaces ?? []
|
|
76
|
+
const row = namespaces.find((candidate) => candidate.ns === SETTINGS_ENTRY_ID)
|
|
77
|
+
return (row?.secrets ?? []).some(
|
|
78
|
+
(secret) => secret.path.length === 1 && secret.path[0] === DEVICE_TOKEN_FIELD && secret.set,
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
const config = new ZenRemoteSettingsForm(scope, secretConfigured)
|
|
82
|
+
const offDescribe = describe !== undefined ? describe.subscribe(() => { config.refresh() }) : () => {}
|
|
83
|
+
formsCtx.effect(() => () => { offDescribe(); config.dispose() }, 'dsh-zen-remote: wizard form')
|
|
84
|
+
const saveRowFields = async (fields: Record<string, unknown>, options?: SaveRowFieldsOptions): Promise<void> => {
|
|
85
|
+
const snapshot = scope.getSnapshot()
|
|
86
|
+
const ops = [
|
|
87
|
+
...Object.entries(fields).map(([key, value]) => ({ op: 'set' as const, path: [key], value })),
|
|
88
|
+
...(options?.unset ?? []).map((key) => ({ op: 'unset' as const, path: [key] })),
|
|
89
|
+
]
|
|
90
|
+
await scope.mutate(ops, snapshot.revision)
|
|
91
|
+
}
|
|
92
|
+
formsCtx.effect(() => formsCtx.slots.inject('plugins.bundle.config', () => formsCtx.slots.register({
|
|
93
|
+
name: 'plugins.bundle.config',
|
|
94
|
+
key: WIZARD_BUNDLE_KEY,
|
|
95
|
+
locale: NS,
|
|
96
|
+
inject: () => ({ config, saveRowFields }),
|
|
97
|
+
}, section)), 'dsh-zen-remote: wizard section')
|
|
98
|
+
})
|
|
99
|
+
}
|