手冊首頁 / 系統管理 / 檔案 Agent 管理

檔案 Agent 管理(Remote Agent Manage)

功能群:證據管理|儲存後端 / 兩種證據概念 / 核心資料模型先讀 功能群總覽

事實基準:2026-07-05 從 FE / BE code 掃出(FR-039 分散式檔案 Agent,身分模型 v3 決策 B:agent_uid 為主鍵,device_fingerprint 降為可更新屬性);API schema 逐條對照 marshmallow serializer 與 service 組裝碼

1. 功能描述

檔案 Agent 管理頁是租戶層級的客戶端檔案 Agent 註冊表管理介面:機敏證據檔案不落雲端,改落地在客戶自有設備(agent),本頁負責「產生註冊 Token 給 agent 安裝時使用」與「檢視 / 管理已註冊(或手動新增)的 agent 清單」——含在線狀態、註冊狀態(active/revoked)、設備指紋綁定情形、對帳(SHA-256 完整性比對)。管理的資料是 compliance.remote_agentscompliance.remote_agent_enroll_tokens,供 儲存設定 頁的 storage_type = remote_agent 選路使用(見 _overview §4)。

主要使用者:租戶 admin(系統管理層)。存取由 RBAC 選單權限(ui_routes / capabilitiesresource_type = 'remote-agent-manage')控管,BE 僅檢查 JWT 登入、不在 service 層硬卡角色——與本群其餘管理頁(雲端整合 / 儲存設定)同一基調(見 _overview §5)。

角色速覽

角色 一句話
租戶 admin 本頁唯一使用者——產生/撤銷 token、管理 agent registry、對帳、撤銷/復用設備
(agent 本身) 不是本頁使用者,而是本頁管理資料的「被管理對象」;agent 以 token/mTLS 主動撥出註冊與心跳,不經過本頁 UI

1.1 功能總覽(本頁全部功能)

# 功能 說明 位置 詳述
1 註冊 Token 產生 / 重新產生 每租戶一組(active),installer 安裝時填入;agent 首次連線帶此 token 自我註冊 Card「註冊 Token」 UC-RA-01
2 註冊 Token 撤銷 停用本租戶全部 active token,使用舊 token 的 installer 立即失效 Card「註冊 Token」 UC-RA-01
3 Token 明文顯示(僅一次) 產生後彈窗顯示 token + 雲端 endpoint,各自可複製;關閉後無法再次顯示明文 Dialog(產生後彈出) UC-RA-01
4 Agent 清單 本租戶全部 agent(含停用),逐列顯示名稱 / 位址 / 類型 / 在線狀態 / 健康檢查 / 註冊狀態 / 設備指紋 / 版本 / 啟用開關 DataTable §5
5 新增 Agent(手動) 手動登錄一台 agent(demo / 非自我註冊情境):名稱、位址、類型、啟用 Header「新增」→ Dialog UC-RA-04
6 編輯 Agent 修改名稱 / 類型 / 啟用;位址(base_url)編輯時唯讀(自我註冊的 agent 位址由 mTLS 憑證 SAN 綁定,雲端改了不會同步、換位址會讓 mTLS 因 SAN 不符而斷) 逐列「編輯」→ Dialog UC-RA-04(唯讀坑見 §12 坑 1)
7 測試連線 新增 / 編輯對話框內,或列表 Header「測試連線」對全部 agent 一次探測;雲端 server-to-server 打 agent /health Dialog 內 + Header UC-RA-04
8 啟用 / 停用開關 InputSwitch 直接切換,單欄位 PUT 清單「啟用」欄 UC-RA-04
9 心跳在線狀態 由 BE last_seen_at 推算 online/offline(非即時探測),顯示最後回報時間 清單「在線狀態」欄 §10
10 即時健康檢查 頁面載入後或按「測試連線」時,逐一探測(與 #9 心跳推算獨立、平行呈現) 清單「健康檢查」欄 §10
11 對帳 請 agent 對磁碟實際 bytes 重算 SHA-256,比對雲端錨點,抓靜態竄改 / 遺失 / 連線異常 逐列「對帳」按鈕 UC-RA-02
12 撤銷 / 復用設備 撤銷:status=revoked,雲端所有心跳 / 資料面呼叫立即拒絕;復用:改回 active 逐列「撤銷設備」/「復用設備」按鈕 UC-RA-03
13 刪除 Agent 從 registry 移除該列(硬刪除) 逐列「刪除」按鈕 §6.1
14 設備指紋顯示 已綁定顯示截短指紋(tooltip 全值);未綁定顯示「未綁定」 清單「設備指紋」欄 §9.3

UC(§2)展開本頁所有具實質後端邏輯的功能:Token 產生/撤銷(1/2/3)、對帳(11)、撤銷/復用(12)、手動新增/編輯 Agent 含測試連線與存前探測(5/6/7/8,即 UC-RA-04)。純顯示 / 導覽類(4 清單、9 心跳在線狀態、10 即時健康檢查、14 設備指紋顯示)見 §5 / §10;刪除 Agent(13)為單步硬刪除、無前置邏輯,於總覽表描述已完整。重新綁定(rebind)功能已從 UI 移除(見 §12 坑 2),本頁不再展示該按鈕。

2. Use Case

角色速覽

角色 一句話
租戶 admin 本頁唯一使用者——產生/撤銷 token、管理 agent registry、對帳、撤銷/復用設備
(agent 本身) 不是本頁使用者,而是本頁管理資料的「被管理對象」;agent 以 token/mTLS 主動撥出註冊與心跳,不經過本頁 UI

流程圖視覺慣例:菱形 = 判斷、橘底 = 例外/擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。

UC-RA-01 產生註冊 Token 並提供 agent 自我註冊

項目 內容
角色 租戶 admin(產生/撤銷);agent(消費 token 自我註冊,非本頁操作)
前置條件 已登入;remote-agent-manage 選單可見
產出 / 後置條件 remote_agent_enroll_tokens 新增一列 enabled(舊列停用);agent 安裝後首次連線帶此 token → remote_agents upsert 一列
agent_enrollment_lifecycle start admin 在管理頁「產生 Token」 POST /agents/enroll-token installer Token + endpoint 寫入 agent installer/.env start->installer boot agent 啟動:算 device_uuid (sha256 of product_uuid+machine_id+MAC) installer->boot register agent 自我註冊 POST /agents/register {token, device_uuid, agent_uid?, hardware_info, csr?} boot->register tokCheck BE 檢查:token hash 對得到 啟用中的 tenant token? register->tokCheck tokInvalid 401 FILE_AGENT_TOKEN_INVALID tokCheck->tokInvalid resolve _resolve_agent:先 by agent_uid(同 tenant) 找不到退 by (tenant_id, device_fingerprint) tokCheck->resolve existing 已有列?(re-auth 或換硬體) 無 → 新增;有且指紋變 → 自動更新+記稽核 resolve->existing upsertRow upsert remote_agents(uid=agent_uid) base_url/device_fingerprint/hardware_info/ agent_version/last_seen_at/status=active existing->upsertRow signCert 有 CSR 且 CA 已設定? → 簽 agent server 憑證+回 CA/JWT 公鑰 upsertRow->signCert respond 回傳 {agent_uid, certificate?, ca_certificate?, jwt_public_key?, heartbeat_interval_sec} signCert->respond visible 管理頁重新整理即可見 (無即時 UI,延後 SocketIO) respond->visible
UC-RA-01 流程:admin 產生 token(明文僅顯示一次)→ 寫入 installer → agent 啟動自我註冊 → 雲端驗 token → upsert registry → 可簽憑證 → 管理頁重新整理可見

UC-RA-02 對帳(SHA-256 完整性比對)

項目 內容
角色 租戶 admin
前置條件 該 agent 底下有雲端已知的檔案錨點(upload_files 存過 sha256)
產出 / 後置條件 無寫入資料(唯讀比對);回傳 {total, matched, mismatched[], missing[], error[]}mismatched/missing/error 任一 > 0 即以錯誤樣式呈現(不算全綠成功)

流程:逐檔向 agent 打 /blob/{save_file_name}/sha256(full 模式帶 mTLS + 短效 JWT)→ 404 記 missing、≥400 或連線例外記 error(並寫 warning log,不靜默)、sha256 相符記 matched、不符記 mismatched(疑似靜態竄改)。

agent_reconcile start admin 管理頁點「對帳」 (唯讀,不寫資料) probe 逐檔向 agent 打 GET /blob/{save_file_name}/sha256 (full 模式帶 mTLS + 短效 JWT) start->probe branch 每檔回應? probe->branch matched sha256 相符 → matched branch->matched 相符 mismatched sha256 不符 → mismatched(疑似靜態竄改) branch->mismatched 不符 missing 404 → missing branch->missing 404 error ≥400 或連線例外 → error(寫 warning log,不靜默) branch->error ≥400/例外 agg 回傳 {total, matched, mismatched[], missing[], error[]} mismatched/missing/error 任一 > 0 → 以錯誤樣式呈現(非全綠成功) matched->agg mismatched->agg missing->agg error->agg
UC-RA-02 流程:逐檔打 /blob/{name}/sha256 → 依回應四向分類(相符 matched/不符 mismatched/404 missing/≥400 或例外 error)→ 彙總,任一非 matched > 0 即以錯誤樣式呈現

UC-RA-03 撤銷 / 復用設備

項目 內容
角色 租戶 admin
前置條件 該 agent 列已存在(verify_remote_agent_is_exist 404 檢查)
產出 / 後置條件 statusactive/revoked 間切換;revoked 後該 agent 心跳(403 FILE_AGENT_REVOKED)與資料面呼叫立即被拒
agent_heartbeat_revoke heartbeat agent 每 N 秒心跳 POST /agents/heartbeat {agent_uid, device_uuid, status} hbFind by agent_uid 找列 (找不到再退 by 指紋,向後相容舊 agent) heartbeat->hbFind hbMissing 仍找不到? (平台已刪該列) hbFind->hbMissing hb404 404 FILE_AGENT_NOT_FOUND → agent 清本地狀態,自動重新 register hbMissing->hb404 hbRevoked status == revoked? hbMissing->hbRevoked hb403 403 FILE_AGENT_REVOKED hbRevoked->hb403 hbUpdate 更新 last_seen_at (指紋變動同樣自動更新+記稽核,不需人工 rebind) hbRevoked->hbUpdate online 管理頁「在線狀態」由 last_seen_at 推算 (< heartbeat_interval_sec × 3,預設 15 分=online) hbUpdate->online revoke admin 管理頁「撤銷設備」 POST /remote-agents/{uid}/revoke online->revoke admin 可隨時 revoked status=revoked revoke->revoked blocked 後續心跳/資料面 /blob 呼叫 一律 403 拒絕 revoked->blocked unrevoke admin「復用設備」 POST /remote-agents/{uid}/unrevoke → status=active blocked->unrevoke admin 可復用
UC-RA-03 流程:心跳 by agent_uid 找列 → 找不到 404 自動重註冊 → revoked 拒絕 403 → admin 可隨時撤銷/復用

UC-RA-04 手動新增 / 編輯 Agent(含測試連線)

項目 內容
角色 租戶 admin
前置條件 已登入;remote-agent-manage 選單可見;編輯時該列已存在
產出 / 後置條件 remote_agents 新增一列(POST)或部分更新既有列(PUT);名稱 / 位址為空 → 400(FILE_AGENT_400001 / FILE_AGENT_400002);編輯 uid 不存在 → 404(FILE_AGENT_NOT_FOUND

存前流程走探測後儲存:新增 / 編輯共用同一 doSave(),存前先呼叫 POST /remote-agents/health-check 探測 base_url;探測不通(reachable=false,任何連線 / 逾時錯誤都被吞成此值、不丟 500)時彈確認框,admin 可選擇仍要儲存。編輯既有列時 base_url 輸入框鎖唯讀(FE 防呆;自我註冊 agent 位址由憑證 SAN 綁定,改位址會斷 mTLS,見 §12 坑 1)。新增可先按「測試連線」單獨探測,或存前自動探測。

agent_crud start admin 開「新增」或逐列「編輯」對話框 editMode 編輯既有列?(有 uid) start->editMode lockUrl base_url 輸入框鎖唯讀 (自我註冊 agent 位址由憑證 SAN 綁定) editMode->lockUrl fill 填名稱/位址/類型/啟用 editMode->fill 否(新增) lockUrl->fill test 測試連線(可選) POST /remote-agents/health-check {base_url} fill->test 可先測 probe 存前自動探測 POST /remote-agents/health-check (任何連線/逾時錯誤吞成 reachable=false) fill->probe test->probe reachable reachable? probe->reachable confirm 彈確認框:仍要儲存? reachable->confirm valid 名稱/位址為空? reachable->valid confirm->valid 確認仍存 err400 400 FILE_AGENT_400001(缺名稱) / FILE_AGENT_400002(缺位址) valid->err400 save 新增 POST /remote-agents / 編輯 PUT /remote-agents/{uid}(部分更新) valid->save err404 編輯 uid 不存在 → 404 FILE_AGENT_NOT_FOUND save->err404 編輯且不存在 done 關閉對話框 → 重新 getAgents() save->done
UC-RA-04 流程:開對話框→編輯則鎖 base_url→(可選)測試連線→存前 health-check 探測→不通彈確認仍可存→名稱/位址空擋 400→POST 新增或 PUT 部分更新(編輯 uid 不存在 404)→重載清單

3. 權限矩陣

操作 FE 判定 BE 強制 BE 檢查位置
選單 / 頁面可見性 RBAC 選單(ui_routes.name='remote-agent-manage' + capabilities.resource_type='remote-agent-manage' ⚠️ ——全部 16 支 UI 相關 endpoint 僅 @jwt_required() api/remote_agent/routes/remote_agent_route.py(每個 Route class 逐一)
新增 / 編輯 / 刪除 / 啟用切換 / 撤銷 / 復用 / 對帳 / 健康檢查 無額外前端角色判斷(頁面本身即假設只有 admin 進得來) 同上,僅 JWT,service 層不做角色檢查AgentEnrollTokenService docstring 明寫「存取權限由 RBAC 控管,不在 service 層硬卡角色」) app/remote_agent/service/remote_agent_service.pyagent_enroll_token_service.py
agent 自我註冊 / 心跳 不適用(非使用者操作) 不走 user JWTPOST /agents/register 用註冊 token(sha256 比對)驗身;POST /agents/heartbeat 依賴網路層 mTLS client 憑證(nginx 終止)+ agent_uid/指紋比對 app/remote_agent/service/agent_enrollment_service.py

⚠️ 已知設計決策(非遺漏):本頁與本群其餘管理頁一致,刻意不在 service 層加角色檢查,完全依賴 RBAC 選單/capability 層擋非 admin 使用者看到入口。若日後要素比照寫入類頁面(如專案規劃頁)補角色檢查,需另評估是否與「RBAC 已经擋」的既有假設衝突。

4. 狀態機與前置條件

本頁無輪次 / 階段狀態機(不屬於稽核輪次流程),僅有 agent 自身的兩組獨立狀態:

條件 效果 出處
remote_agents.status active:正常參與心跳與資料面呼叫;revoked:心跳 403 FILE_AGENT_REVOKED、資料面呼叫一律拒絕 agent_enrollment_service.py:185-186
last_seen_at 距今 < heartbeat_interval_sec × 3 判定 online=true(在線狀態欄綠色);預設 heartbeat_interval_sec=300 秒(環境變數 AGENT_HEARTBEAT_INTERVAL_SEC)→ 預設離線門檻 900 秒(15 分) common/util/agent_auth/settings.py:29,44-46app/remote_agent/service/agent_enrollment_service.py:36-44is_online
remote_agent_enroll_tokens.enabled 僅一組 enabled=true(per-tenant);產生新 token 或撤銷時,既有全部 enabled 列先設 false(替換語意) app/remote_agent/service/agent_enroll_token_service.py:48-51,68-72
編輯 Dialog:currentData.uid 存在 base_url 輸入框 disabled(唯讀)——新增時可填,編輯既有列時鎖住 RemoteAgentManage.vue:559(FE 慣例,非 BE 強制;BE RemoteAgentUpdateRequest.base_url 技術上仍可送)
儲存前偵測不可連線 base_url 探測不通時彈確認框,使用者可選擇仍要儲存 RemoteAgentManage.vue:159-185onSaveClick

5. UI 設計

📸 實際畫面截圖待補(一律亮色模式拍攝):以 playwright 對 STG 擷取,預計補以下三張到 assets/img/: ① 整頁(Token 卡片 + Agent 清單,含各狀態欄位)remote-agent-overview.png ② 新增 / 編輯 Agent 對話框(含測試連線按鈕)remote-agent-edit-dialog.png ③ Token 明文顯示對話框(示意用,實際截圖需以測試假資料遮蔽敏感值)remote-agent-token-reveal.png

版面骨架與區塊職責(單欄:Token 管理卡片+Agent 清單卡片,無左側樹、無 Tab;彩色區塊可點跳本頁小節錨點,API 僅標代表性):

頁首:檔案 Agent 管理(單欄,無左樹/無 Tab;Header「新增」「測試連線」) 跳至 §2 Use Case(UC-RA-01 產生註冊 Token 並提供 agent 自我註冊) Card 1:註冊 Token 管理(每租戶一組 active,installer 安裝時填入) 產生 / 重新產生(明文僅顯示一次)・撤銷(停用本租戶全部 active token) GET /agents/enroll-token ・ POST /agents/enroll-token ・ POST …/enroll-token/revoke → §2 UC-RA-01 跳至 §2 Use Case(UC-RA-02 對帳/UC-RA-03 撤銷·復用/UC-RA-04 手動新增·編輯) Card 2:Agent 清單 DataTable(本租戶全部 agent,含停用) 名稱/位址/類型/在線狀態/健康檢查/註冊狀態/設備指紋/版本/啟用開關 agent-a1b2 ・ https://… ・ file_storage ・ 🟢online ・ ✓ ・ active ・ ●啟用 | 對帳 撤銷 編輯 刪除 agent-c3d4 ・ https://… ・ file_storage ・ ⚪offline ・ ✗ ・ revoked ・ ○停用 | 對帳 復用 編輯 刪除 逐列動作:對帳(SHA-256 完整性比對)/撤銷·復用設備/啟用開關(單欄位 PUT) GET /remote-agents/all ・ POST /remote-agents/{uid}/reconcile POST …/{uid}/revoke ・ POST …/{uid}/unrevoke ・ PUT /remote-agents/{uid} → §2 UC-RA-02/03/04 跳至 §2 Use Case(UC-RA-04 手動新增/編輯 Agent,含測試連線與存前探測) 新增 / 編輯 Agent Dialog 名稱/位址(編輯時唯讀)/類型/啟用+測試連線 POST /remote-agents ・ PUT /remote-agents/{uid} POST /remote-agents/health-check(存前探測) Token 明文 Dialog(產生後彈出) token + 雲端 endpoint,各自可複製 明文僅顯示一次,關閉後無法再顯示 (隨 UC-RA-01 產生流程) 彩色=可點跳本頁小節錨點(框內標錨點+代表性 API)|灰=本頁描述|虛線框=對話框條件層 API 僅列代表性 2~3 條,完整規格見 §6(含 agent 控制面 /agents/register、/agents/heartbeat)
檔案 Agent 管理頁版面 wireframe:Card1 註冊 Token 管理、Card2 Agent 清單 DataTable(含逐列動作)、新增/編輯 Dialog、Token 明文 Dialog

狀態呈現:清單載入時 DataTable loading 遮罩;逐列動作(對帳 / 撤銷 / 復用)各自獨立 loading(reconcilingUid / revokingUid),不阻塞整頁;健康檢查欄位 statusMap 顯示 checking…online/offline 三態;無 socket,操作後皆重打對應 GET 重新整理(getAgents() / getEnrollToken())。

6. API 規格

Envelope:成功 {"status": true, "data": …}、失敗 {"status": false, "error_code": "…", "msg": "…"}common/util/response_util.py:return_response)。

6.1 總清單(本頁涉及的全部 endpoint)

分類 Method + Path 呼叫者 說明 完整規格
Agent 清單 GET /remote-agents UI(storage-config 下拉用,僅回 uid+name) 本頁不直接用;同 blueprint 收錄供對照 GAI-SD-02
Agent 清單 GET /remote-agents/all UI(本頁) 本租戶全部 agent(含停用),管理頁列表資料來源 §6.2
Agent CRUD POST /remote-agents UI(本頁) 新增 agent §6.2
Agent CRUD GET /remote-agents/{uid} UI 取單筆(本頁未直接呼叫,供他頁 / 除錯用) GAI-SD-02
Agent CRUD PUT /remote-agents/{uid} UI(本頁) 更新 agent(含啟用開關單欄位更新) §6.2
Agent CRUD DELETE /remote-agents/{uid} UI(本頁) 刪除 agent GAI-SD-02
健康檢查 POST /remote-agents/health-check UI(本頁) 雲端 server-to-server 打 agent /health(body {base_url} §6.3
對帳 POST /remote-agents/{uid}/reconcile UI(本頁) 逐檔向 agent 要 SHA-256,比對雲端錨點 §6.3
撤銷 / 復用 POST /remote-agents/{uid}/revoke UI(本頁) status=revoked §6.4
撤銷 / 復用 POST /remote-agents/{uid}/unrevoke UI(本頁) status=active §6.4
重新綁定(dormant) POST /remote-agents/{uid}/rebind UI(endpoint 存在,但 FE 按鈕已移除,僅供特殊維運手動呼叫) 手動覆寫 device_fingerprint(v3 決策 B 後,換硬體改自動更新,不需人工 rebind) §12 坑 2
註冊 Token GET /agents/enroll-token UI(本頁) 取本租戶目前 token 摘要(不含明文) §6.5
註冊 Token POST /agents/enroll-token UI(本頁) 產生 / 重新產生 token(明文僅此次回傳) §6.5
註冊 Token POST /agents/enroll-token/revoke UI(本頁) 停用本租戶全部 enabled token §6.5
控制面(agent 呼叫) POST /agents/register Agent 端(不經 UI,agent 主動撥出,bootstrap 用 token + TLS) agent 自我註冊:驗 token → upsert registry → 可簽憑證回傳 §6.6
控制面(agent 呼叫) POST /agents/heartbeat Agent 端(不經 UI,穩態走 mTLS) agent 心跳:更新 last_seen_at;revoked 拒絕;找不到列回 404(agent 據此自動重新註冊) §6.6

/agents/register/agents/heartbeat控制面端點——呼叫方是客戶端 agent 軟體本身,不是本頁瀏覽器操作;列在此處只為完整說明 registry 資料如何被寫入(本頁看到的清單資料源頭)。這兩支路由 不掛 @jwt_required(),認證機制分別是「hash 比對註冊 token」與「網路層 mTLS client 憑證」。

6.2 Agent CRUD

[GET] /remote-agents/all

無 query 參數。Response data[](每項一個 agent):

欄位 型別 說明
uid string agent 識別碼(v3 決策 B 後即為身分主鍵 agent_uid
name string 顯示名稱(自我註冊時預設取 hardware_info.hostname,否則 agent-{device_uuid前12碼}
base_url string agent 對外位址(雲端打 /blob/health 用)
agent_type string 目前僅 file_storagepublic.system_menus group=AGENT_TYPE 種子值)
enabled bool 是否列入 storage-config 下拉可選
device_fingerprint string|null SHA-256 設備指紋;null = 尚未有 agent 用此列自我註冊過
status string active / revoked
last_seen_at datetime|null 最後心跳時間;null = 從未回報過
agent_version string|null agent 回報版本
hardware_info object|null agent 回報硬體細節(JSONB passthrough,稽核用)
online bool 非 DB 欄位,服務層依 last_seen_at 動態推算後附加
created_at / updated_at datetime 審計欄

出處:RemoteAgentDetailResponseapi/remote_agent/serializers/remote_agent.py:10-24);組裝邏輯 app/remote_agent/service/remote_agent_service.py:70-77list_all_agents,含 online 附加)。

[POST] /remote-agents

Request:

欄位 型別 必填 說明
name string 顯示名稱;空字串觸發 FILE_AGENT_400001
base_url string agent 位址;空字串觸發 FILE_AGENT_400002
agent_type string 預設 file_storage
enabled bool 預設 true

Response:同 §6.2 GET 單筆形狀。出處:RemoteAgentCreateRequest(同檔 :27-31)、create_agentremote_agent_service.py:115-132)。

[PUT] /remote-agents/{uid}

Request(全部選填,None 欄位不更新——部分更新語意,非 full-replace):

欄位 型別 必填 說明
name / base_url / agent_type string 省略即不動該欄位
enabled bool 單獨送 {enabled: false} 即可只切換啟用狀態(清單開關即此用法)

不存在的 uid → 404 FILE_AGENT_NOT_FOUND。出處:RemoteAgentUpdateRequest(同檔 :34-38)、update_agentremote_agent_service.py:134-147)。

6.3 健康檢查 / 對帳

[POST] /remote-agents/health-check

Request:

{
  "base_url": "string"
}

Response:

{
  "reachable": true,
  "status_code": 0,  // 選填
  "detail": "string"  // 選填
}

——任何連線 / 逾時錯誤都吞成 reachable=false,不丟 500(健康檢查本來就會遇到不通的 agent)。full 模式下對 https:// 位址走 mTLS SSLContext;否則走裸 HTTP GET {base_url}/health。逾時 5 秒。出處:remote_agent_service.py:32-54

[POST] /remote-agents/{uid}/reconcile

無 body。Response:

欄位 型別 說明
total int 該 agent base_url 下雲端已知的檔案筆數
matched int SHA-256 與雲端錨點相符筆數
mismatched array {uid, file_name}[]——SHA-256 不符,疑似靜態竄改
missing array {uid, file_name}[]——agent 回 404,物件遺失
error array {uid, reason}[]——連線 / 認證失敗或無錨點(no_anchor

mismatched/missing/error 任一 > 0 時 FE 以錯誤樣式呈現(不算成功)。出處:remote_agent_service.py:154-215reconcile / _agent_sha256_get)。

6.4 撤銷 / 復用

[POST] /remote-agents/{uid}/revoke ・ /unrevoke

無 body。Response:同 §6.2 GET 單筆形狀(status 欄位切換為 revoked / active)。不存在 → 404。出處:remote_agent_service.py:83-97revoke_agent / unrevoke_agent)。

6.5 註冊 Token 管理

[GET] /agents/enroll-token

無 query。Response:{exists: bool, label?: string, created_at?: "YYYY-MM-DD HH:MM:SS"}——不回明文exists=false 時僅有該欄位。出處:agent_enroll_token_service.py:32-43get_current;response 為 service 直接組的 dict,非獨立 marshmallow schema)。

[POST] /agents/enroll-token

Request:

{
  "label": "string"  // 選填,預設 "default"
}

Response:

{
  "token": "string",
  "endpoint": "string"
}

——明文僅此次回傳,DB 只存 sha256;產生新 token 前會先把本租戶既有全部 enabled token 設為停用(替換語意,非疊加)。出處:EnrollTokenGenerateRequest(serializer :63-64)、generateagent_enroll_token_service.py:45-62)。

[POST] /agents/enroll-token/revoke

無 body。Response:{revoked: int}(本次停用的 token 數)。出處:revoke(同檔 :64-73)。

6.6 控制面(agent 呼叫,非 UI)

[POST] /agents/register

Request:

欄位 型別 必填 說明
token string 註冊 token 明文(BE 內部 hash 後比對)
device_uuid string 設備指紋(sha256(product_uuid+machine_id+MAC)
agent_uid string agent 持久化的既有身分(re-auth 時帶回;首次為空)
base_url string agent 對外位址
hardware_info object 硬體細節(JSON,稽核用)
agent_version string agent 版本
csr string 憑證簽署請求(full 模式雲端 CA 簽回 server 憑證)

Response:

欄位 型別 說明
agent_uid string 伺服器發的身分主鍵(v3 決策 B);agent 持久化此值供 re-auth 帶回
certificate string 選填;full 模式雲端 CA 依 csr 簽回的 agent server 憑證
ca_certificate string 選填;full 模式雲端 CA 根憑證
jwt_public_key string 選填;full 模式驗證短效 JWT 用的公鑰
heartbeat_interval_sec int 心跳間隔(秒),agent 據此排程心跳

身分解析(v3 決策 B):優先 by agent_uid(同 tenant)→ 找不到退 by (tenant_id, device_fingerprint) 去重 → 都無才新建;指紋變動自動更新同一列並記稽核EventCode.FILE_AGENT_FINGERPRINT_CHANGED),不冒新列、不需人工 rebind。出處:AgentRegisterRequest(serializer :42-54)、registeragent_enrollment_service.py:79-165)。

[POST] /agents/heartbeat

Request:

{
  "agent_uid": "string",  // 選填
  "device_uuid": "string",
  "status": "string"  // 選填
}

Response:

{
  "status": "ok"
}

找不到對應列(平台已刪除)→ 404 FILE_AGENT_NOT_FOUND(agent 據此清本地狀態並自動重新註冊);status=revoked → 403 FILE_AGENT_REVOKED。出處:AgentHeartbeatRequest(serializer :57-60)、heartbeatagent_enrollment_service.py:167-199)。

7. 前端檔案地圖(compliance-manager-fe/)

檔案 角色
src/views/remote-agent/RemoteAgentManage.vue 唯一檢視檔(617 行;無子元件,Token 卡片 + Agent DataTable + 新增/編輯 Dialog + Token 顯示 Dialog 全在此)
src/config/router/index.js:302-315 route remote-agent-manage,path /system/remote-agent-managemeta 無 capability 欄位,可見性由 RBAC 選單資料決定
src/config/api/api.js:330-334 REMOTE_AGENTS / REMOTE_AGENTS_ALL / REMOTE_AGENTS_HEALTH_CHECK / AGENT_ENROLL_TOKEN 常數
src/service/BaseService.js 本頁未使用專屬 service 檔,直接用 BaseService 泛型 get/post/put/deleteAPI.*
src/components/common/LoadingState.vue Token 卡片載入中的 inline spinner
src/config/locales/i18n/{zh-tw,en}/remote-agent.json 頁面 i18n,namespace lang.remote_agent_manage.*(67 行,含 token 明文警語、撤銷/復用/對帳等文案)
src/config/locales/i18n/{zh-tw,en}/menu.json 選單顯示名稱「Agent 管理」(remote-agent-manage key)

8. 後端檔案地圖(本頁核心鏈路)

鏈路 Route App Service 底層
Agent CRUD / 清單 api/remote_agent/routes/remote_agent_route.py::RemoteAgentManageRoute(GET all)/ RemoteAgentsRoute(POST)/ RemoteAgentDetailRoute(GET/PUT/DELETE) app/remote_agent/service/remote_agent_service.py::RemoteAgentServicelist_all_agents / create_agent / update_agent / delete_agent,皆 @transaction domain/remote_agent/service/remote_agent_domain_service.py::RemoteAgentDomainServiceinfra/remote_agent/repository/remote_agent_repo_impl.py::RemoteAgentRepoImpl(繼承 BaseRepositoryImpl
健康檢查 RemoteAgentHealthCheckRoute RemoteAgentService.check_health@transaction,明確標註不寫 DB) 直接 httpx 呼叫 agent /health(依 AGENT_AUTH_MODE 決定是否走 mTLS SSLContext)
對帳 RemoteAgentReconcileRoute RemoteAgentService.reconcile infra/remote_agent/repository/remote_agent_recon_query.py::RemoteAgentReconQuery(查該 base_url 下雲端檔案錨點)+ 逐檔 httpx 打 agent /blob/*/sha256
撤銷 / 復用 / 重新綁定(dormant) RemoteAgentRevokeRoute / RemoteAgentUnrevokeRoute / RemoteAgentRebindRoute RemoteAgentService.revoke_agent / unrevoke_agent / rebind_agentrebind_agent docstring 明寫 DORMANT,僅供特殊維運手動呼叫) 同 CRUD 底層
註冊 Token 管理 EnrollTokenRoute(GET/POST)/ EnrollTokenRevokeRoute app/remote_agent/service/agent_enroll_token_service.py::AgentEnrollTokenService domain/remote_agent/service/remote_agent_enroll_token_domain_service.pyinfra/remote_agent/repository/remote_agent_enroll_token_repo_impl.py
agent 控制面(register/heartbeat,非本頁 UI 觸發) AgentRegisterRoute / AgentHeartbeatRoute app/remote_agent/service/agent_enrollment_service.py::AgentEnrollmentServiceregister / heartbeat,皆 @transactionis_online 為模組層函式) RemoteAgentDomainService + RemoteAgentEnrollTokenDomainService;憑證簽發用 common/util/agent_auth/ca.py、JWT 用 common/util/agent_auth/jwt_util.py

DDD 提醒:本模組層級守法規範——route 不碰 DB、app service 全數 @transaction(除 check_health 明確標註唯讀不寫)、domain service 不依賴 infra、repo 繼承 BaseRepositoryImpl唯一偏離常態的是「寫入 API 無角色檢查」——這是本模組與本群其餘管理頁(雲端整合 / 儲存設定)一致的既有設計(見 §3 說明),不是本頁獨有的疏漏,新增類似「系統管理層 registry CRUD」可參考此模式,但務必在 spec/PR 中明確標註是刻意選擇。

9. DB

9.1 資料表總清單(本頁讀寫的全部表)

讀/寫 說明 欄位詳述
compliance.remote_agents FR-039 客戶端 agent registry(per-tenant,以 agent_type 區分型別;無 FK,tenant_id/org_unit_id 皆為 soft ref by RLS) §9.3
compliance.remote_agent_enroll_tokens per-tenant 註冊 token(存 sha256 hash,不存明文;理論上表保留多組供未來輪替,目前 service 邏輯維持恆一組 enabled) §9.3

本頁無其他表讀寫——不同於群內其餘頁面涉及 Drive / 分類 run 等表,本頁純粹圍繞這兩張 FR-039 專屬表。與 儲存設定 頁的關聯只在於「該頁的 STORAGE_CONFIG JSONB 內以 base_url 字串引用本頁管理的 agent」,不是 DB 層 FK。

9.2 ER 圖(本頁讀寫範圍)

agent_er agents compliance.remote_agents uid (identity=agent_uid)|name|base_url agent_type (file_storage)|enabled device_fingerprint (可更新屬性)|status (active/revoked) last_seen_at|agent_version|hardware_info (jsonb) tenant_id|org_unit_id tokens compliance.remote_agent_enroll_tokens uid|tenant_id|token_hash (sha256) label|enabled tokens->agents 驗證後 upsert (register 用) tenant 租戶(tenant_id) soft ref,無 FK tenant->agents tenant_id(RLS 分流) tenant->tokens tenant_id(RLS 分流, 每 tenant 僅 1 組 enabled) storage STORAGE_CONFIG (system_config,storage_type=remote_agent) 引用 agent base_url storage->agents 依 base_url 選路 (soft ref,非 FK)
Agent 管理 ER:remote_agents / remote_agent_enroll_tokens 皆 per-tenant soft-scope,無 FK;register 時憑 token 驗證後 upsert agent 列;storage-config 以 base_url 字串軟引用

9.3 核心表欄位(取自 DEV DB dump,含 DB comment)

compliance.remote_agents — 客戶端檔案 Agent registry

DB comment:「FR-039 客戶端 agent registry(remote agent endpoint 清單,per-tenant;以 agent_type 區分型別)」。無 FKtenant_id/org_unit_id 為 soft ref,靠 RLS policy 隔離,非 CASCADE 關聯)。索引沿用改名前的舊名(file_agents_pkey / file_agents_uid_key,2026-06-19 由 file_agents 正名 remote_agents 時 RENAME 而非重建)。

欄位 型別 說明
uid varchar(50) 對外識別碼;v3 決策 B 後即為身分主鍵 agent_uid(register/heartbeat 皆優先以此定位列)
name varchar(255) 顯示名稱;自我註冊時預設取 hardware_info.hostnameagent-{device_uuid前12碼}
base_url varchar(500) agent 對外位址;full 模式下受憑證 SAN 綁定,編輯時 FE 鎖唯讀
agent_type varchar(50) 預設 file_storage;下拉選項來自 public.system_menus(group=AGENT_TYPE,目前僅一個種子值)
enabled boolean 是否列入 儲存設定 頁下拉可選(GET /remote-agents 僅回 enabled 者)
tenant_id integer 租戶(RLS 分流,soft ref)
org_unit_id integer|null 部門(soft ref,選填)
device_fingerprint varchar(64)|null v3 決策 B:降為可更新屬性(非身分鍵)。FR-039 綁定設備 SHA-256 指紋,NULL=未綁定;換硬體時由 register/heartbeat 自動更新並記稽核(FILE_AGENT_FINGERPRINT_CHANGED),不需人工 rebind
status varchar(20) FR-039 agent 狀態:active / revoked(資安撤銷,心跳與資料面呼叫立即拒絕)
last_seen_at timestamp|null 最後心跳時間;online/offline 由此推算(門檻 = heartbeat_interval_sec × 3,預設 900 秒)
agent_version varchar(50)|null agent 回報版本
hardware_info jsonb|null agent 回報硬體細節(稽核用,非結構化強制欄位)

compliance.remote_agent_enroll_tokens — 註冊 Token

DB comment:「FR-039 per-tenant agent 註冊 token(存 sha256 hash;先一組 active,表保留多組輪替)」。無 FK

欄位 型別 說明
uid varchar(50) 對外識別碼
tenant_id integer 租戶(RLS 分流)
org_unit_id integer|null 選填
token_hash varchar(64) token 明文的 sha256 hex digest;明文永不落 DB,只在產生當下的 API response 出現一次
label varchar(100)|null 備註(預設 "default"
enabled boolean 是否為目前有效 token;產生新 token 或撤銷時,既有全部 enabled 列先設 false(替換語意)

10. 頁面邏輯與資料對應

載入時序onMounted 平行觸發三支 GET(agent_type 選單、agent 清單、token 狀態);agent 清單載入完成後額外逐一觸發 health-check 探測(非同步、不阻塞清單渲染)。

關鍵欄位對應(API ↔︎ 畫面):

畫面元素 FE state API 欄位
在線狀態欄(心跳推算) 直接綁定 data.online(服務層動態附加,非 DB 欄位)+ data.last_seen_at
健康檢查欄(即時探測) statusMap[uid]'checking'/'online'/'offline' 由 FE 自行對每列呼叫 health-check API 填入,與 online 欄位資料來源不同
註冊狀態 Tag 直接綁定 data.statusactive→success Tag/revoked→danger Tag)
設備指紋 Tag shortFp(fp)(截前 12 碼 + ,tooltip 顯示全值) data.device_fingerprint(null → 顯示「未綁定」)
Agent 類型欄 agentTypeLabel(value)(查 agentTypeMenu data.agent_type
Token 卡片狀態 enrollToken.exists GET /agents/enroll-token 回傳 {exists, label, created_at}
Token 明文 Dialog revealedToken.{token, endpoint} POST /agents/enroll-token 回傳(僅此次)
編輯 Dialog base_url 唯讀 !!currentData.uid(有 uid = 編輯既有列) 無對應 API 欄位,純 FE 防呆

儲存流程:新增 / 編輯共用 doSave()——存前先呼叫 health-check 探測,若不可連線則彈確認框詢問是否仍要儲存;儲存成功後 dialogVisible=false + 重新 getAgents()。啟用開關(onToggleEnabled)獨立即時 PUT,失敗時 FE 自行回滾 data.enabled(不重打整份清單)。撤銷 / 復用 / 對帳皆先彈 confirm.require 二次確認才送出。

錯誤對應:BE error envelope → toast 顯示;本頁常見碼:FILE_AGENT_404001(agent 不存在)、FILE_AGENT_400001/400002(新增缺名稱/位址)、FILE_AGENT_401001(register 帶錯 token,非本頁 UI 直接觸發)、FILE_AGENT_403001(heartbeat 打到 revoked 的 agent,非本頁 UI 直接觸發)。健康檢查與對帳皆刻意吞連線例外成 reachable=false / error[] bucket,不冒 500 干擾整頁。

11. 背景行為與外部依賴

類型 內容
通知 無——本頁不觸發、也不消費任何通知
Socket 無 socket / 輪詢;即時 UI 明確延後(design doc:未來走 SocketIO 推播),現階段靠手動「重新整理」或操作後重新 fetch
agent 控制面(背景,非本頁觸發) 已註冊的 agent 每 heartbeat_interval_sec(預設 300 秒)主動打 /agents/heartbeat;本頁清單的「在線狀態」即反映這個背景行為的結果,但本頁本身不主動輪詢 agent
mTLS / 憑證 AGENT_AUTH_MODE=full 時,register 簽發 agent server 憑證(common/util/agent_auth/ca.py)、心跳與資料面走 mTLS + 短效 JWT(jwt_util.py);none 模式(demo/測試)走裸 HTTP,本頁的健康檢查 / 對帳邏輯依此模式二選一
jedi-* 套件 無直接依賴(agent 端 evidence-agent 專案重用 jedi-file-upload,但那是另一個 repo/服務,非本頁鏈路)
系統參數 AGENT_AUTH_MODEnone/full)、AGENT_HEARTBEAT_INTERVAL_SEC(預設 300)、AGENT_CLOUD_ENDPOINT(回填進 token 產生 response 的 endpoint 欄位)——皆環境變數,非資料庫 feature flag

12. 邊界情況與已知坑

  1. 編輯時 base_url 唯讀是 FE 鎖,BE 技術上仍接受RemoteAgentUpdateRequest.base_url schema 層仍允許送值且會被套用;唯讀限制只在 RemoteAgentManage.vue:559:disabled 綁定,若繞過 FE 直接打 API 送 base_url,BE 會照改——但這樣做會讓自我註冊 agent 的 mTLS 因憑證 SAN 與新位址不符而斷線,屬「技術上可行、實務上不該做」的坑
  2. 重新綁定(rebind)功能保留 API、UI 已移除:FR-039 v3 決策 B 後,身分主鍵從 device_fingerprint 改為伺服器發的 agent_uid;換硬體時指紋由 register/heartbeat 自動更新並記稽核(FILE_AGENT_FINGERPRINT_CHANGED),不再需要人工 rebind。POST /remote-agents/{uid}/rebind 端點仍存在(RemoteAgentRebindRoute),rebind_agent service method docstring 明寫「DORMANT」,僅供特殊維運手動覆寫指紋時使用——本頁 UI 不再提供入口RemoteAgentManage.vue:287-288, 524-525 有明確註解說明移除原因。日後維護本頁切勿誤以為缺按鈕是遺漏
  3. 在線狀態(心跳推算)與健康檢查(即時探測)是兩套獨立機制:清單同時顯示兩欄——「在線狀態」由 BE last_seen_at 推算(agent 背景心跳的結果,可能有數分鐘延遲);「健康檢查」是 FE 載入時對每列即時打 /health 探測的結果(statusMap)。兩者資料來源、更新時機都不同,可能短暫不一致(如 agent 剛斷線但心跳門檻還沒到,會顯示「在線」但健康檢查「離線」)——這是設計如此,不是 bug
  4. 寫入 API 全數無角色檢查:本頁與整個 FR-039 registry 模組刻意不在 service 層加角色守門,僅靠 RBAC 選單/capability 擋非 admin 使用者看到入口(見 §3)。若使用者已知 API path,任何登入者理論上都能操作本頁全部功能(新增/刪除/撤銷 agent、產生/撤銷 token)——這是與本群其餘管理頁一致的既有設計,不是本次盤點新發現的缺口,但改動時勿誤以為是可隨意加嚴或放寬的細節
  5. agent_type 下拉目前僅一個選項public.system_menus(group=AGENT_TYPE)目前只種了 file_storage 一個值(scripts/sql/2026-06-19-fr039-agent-type-menu.sql),欄位存在是為未來多種 agent 類型(非檔案儲存用途)預留擴充位,目前實務上等於恆定值
  6. Token 表結構支援多組但邏輯恆維持一組remote_agent_enroll_tokens schema comment 寫「表保留多組輪替」,但 AgentEnrollTokenService.generate() 每次都會先把全部既有 enabled 設 false 才建新的——目前無法有「兩組並存」的使用情境,是刻意的簡化(一租戶一組 active token),非資料模型限制
  7. Demo 安全邊界(誠實揭露,見 FR-039 design.md)AGENT_AUTH_MODE=none 時走裸 HTTP、無 mTLS、指紋為 agent 軟體自報(無法防完全控制機器 + 改 agent 原始碼偽造)——部署一律應使用 full 模式;none 僅供測試環境暫時停用認證
  8. hardware_info 為自由格式 JSONB,無強制 schemaregister payload 的 hardware_infofields.Raw(),FE 顯示與後續稽核僅取用 hostname 這個慣例欄位,其餘內容視 agent 版本而定,欄位存在性不保證

13. 開發與驗證

  • 跑起來:BE python main_socketio.py(port 8000,log 在 log/app.log);FE 在 compliance-manager-fe/ 起 Vite dev server。BE 改 service code 後必須重啟(無 hot reload)
  • 測試帳號:dev 環境租戶 admin 角色帳號(密碼見 .env / 部署文件)
  • 導航路徑:登入 → 系統設定選單「Agent 管理」(remote-agent-manage,path /system/remote-agent-manage
  • 前置資料:無強制前置資料即可開頁(空清單顯示「尚無 Agent」);要驗證心跳 / 對帳等完整流程需真實 agent(evidence-agent 專案)以 full 模式部署並用本頁產生的 token 完成自我註冊
  • E2Ecompliance-manager-test/ repo(Cucumber + Playwright);本頁相關 feature 檔以 remote-agent / agent-manage / 檔案 agent 搜尋
  • 相關文件:FR-039(docs/features/FR-039-2606-distributed-file-agent/design.md 主設計、design-agent-auth.md 認證 v2 設計);身分模型 v3 決策記錄 docs/analysis/2026-06-19-fr039-agent-identity-v3-uid-vs-fingerprint.md;mTLS/httpx 踩雷紀錄見該 FR 目錄下 handoff;儲存後端整體脈絡見 _overview §4儲存設定;DB / API 全量見 GAI-SD-02/03