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) => (
-
{i < step ? : }
{i + 1}. {s.label}
))}
{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 (
)
}