61 lines
2.2 KiB
TypeScript
61 lines
2.2 KiB
TypeScript
import { useEffect, useState } from 'react'
|
||
import { api } from '@shared/api/client'
|
||
import type { Page, PasskeyDto, TrustStateDto } from '@shared/api/types'
|
||
|
||
/**
|
||
* 选这个节点的属主(必选,至少一把):允许授权它 extension 会话(web ssh 等,以后)、解密它加密内容的 passkey。只是选,不需要触碰 Passkey:
|
||
* 选定的凭证 id 会写进安装命令,节点装上时固定在本地,之后面板改不了。默认是操作员集合里的全部。
|
||
* 还没有建立信任根时不显示选项,只提示去建立。
|
||
*/
|
||
export function OwnerPicker({ value, onChange }: { value: string[] | null; onChange: (next: string[]) => void }) {
|
||
const [trust, setTrust] = useState<TrustStateDto>()
|
||
const [own, setOwn] = useState<PasskeyDto[]>([])
|
||
|
||
useEffect(() => {
|
||
let alive = true
|
||
api
|
||
.get<TrustStateDto>('/api/admin/trust')
|
||
.then((t) => {
|
||
if (!alive) return
|
||
setTrust(t)
|
||
if (value === null) onChange(t.operators ?? [])
|
||
})
|
||
.catch(() => undefined)
|
||
api
|
||
.get<Page<PasskeyDto>>('/api/admin/auth/passkeys')
|
||
.then((list) => alive && setOwn(list.items))
|
||
.catch(() => undefined)
|
||
return () => {
|
||
alive = false
|
||
}
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [])
|
||
|
||
if (!trust) return null
|
||
if (!trust.initialized) {
|
||
return <div className="notice warn">还没有建立信任根:每个节点都必须有属主(passkey),先到「系统 → 信任」用 Passkey 建立信任根,才能添加节点。</div>
|
||
}
|
||
|
||
const chosen = new Set(value ?? [])
|
||
const name = (id: string) => own.find((p) => p.id === id)?.name ?? `其他账号的 Passkey(${id.slice(0, 8)}…)`
|
||
return (
|
||
<div className="stack" style={{ gap: 4 }}>
|
||
{(trust.operators ?? []).map((id) => (
|
||
<label key={id} className="switch">
|
||
<input
|
||
type="checkbox"
|
||
checked={chosen.has(id)}
|
||
onChange={(e) => {
|
||
const next = new Set(chosen)
|
||
if (e.target.checked) next.add(id)
|
||
else next.delete(id)
|
||
onChange([...next])
|
||
}}
|
||
/>
|
||
{name(id)}
|
||
</label>
|
||
))}
|
||
</div>
|
||
)
|
||
}
|