檔案 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_agents 與
compliance.remote_agent_enroll_tokens,供 儲存設定 頁的
storage_type = remote_agent 選路使用(見 _overview §4)。
主要使用者:租戶 admin(系統管理層)。存取由 RBAC
選單權限(ui_routes /
capabilities,resource_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 一列 |
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(疑似靜態竄改)。
UC-RA-03 撤銷 / 復用設備
| 項目 | 內容 |
|---|---|
| 角色 | 租戶 admin |
| 前置條件 | 該 agent 列已存在(verify_remote_agent_is_exist 404
檢查) |
| 產出 / 後置條件 | status 於 active/revoked
間切換;revoked 後該 agent 心跳(403
FILE_AGENT_REVOKED)與資料面呼叫立即被拒 |
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)。新增可先按「測試連線」單獨探測,或存前自動探測。
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.py、agent_enroll_token_service.py |
| agent 自我註冊 / 心跳 | 不適用(非使用者操作) | 不走 user JWT:POST /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-46、app/remote_agent/service/agent_enrollment_service.py:36-44(is_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-185(onSaveClick) |
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 僅標代表性):
狀態呈現:清單載入時 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_storage(public.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 | 審計欄 |
出處:RemoteAgentDetailResponse(api/remote_agent/serializers/remote_agent.py:10-24);組裝邏輯
app/remote_agent/service/remote_agent_service.py:70-77(list_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_agent(remote_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_agent(remote_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-215(reconcile
/ _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-97(revoke_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-43(get_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)、generate(agent_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)、register(agent_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)、heartbeat(agent_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-manage;meta 無
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/delete 打 API.* |
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::RemoteAgentService(list_all_agents
/ create_agent / update_agent /
delete_agent,皆 @transaction) |
domain/remote_agent/service/remote_agent_domain_service.py::RemoteAgentDomainService
→
infra/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_agent(rebind_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.py
→
infra/remote_agent/repository/remote_agent_enroll_token_repo_impl.py |
| agent 控制面(register/heartbeat,非本頁 UI 觸發) | AgentRegisterRoute /
AgentHeartbeatRoute |
app/remote_agent/service/agent_enrollment_service.py::AgentEnrollmentService(register
/ heartbeat,皆
@transaction;is_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_CONFIGJSONB 內以base_url字串引用本頁管理的 agent」,不是 DB 層 FK。
9.2 ER 圖(本頁讀寫範圍)
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 區分型別)」。無
FK(tenant_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.hostname 或
agent-{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.status(active→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_MODE(none/full)、AGENT_HEARTBEAT_INTERVAL_SEC(預設
300)、AGENT_CLOUD_ENDPOINT(回填進 token 產生 response 的
endpoint 欄位)——皆環境變數,非資料庫 feature flag |
12. 邊界情況與已知坑
- 編輯時
base_url唯讀是 FE 鎖,BE 技術上仍接受:RemoteAgentUpdateRequest.base_urlschema 層仍允許送值且會被套用;唯讀限制只在RemoteAgentManage.vue:559的:disabled綁定,若繞過 FE 直接打 API 送base_url,BE 會照改——但這樣做會讓自我註冊 agent 的 mTLS 因憑證 SAN 與新位址不符而斷線,屬「技術上可行、實務上不該做」的坑 - 重新綁定(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_agentservice method docstring 明寫「DORMANT」,僅供特殊維運手動覆寫指紋時使用——本頁 UI 不再提供入口,RemoteAgentManage.vue:287-288, 524-525有明確註解說明移除原因。日後維護本頁切勿誤以為缺按鈕是遺漏 - 在線狀態(心跳推算)與健康檢查(即時探測)是兩套獨立機制:清單同時顯示兩欄——「在線狀態」由
BE
last_seen_at推算(agent 背景心跳的結果,可能有數分鐘延遲);「健康檢查」是 FE 載入時對每列即時打/health探測的結果(statusMap)。兩者資料來源、更新時機都不同,可能短暫不一致(如 agent 剛斷線但心跳門檻還沒到,會顯示「在線」但健康檢查「離線」)——這是設計如此,不是 bug - 寫入 API 全數無角色檢查:本頁與整個 FR-039 registry 模組刻意不在 service 層加角色守門,僅靠 RBAC 選單/capability 擋非 admin 使用者看到入口(見 §3)。若使用者已知 API path,任何登入者理論上都能操作本頁全部功能(新增/刪除/撤銷 agent、產生/撤銷 token)——這是與本群其餘管理頁一致的既有設計,不是本次盤點新發現的缺口,但改動時勿誤以為是可隨意加嚴或放寬的細節
agent_type下拉目前僅一個選項:public.system_menus(group=AGENT_TYPE)目前只種了file_storage一個值(scripts/sql/2026-06-19-fr039-agent-type-menu.sql),欄位存在是為未來多種 agent 類型(非檔案儲存用途)預留擴充位,目前實務上等於恆定值- Token
表結構支援多組但邏輯恆維持一組:
remote_agent_enroll_tokensschema comment 寫「表保留多組輪替」,但AgentEnrollTokenService.generate()每次都會先把全部既有 enabled 設 false 才建新的——目前無法有「兩組並存」的使用情境,是刻意的簡化(一租戶一組 active token),非資料模型限制 - Demo 安全邊界(誠實揭露,見 FR-039
design.md):
AGENT_AUTH_MODE=none時走裸 HTTP、無 mTLS、指紋為 agent 軟體自報(無法防完全控制機器 + 改 agent 原始碼偽造)——部署一律應使用full模式;none僅供測試環境暫時停用認證 hardware_info為自由格式 JSONB,無強制 schema:registerpayload 的hardware_info是fields.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 完成自我註冊 - E2E:
compliance-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