feat: the node itself requires an owner passkey (shared by agent and probe) and reports it; the panel derives the account and passkey it is bound to [CI]

This commit is contained in:
justme committed 2026-10-08 06:15:07 +08:00
1 parent 8c37db1577
commit 2ae63653c4
23 files changed
+328 -10

No files matched your search

+23 -1
View File
@@ -1,7 +1,7 @@
import { Download, KeyRound, Plus, Trash2 } from 'lucide-react'
import { useEffect, useMemo, useState, type ReactNode } from 'react'
import { api } from '@shared/api/client'
import type { AgentDto, ApiTokenDto, BuildInfoDto, NodeBinaryDto, CapabilityCatalogDto, MeDto, PasskeyDto, NodeUpdateDto, ReleaseDto, CreatedApiTokenDto, AuditDto, EventDto, Page, ProbeDto, RawChunkMetaDto, RecomputeDto, SettingDto, UserDto } from '@shared/api/types'
import type { AgentDto, ApiTokenDto, BuildInfoDto, NodeBinaryDto, NodeOwnerDto, CapabilityCatalogDto, MeDto, PasskeyDto, NodeUpdateDto, ReleaseDto, CreatedApiTokenDto, AuditDto, EventDto, Page, ProbeDto, RawChunkMetaDto, RecomputeDto, SettingDto, UserDto } from '@shared/api/types'
import { fmtBuiltAt, frontendBuild } from '@shared/buildInfo'
import { fmtNum, fmtTime } from '@shared/format'
import { translateSetting } from '@shared/i18n'
@@ -192,6 +192,7 @@ function BuildRow({ name, kind, version, tag, commit, builtAt, platform, update
<td className="muted">{platform || dash}</td>
<td>{update ?? dash}</td>
<td>{dash}</td>
<td>{dash}</td>
</tr>
)
}
@@ -210,6 +211,23 @@ const UPDATE_STATE: Record<string, { text: string; tone: '' | 'ok' | 'warn' | 'b
* 节点二进制的绑定:第一次握手成功时绑定它正在运行的二进制的哈希,之后对不上又不在 CI 签名的更新清单里就拒绝连接。
* 「已签名」表示哈希出现在一份 CI 签名的清单里;否则只是首次握手时记下的。不符时可以确认当前的二进制是自己要的(比如手工升级了)。
*/
/**
* 节点的属主:节点自己要求有属主(passkey),安装时固定在本地;面板顺着它报上来的凭证 id 反查出绑定在哪个账户的哪把 passkey 上。
* 以后给节点更多接口和操作(web ssh、加密内容)时,只认这些 passkey。
*/
function OwnerCell({ owners }: { owners?: NodeOwnerDto[] | null }) {
if (!owners || owners.length === 0) return <span className="faint">未报告</span>
return (
<span className="row" style={{ gap: 6, flexWrap: 'wrap' }}>
{owners.map((o) => (
<Chip key={o.credential_id} tone={o.passkey ? '' : 'warn'}>
<span title={o.credential_id}>{o.passkey ? `${o.passkey}${o.account ? ` · ${o.account}` : ''}` : '未知凭证'}</span>
</Chip>
))}
</span>
)
}
function BinaryCell({ binary, busy, onAccept }: { binary?: NodeBinaryDto | null; busy: boolean; onAccept: () => void }) {
if (!binary) return <span className="faint">未绑定</span>
const short = binary.sha256.slice(0, 10)
@@ -293,6 +311,7 @@ function Versions() {
<th>系统</th>
<th>更新</th>
<th>二进制</th>
<th>属主</th>
</tr>
</thead>
<tbody>
@@ -317,6 +336,9 @@ function Versions() {
<td>
<BinaryCell binary={n.binary} busy={busy} onAccept={() => void run(async () => { await api.del(`/api/admin/nodes/${n.id}/binary`); info.reload() }, '已清除绑定,节点下一次连接时重新绑定')} />
</td>
<td>
<OwnerCell owners={n.owners} />
</td>
</tr>
))}
</tbody>