>('/api/admin/auth/passkeys')
- .then((list) => alive && setOwn(list.items))
- .catch(() => undefined)
return () => {
alive = false
}
@@ -33,11 +28,20 @@ export function OwnerPicker({ value, onChange }: { value: string[] | null; onCha
if (!trust) return null
if (!trust.initialized) {
- return 还没有建立信任根:这个节点装上后不会固定属主,也不会校验面板的操作员集合。先到「系统 → 信任」用 Passkey 建立。
+ return (
+
+ 还没有建立信任根,所以现在无法添加 Agent 和 Probe:每个节点都要绑定到属主的 Passkey 上,节点只信任由 Passkey 签名的信任根。没有信任根时只能进入面板做基础管理,这是架构如此。请先到「系统 → 信任」注册 Passkey 并建立信任根。
+
+ )
}
const chosen = new Set(value ?? [])
- const name = (id: string) => own.find((p) => p.id === id)?.name ?? `其他账号的 Passkey(${id.slice(0, 8)}…)`
+ const details = new Map((trust.operator_details ?? []).map((d) => [d.credential_id, d] as const))
+ // 绑定到哪个账户的哪把 Passkey:「Passkey 名称 · 账户」;对不上任何已注册的 Passkey 时标明
+ const name = (id: string) => {
+ const d = details.get(id)
+ return d?.passkey ? `${d.passkey}${d.account ? ` · ${d.account}` : ''}` : `未知 Passkey(${id.slice(0, 8)}…)`
+ }
return (
{(trust.operators ?? []).map((id) => (
diff --git a/web/admin/src/pages/Probes.tsx b/web/admin/src/pages/Probes.tsx
index 63a1c8c..c64bcc6 100644
--- a/web/admin/src/pages/Probes.tsx
+++ b/web/admin/src/pages/Probes.tsx
@@ -113,7 +113,7 @@ function AddProbeDialog({ onClose, onCreated }: { onClose: () => void; onCreated
- 建立之后,新装的节点会把你的信任根指纹和属主固定在本地:以后 web ssh 等敏感操作只认你的 Passkey,Backend 被攻破也改不了。添加节点本身不需要触碰 Passkey。
+ 不建立信任根就无法添加 Agent 和 Probe,只能进入面板做基础管理——这是架构如此:节点只信任由你的 Passkey 签名的信任根,面板自己没法代签。建立之后,新装的节点会把你的信任根指纹和属主固定在本地:以后 web ssh 等敏感操作只认你的 Passkey,Backend 被攻破也改不了。添加节点本身不需要触碰 Passkey。
这一步会把下面列出的 Passkey 登记为操作员,需要你触碰一次 Passkey 给集合签名。
{state.proposal ? (
diff --git a/web/shared/api/schema.ts b/web/shared/api/schema.ts
index 90c4d80..4102822 100644
--- a/web/shared/api/schema.ts
+++ b/web/shared/api/schema.ts
@@ -327,6 +327,24 @@ export interface paths {
patch?: never;
trace?: never;
};
+ "/api/admin/auth/setup": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Whether the panel has no users yet and still has to be claimed */
+ get: operations["setup_status"];
+ put?: never;
+ /** Create the first administrator (needs the claim code from the backend's log); the same response as the password sign-in. Closed once the panel is claimed (a user exists) */
+ post: operations["setup_complete"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/api/admin/auth/passkey/options": {
parameters: {
query?: never;
@@ -1720,6 +1738,7 @@ export interface components {
built_at: null | string;
update?: null | components["schemas"]["NodeUpdateDto"];
binary?: null | components["schemas"]["NodeBinaryDto"];
+ owners?: null | components["schemas"]["NodeOwnerDto"][];
};
NodeInfoDto: {
id: string;
@@ -1733,6 +1752,11 @@ export interface components {
version?: null | string;
tag?: null | string;
};
+ NodeOwnerDto: {
+ credential_id: string;
+ passkey: null | string;
+ account: null | string;
+ };
NodeUpdateDto: {
state: string;
current_digest: null | string;
@@ -2067,6 +2091,15 @@ export interface components {
default: string;
description: string;
};
+ SetupRequest: {
+ claim_code: null | string;
+ username: null | string;
+ password: null | string;
+ return_session: null | boolean;
+ };
+ SetupStatusDto: {
+ needs_setup: boolean;
+ };
SignedStatementDto: {
statement?: string;
credential_id?: string;
@@ -2188,6 +2221,7 @@ export interface components {
server_key: string;
/** @default false */
server_key_listed: boolean;
+ operator_details?: null | components["schemas"]["NodeOwnerDto"][];
};
UserDto: {
id: string;
@@ -3070,6 +3104,86 @@ export interface operations {
};
};
};
+ setup_status: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description OK */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["SetupStatusDto"];
+ };
+ };
+ };
+ };
+ setup_complete: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["SetupRequest"];
+ };
+ };
+ responses: {
+ /** @description Created */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["LoginResponse"];
+ };
+ };
+ /** @description Bad Request */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/problem+json": components["schemas"]["ProblemDto"];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/problem+json": components["schemas"]["ProblemDto"];
+ };
+ };
+ /** @description Conflict */
+ 409: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/problem+json": components["schemas"]["ProblemDto"];
+ };
+ };
+ /** @description Too Many Requests */
+ 429: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/problem+json": components["schemas"]["ProblemDto"];
+ };
+ };
+ };
+ };
auth_passkey_options: {
parameters: {
query?: never;
diff --git a/web/shared/api/types.ts b/web/shared/api/types.ts
index ed42e81..618df03 100644
--- a/web/shared/api/types.ts
+++ b/web/shared/api/types.ts
@@ -20,6 +20,7 @@ export type BackendUpdateDto = Narrow
export type NodeBuildDto = Narrow
export type NodeUpdateDto = Narrow
export type NodeBinaryDto = Narrow
+export type NodeOwnerDto = Narrow
export type ReleaseDto = Narrow
export type LinkDto = Narrow
export type LinkMatrixDto = Narrow
@@ -55,6 +56,7 @@ export type GapDto = Narrow
export type RecomputeDto = Narrow
export type RawChunkMetaDto = Narrow
export type LoginResponse = Narrow
+export type SetupStatusDto = Narrow
export interface Page {
items: T[]