import { Check, KeyRound, ShieldCheck, UserPlus } from 'lucide-react' import { useState, type FormEvent } from 'react' import { ApiError } from '@shared/api/client' import { passkeySupported } from '@shared/api/webauthn' import { describeError, Field } from '@shared/ui' import { useAuth } from './auth' import { Trust } from './pages/Trust' import { PasskeyAddForm } from './pages/PasskeyAdd' const STEPS = [ { icon: UserPlus, label: '认领面板' }, { icon: KeyRound, label: '注册 Passkey' }, { icon: ShieldCheck, label: '建立信任根' }, ] /** * 认领向导:面板里还没有任何用户时出现。没有默认账户,管理员的用户名和密码由部署的人自己填; * 谁先打开页面谁就能当管理员,所以第一步要后端日志里的认领码(只有部署的人拿得到)。 * 后两步是引导,不是门槛:之后随时可以在「用户 → 我的 Passkey」和「系统 → 信任」里补,没有 passkey 也能用密码登录管理。 */ export function Setup() { const { me, completeSetup, finishSetup } = useAuth() const [step, setStep] = useState(me ? 1 : 0) const [trusted, setTrusted] = useState(false) return (

MoShiro

第一次打开:先认领这个面板——建好管理员、Passkey 和信任根。

    {STEPS.map((s, i) => (
  1. {i < step ? : } {i + 1}. {s.label}
  2. ))}
{step === 0 ? setStep(1)} complete={completeSetup} /> : null} {step === 1 ? (

Passkey 是整个系统的根:它给「操作员集合」签名,节点只认从它出发的信任根,以后 web ssh 等敏感操作也只认它。 不注册 Passkey、不建立信任根就无法使用这些功能:添加不了 Agent 和 Probe,只能进入面板做基础管理,这是架构如此,面板自己没法替你代签。 {passkeySupported() ? '' : ' 这个浏览器不支持 Passkey(需要 HTTPS 或 localhost)。'}

setStep(2)} />
) : null} {step === 2 ? (
setTrusted(true)} />
) : null}
) } function Account({ complete, onDone }: { complete: (code: string, username: string, password: string) => Promise; onDone: () => void }) { const [code, setCode] = useState('') const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [confirm, setConfirm] = useState('') const [error, setError] = useState('') const [busy, setBusy] = useState(false) const submit = async (e: FormEvent) => { e.preventDefault() if (password !== confirm) { setError('两次输入的密码不一样') return } setBusy(true) setError('') try { await complete(code, username, password) onDone() } catch (err) { if (err instanceof ApiError && err.status === 401) setError('认领码不对:它写在后端的日志里(journalctl -u moshiro-backend,或 docker logs)') else if (err instanceof ApiError && err.status === 409) setError('这个面板已经被认领过了,请直接登录(刷新页面)') else setError(describeError(err)) } finally { setBusy(false) } } return (
setCode(e.target.value)} placeholder="XXXX-XXXX-XXXX-XXXX" autoComplete="off" autoFocus required /> setUsername(e.target.value)} autoComplete="username" required /> setPassword(e.target.value)} autoComplete="new-password" required /> setConfirm(e.target.value)} autoComplete="new-password" required /> {error ?
{error}
: null}
) }