跳轉到

設備管理(Device Manage)

功能群:系統管理|三層 RBAC 權限模型與角色權限基調先讀 功能群總覽

事實基準:2026-07-05 從 FE / BE code 掃出;2026-10-05 依 jedi-asset(設備 domain/route 全在套件,主專案只提供匯入範本與 DI 接線)重核 §1/§3/§4/§6/§12

變更紀錄: - 2026-10-05(FR-129):設備清冊成為檢測線的授權目標來源——逐台型掃描目標只能從清冊挑、agent 心跳下發的准連清單也取清冊內網 IP。補 Excel/CSV 匯入(兩段式:驗證→確認)與匯出;端點改以 device.* 能力點守門、新增時租戶與部門取自登入身分;後端驗 IP 為單一位址(不收網段、不收 %scope);刪除改看 device.delete 能力點,不再限建立者本人 - 2026-07-09 FR-047 已知坑修復 —— device_type 改 system_menus 標準下拉(六項#6)、補設備「停用」status UI(六項#2);PUT / DELETE 不存在 uid 回結構化 404(ECODE,jedi-device);刪除補引用計數警示(R-2b);清理重複 tenant 索引(SCHEMA,DEV+STG,POC 待部署)

1. 功能描述

設備管理頁是租戶層級的設備主檔 CRUD 介面:維護 public.devices 這張設備清冊(名稱、IP、作業系統、製造商、用途等),供稽核流程中「任務指派設備」(如專案規劃頁的任務卡)與 程序書 / device picker 等其他頁面以下拉選單引用。

清冊同時是檢測線的授權目標來源:CINC Auditor/GCB/OpenSCAP 的掃描目標、Nmap 的執行主機、檢測工具測試連線,都只能從本清冊挑設備(不收手打 IP);agent 心跳下發的准連清單也取本清冊中內網位址的 IP。網段型目標另由授權掃描網段管。因此改清冊的權限(device.*)應與執行掃描的權限分開給,見 scan-zone-manage §3。本頁本身是單一 DataTable + 新增/編輯 Dialog 的標準管理頁,無左側樹、無 Tab、無狀態機。

主要使用者:持 device.* 能力點的使用者(讀/新增/修改/刪除各一顆,見 §3)。

角色速覽:

角色 一句話
持 device.* 能力點的使用者 本頁唯一操作者——選單可見性由 RBAC 控管(見 _overview §2),每個端點各守一顆 device.read/create/update/delete

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

# 功能 說明 位置 詳述
1 設備清單(分頁/排序/搜尋) 15 欄 DataTable,lazy 分頁 + 多欄排序;per-user 查詢狀態持久化(useTableQuery scope={userUid}::device-manage) 主表格 §5
2 全域關鍵字搜尋 單一搜尋框套用到 9 個欄位(OR 條件 ILIKE) Header 搜尋框 §6.3
3 重置查詢 清空搜尋/排序/分頁回到第一頁 Header「重置查詢」按鈕 §10
4 新增設備 開 Dialog 填寫名稱/IP(必填)+ 8 個選填欄位 Header「新增」→ Dialog UC-DM-01
5 編輯設備 同一 Dialog,帶入既有值後 PUT 逐列名稱連結 / 「編輯」按鈕 UC-DM-01
6 刪除設備(單筆) 逐列「刪除」按鈕,持 device.delete 才顯示;二次確認,被任務引用時確認框警示將解除幾筆關聯 逐列「功能」欄 UC-DM-02
7 批次刪除 勾選多列後批次刪除;勾選欄只對 device.delete 持有者顯示 表格 checkbox + Header「批次刪除」 UC-DM-02
8 建立者/修改者顯示 顯示 nickname(非 login_name),BE app service 層批次 enrich 清單「建立者」「最後修改者」欄 §6.2
9 匯入(Excel/CSV) 下載範本 → 選檔驗證(逐列列出可匯入/錯誤/重複與原因,不寫入)→ 確認匯入;可勾「IP 已存在的改為更新既有設備」 Header「匯入」→ Dialog(持 device.create 才顯示) UC-DM-03
10 匯出(Excel/CSV) 套用目前的搜尋與排序、不分頁全部匯出;匯出檔可直接匯回 Header「匯出」選單(持 device.read 才顯示) §6.5

UC(§2)展開 1(新增/編輯 CRUD)、2(單筆/批次刪除)、3(匯入)這三條核心操作路徑;其餘(搜尋/排序/分頁/重置)屬標準 DataTable 行為,不另展開。

2. Use Case

角色速覽:

角色 一句話
持 device.* 能力點的使用者 本頁唯一操作者——選單可見性由 RBAC 控管(見 _overview §2),每個端點各守一顆 device.read/create/update/delete

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

UC-DM-01 新增/編輯設備

項目 內容
角色 系統管理層使用者(頁面可見性由 RBAC 選單控管)
前置條件 已登入;device-manage 選單可見
產出 / 後置條件 public.devices 新增一列或更新既有列;清單重新整理

UC-DM-01 流程:FE 驗證名稱/IP 必填格式 → 送出 → BE 僅檢查 JWT、無角色或租戶檢查 → upsert

UC-DM-02 刪除設備(單筆 / 批次)

項目 內容
角色 持 device.delete(同租戶任何設備都可刪,不看建立者)
前置條件 已登入;該設備列存在於清單
產出 / 後置條件 public.devices 刪除一列;FK ON DELETE CASCADE 連帶刪除該設備在任務關聯表的列(見 §9.1)

UC-DM-02 流程:FE 依 created_user 判定是否顯示刪除鈕 → 二次確認 → BE 僅 JWT 無擁有者檢查 → FK CASCADE 連帶刪除任務設備關聯 → uid 找不到回傳 data:false(非 404)

UC-DM-03 匯入設備清冊

項目 內容
角色 持 device.create;勾「更新既有設備」另需 device.update
前置條件 已登入;頁面可見
產出 / 後置條件 public.devices 新增或更新 N 列;稽核事件 DEVICE_IMPORTED(6160)記誰、幾筆、什麼格式

流程:

  1. 下載範本(GET /devices/import-template):Excel 單一工作表「設備匯入」,第一列為欄名(欄名與欄序來自套件 IMPORT_COLUMNS,範本、解析、匯出三方共用同一份),「設備類型」欄掛下拉(選項來自 system_menus group=DEVICE_TYPE,顯示中文)。
  2. 選檔驗證(POST /devices/import/verify,multipart file)——不寫 DB:
  3. 檔案層:不是 xlsx/csv → DEVICE_400001;表頭與範本不符 → DEVICE_400002;沒有資料列 → DEVICE_400003;超過 2,000 列 → DEVICE_400004;超過 5MB → DEVICE_400007。
  4. 逐列:名稱、IP 必填(DEVICE_400010);IP 必須是單一 IPv4/IPv6 位址、不收網段、含 % 的 IPv6 scope 一律拒(DEVICE_400011);設備類型不在選項內(DEVICE_400012,中文顯示名或英文代碼皆收,存檔一律存代碼);超長(DEVICE_400013)。
  5. 重複:同檔案內 IP 重複,第二列起標 duplicate(DEVICE_409001);IP 已在清冊內標 duplicate(DEVICE_409002)並帶 existing_uid。IP 比對用正規化形式(::0001 與 ::1 視為同一台)。
  6. 確認匯入(POST /devices/import/confirm):後端逐列重新驗證,不採用前端送回的結果;只寫 ok 列;duplicate 預設跳過,update_existing=true 時覆寫清冊既有那台(不動其啟用狀態)。回傳新增/更新/跳過/錯誤筆數。

3. 權限矩陣

頁面可見性依 RBAC 三層模型基調,見 _overview §2。端點全部掛在 jedi-asset 套件 routing(主專案不另掛 route),每個端點以 capability_required 守一顆能力點:

操作 FE 判定 BE 強制 BE 檢查位置(jedi-asset)
選單 / 頁面可見性 RBAC 選單資料決定 — web-menu
讀清單 / 讀單筆 / menu / 查引用數 — device.read api/routes/device_route.py(DeviceListRoute / DeviceDetailRoute.get / DeviceMenuRoute / DeviceReferenceRoute)
新增 hasCap('device.create') device.create;request 帶 tenant_id/org_unit_id 直接 400(schema 未宣告、marshmallow 預設拒收未知欄位),租戶與部門一律取自登入身分 DeviceCreateRoute.post
編輯 — device.update DeviceDetailRoute.put
刪除(單筆/批次) hasCap('device.delete'):刪除鈕與勾選欄只對持有者顯示 device.delete;同租戶任何設備都可刪,不看建立者 DeviceDetailRoute.delete
下載範本 / 匯入驗證 / 確認匯入 hasCap('device.create'):「匯入」鈕 device.create;確認時 update_existing=true 另驗 device.update api/routes/device_import_route.py
匯出 hasCap('device.read'):「匯出」選單 device.read DeviceExportRoute.post
租戶隔離 — public.devices RLS(insert/update/delete 各有 policy);修改、刪除他租戶的 uid 在 RLS 下即查無 → 404 DEVICE_404001 DB

清冊是檢測線的授權目標來源(見 §1)。持 device.create/device.update 的人等於能決定 agent 可以連到哪幾台主機——建議與執行掃描的權限分開給不同帳號,見 scan-zone-manage §3。

4. 狀態機與前置條件

本頁無狀態機、無輪次/階段概念(設備是靜態主檔資源,不屬於稽核輪次生命週期)。僅有欄位層級的前置條件:

條件 效果 出處
name / ip 為空 FE 表單驗證擋下,紅字提示,不送出 API DeviceManage.vue:72-77(rules)
ip 不符合 IP 格式 FE ipAddress validator 擋下;BE schema 再驗(單一 IPv4/IPv6、不收網段、不收 %scope),不合格 400 FE 同上;BE jedi-asset api/serializers/device.py::_validate_single_ip
未持 device.delete 刪除鈕、勾選欄不渲染;BE 403 DeviceManage.vue(canDelete)、§3
刪除仍被任務引用的設備 不擋;確認框警示將解除 N 筆關聯(GET /device/<uid>/references) §12 坑 7

5. UI 設計

📸 實際畫面截圖待補(一律亮色模式拍攝):以 playwright 對 STG 擷取,預計補以下三張到 assets/img/: ① 整頁(搜尋框 + DataTable 15 欄 + 批次刪除/新增按鈕)device-overview.png ② 新增 / 編輯設備 Dialog(10 個欄位)device-edit-dialog.png ③ 批次刪除確認框(含非擁有者提示樣式)device-batch-delete-confirm.png

版面骨架(單欄:Header 搜尋/動作 + DataTable + Dialog,無左側樹、無 Tab):

設備管理頁版面骨架:Header 搜尋/批次刪除/新增、DataTable 15 欄(連 §6 API)、新增/編輯 Dialog(連 UC-DM-01)、刪除單筆/批次確認(連 UC-DM-02)

狀態呈現:DataTable loading(deviceLoading)遮罩載入中;批次刪除單獨 batchDeleting loading 狀態,不阻塞整頁;長文字欄位(用途/描述/OS 等)以 text-overflow-ellipsis 截斷 + tooltip 顯示全文;無 socket,操作後皆重打 getDevices() 重新整理。查詢狀態(搜尋關鍵字/排序/分頁)以 useTableQuery 依 {使用者uid}::device-manage scope 持久化於 queryStore,換使用者或重新登入會自動重新查詢(DeviceManage.vue:86-90)。

6. API 規格

Envelope:成功 {"status": true, "data": …}(部分回應直接用頂層 {code, data} 由 return_response 統一組裝,見 common/util/response_util.py);失敗為 4xx/5xx + {"error_code": "…", "msg": "…"} 或 marshmallow 驗證錯誤 400。

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

分類 Method + Path 說明 完整規格
清單 POST /devices 分頁清單(本頁主要資料來源,payload={pager, sort, filters}) §6.2
清單 GET /devices/menu 輕量選單(FR-032 F device picker 用;本頁不直接呼叫,供他頁下拉引用) §6.4
CRUD GET /device/{uid} 取單筆(本頁未直接呼叫,供除錯 / 他頁引用) GAI-SD-02
CRUD POST /device 新增設備 §6.3
CRUD PUT /device/{uid} 更新設備(PUT 語意但實為選填多欄位更新,見 §12 坑 3) §6.3
CRUD DELETE /device/{uid} 刪除設備(device.delete,見 §3) §6.3
CRUD GET /device/{uid}/references 該設備被任務引用的筆數(刪除確認框用) §12 坑 7
匯入匯出 GET /devices/import-template 下載 Excel 範本 UC-DM-03
匯入匯出 POST /devices/import/verify 驗證匯入檔,回逐列結果(不寫入) §6.5
匯入匯出 POST /devices/import/confirm 重驗後寫入 ok 列 §6.5
匯入匯出 POST /devices/export 匯出 xlsx/csv §6.5

以下 §6.2~6.4 為核心 endpoint 完整規格(欄位逐條對照 serializer / service 組裝碼,出處標在各段末);標 GAI-SD-02 者屬常規 CRUD,細節見 API 規格書或 Swagger /swagger-ui/。

6.2 清單類

[POST] /devices

Request payload:

欄位 型別 必填 說明
pager object 否 {page, page_size, with_total}
sort array 否 [{field, order}],多欄排序
filters object 否 DeviceQueryEntity 子集欄位(見下);FE 全域搜尋時對 9 個欄位各自帶同一關鍵字(見 §6.3 說明)

Response data[](每項一個設備,欄位同 §6.3 Response):

欄位 型別 說明
id / uid int / string 內部 PK / 對外識別碼
name string 設備名稱
description string|null 描述
os / os_version string|null 作業系統 / 版本
hostname string|null 主機名稱
ip string|null IP 位址(敏感欄位,本文件不列實際值)
device_type string 設備類型(自由輸入文字,非 code table)
purpose string 用途
manufacture string 製造商
manufacture_at date 生產日期(YYYY-MM-DD)
status int 設備狀態(DB comment:1 正常 / 0 異常;本頁 UI 未提供切換入口,恆為預設值 1)
created_user / updated_user string login_name(審計欄)
created_user_name / updated_user_name string|null nickname(app service 層批次查 UserDomainService enrich,非 DB 直接欄位)
created_at / updated_at datetime YYYY-MM-DD HH:MM:SS

出處:jedi-asset api/serializers/device.py(DevicePageQueryResponse/DeviceResponse);_fill_user_names(jedi-asset app/service/device_service.py)。

6.3 CRUD 類

[POST] /device

Request(DeviceRequest):

欄位 型別 必填 說明
name string 是 設備名稱
ip string 是 單一 IPv4/IPv6 位址,最長 50;前後空白先去掉再驗與存;網段、含 % 的 IPv6 scope 一律 400
description / os / os_version / hostname / device_type / purpose / manufacture string 否 選填文字欄位
manufacture_at date 否 YYYY-MM-DD
status int 否 預設 DB 層 1

Response:同 §6.2 單筆形狀。出處:jedi-asset api/serializers/device.py::DeviceRequest、app/service/device_service.py::add_device。

[PUT] /device/{uid}

Request:欄位同 POST(DeviceRequest 共用同一 schema,實務上省略欄位視同傳 None)。更新語意(jedi-common BaseRepositoryImpl.update,update_exclude_fields 固定排除 id/uid/created_at/updated_at/created_user):value is not None 才 setattr——換言之送 null 或省略某欄位皆會跳過更新、保留舊值;沒有 full-replace 或清空欄位的方式(與專案規劃頁 job PUT 的 full-replace 語意不同,見 §12 坑 3)。

不存在或屬他租戶的 uid → 404 DEVICE_404001(見 §12 坑 2)。出處:jedi-asset app/service/device_service.py::update_device。

[DELETE] /device/{uid}

無 body。Response data:bool——true=刪除成功;uid 不存在或屬他租戶 → 404 DEVICE_404001(見 §12 坑 2)。守 device.delete,不看建立者(見 §3)。出處:delete_device(jedi-asset app/service/device_service.py::delete_device → BaseRepositoryImpl.delete_by_uid)。

6.4 選單 / 輔助類

[GET] /devices/menu

無 query。Response data[](陣列,每項一個設備選項):

欄位 型別 說明
id string 對外識別碼(=uid;FR-032 F 命名慣例:id 對外走 uid,對稱 information-system menu)
pk int 內部 PK(=內部 id;FR-032 F 命名慣例:pk 走內部 PK)
name string 設備名稱
ip string IP 位址
hostname string 主機名稱
os string 作業系統

本頁不直接呼叫,供其他頁面(如專案規劃頁任務卡的設備 picker)下拉使用。出處:jedi-asset api/serializers/device.py::DeviceMenuResponse、get_device_menu(jedi-asset repo,load_only 限縮欄位)。

6.5 匯入 / 匯出

[POST] /devices/import/verify

multipart:file(xlsx 或 UTF-8 csv)。Response data:

欄位 型別 說明
rows[] array 逐列:row_no、範本九欄的值、status(ok/error/duplicate)、error({欄位: error_code})、existing_uid(IP 已在清冊時)
summary object {total, ok, error, duplicate}

檔案層錯誤直接 400(碼見 UC-DM-03)。

[POST] /devices/import/confirm

欄位 型別 必填 說明
rows array 是 verify 回傳的列(後端只取欄位值、重新驗證,不信 status)
update_existing bool 否 預設 false;true 時 duplicate 列改為更新清冊既有那台,另驗 device.update

Response data:{created, updated, skipped, failed}。寫稽核事件 DEVICE_IMPORTED(6160)。

[POST] /devices/export

欄位 型別 必填 說明
format string 是 xlsx/csv(其餘 DEVICE_400006)
filters / sort object / array 否 與 POST /devices 清單同形,不分頁

回傳附件 devices.xlsx/devices.csv(csv 開頭帶 BOM,Excel 開中文不亂碼)。欄位=範本九欄+「建立者」「建立時間」(後兩欄匯入時忽略)。寫稽核事件 DEVICE_EXPORTED(6161)。用 POST 是因為搜尋條件是巢狀結構,放 query string 不好帶。

出處:jedi-asset api/routes/device_import_route.py、app/service/device_import_service.py(IMPORT_COLUMNS、MAX_IMPORT_ROWS=2000、MAX_FILE_SIZE=5MB、is_valid_ip)、app/service/device_export_service.py;範本產生在主專案 app/asset/service/device_import_template_app_service.py。

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

檔案 角色
src/views/device/DeviceManage.vue 主檢視:DataTable + 新增/編輯 Dialog、能力點判定(canRead/canCreate/canDelete)
src/views/device/DeviceImportDialog.vue 匯入 Dialog:下載範本、選檔驗證、逐列結果篩選、確認匯入
src/views/device/DeviceExportMenu.vue 匯出選單(Excel/CSV)
src/views/device/deviceFileUtil.js 下載附件共用小工具
src/config/router/index.js:594-607 route device-manage,path /device/device-manage;meta 無 capability 欄位,可見性由 RBAC 選單資料決定
src/config/api/api.js:320-323 DEVICES / DEVICES_MENU / DEVICE 常數
src/service/BaseService.js 本頁未使用專屬 service 檔,直接用 BaseService 泛型 get/post/put/delete 打 API.*
src/composables/useTableQuery.js 共用分頁/排序/搜尋 state(per-scope + per-user 持久化)
src/stores/queryStore.js useTableQuery 背後的 Pinia store(resetCurrent() 供「重置查詢」使用)
src/utils/userUtil.js useUserUtil() 的 hasCap(),供各按鈕能力點判定
src/config/locales/i18n/{zh-tw,en}/device.json 頁面 i18n,namespace lang.device_manage.*

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

設備的 route/app service/domain/infra 全在 jedi-asset 套件(~/Projects/Jedicogy/module/jedi-python-package/jedi-asset/jedi_asset/),由套件 routing 直接掛到 Flask,主專案沒有設備 route。

鏈路 Route(jedi-asset api/routes/) App Service(jedi-asset app/service/) 備註
清單 / menu / 單筆 / CRUD / 引用數 device_route.py(DeviceListRoute/DeviceMenuRoute/DeviceCreateRoute/DeviceDetailRoute/DeviceReferenceRoute) device_service.py::DeviceService(皆 @transaction,_fill_user_names 補暱稱) 引用數由宿主注入 jedi_task_platform 的 DeviceReferenceQuery
匯入 / 匯出 device_import_route.py device_import_service.py(欄位契約 IMPORT_COLUMNS、上限、IP 驗證)、device_export_service.py 範本由宿主產:主專案 app/asset/service/device_import_template_app_service.py(只決定下拉與樣式,欄名 import 套件)
宿主接線 — — 主專案 core/plugins/asset.py(reference_counter、device_import_template adapter)、di_containers/asset/asset_containers.py
檢測線取用 — — 主專案 core/plugins/detection.py(DeviceDirectoryAdapter:查設備時明確比對租戶,不只靠 RLS)、core/plugins/remote_agent.py(AllowedTargetsProvider:心跳下發清冊內網 IP)

改設備核心行為(新增欄位、驗證規則、刪除邏輯)要去 jedi-asset 套件改,改動遵守 CLAUDE.md「外部套件異動規範」。

9. DB

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

表 讀/寫 說明 欄位詳述
public.devices 寫 設備主檔(本頁唯一直接讀寫的表) §9.3
public.users 讀 _fill_user_names 批次查 login_name → nickname(僅 app service 層查詢,非 JOIN) GAI-SD-03

本頁無其他表直接讀寫。但 devices.id 被以下表以 ON DELETE CASCADE 引用(刪除設備會連帶刪除這些關聯列,本頁 UI 對此無警示):compliance.job_execution_device_mapping、compliance.job_execution_devices、compliance.project_job_execution_device_mapping——皆為任務指派設備時寫入的關聯表(見 專案規劃頁 §9)。

9.2 ER 圖(本頁讀寫範圍)

設備管理 ER:devices 為本頁唯一寫入表;虛線 = 被其他模組表以 CASCADE FK 引用(非本頁寫入,僅供了解刪除影響面)

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

public.devices — 設備主檔

DB comment:「設備表:儲存設備資訊,包含設備名稱、作業系統、IP、製造商等資訊」。有 FK(tenant_id / org_unit_id 皆 ON DELETE RESTRICT 參照 tenants / org_units,非 soft ref)。

欄位 型別 說明
id integer 內部 PK(devices_id_seq)
uid varchar(50) 對外識別碼(UUID),devices_uid_key UNIQUE
name varchar(255) 設備名稱,NOT NULL
description text|null 設備描述
os varchar(255)|null 作業系統
os_version varchar(100)|null 作業系統版本
hostname varchar(255)|null 主機名稱
ip varchar(50) IP 位址,NOT NULL;本文件僅描述欄位存在,不列任何實際值
device_type varchar(255)|null 設備類型(自由文字,非 code table 約束)
manufacture varchar(255)|null 製造商
manufacture_at date|null 製造日期
status integer 設備狀態;DB comment:1 正常,0 異常;NOT NULL default 1
purpose text|null 設備用途
tenant_id integer|null → tenants.id(ON DELETE RESTRICT),有索引(3 個獨立 index,疑歷史殘留)
org_unit_id integer|null → org_units.id(ON DELETE RESTRICT)
created_user / updated_user varchar(50)|null login_name(審計欄,無 comment)
created_at / updated_at timestamp NOT NULL,無 comment

idx_devices_tenant / idx_devices_tenant_id 為兩個獨立但等價的 index(皆單欄 tenant_id),加上複合 idx_devices_tenant_org——schema dump 顯示三個 tenant 相關 index 共存,疑似歷史演進留下的重複,未實測是否影響寫入效能,本文件僅如實記錄不擅自判斷是否該清理。

10. 頁面邏輯與資料對應

載入時序:onMounted 呼叫 getDevices() → buildPayload() 組 {pager, sort, filters} → POST /devices → 填入 devices.value 與 pager.value(meta 回填)。使用者 uid 變化(如延遲取得或重新登入)會觸發 watch 重新查詢(DeviceManage.vue:86-90)。

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

畫面元素 FE state API 欄位
DataTable 各欄 devices (array) data[] 逐項對應(name/ip/os/os_version/device_type/manufacture/manufacture_at/hostname/purpose/description)
建立者/修改者欄 直接綁定 created_user_name / updated_user_name(nickname,非 created_user/updated_user 的 login_name)
刪除鈕顯示與否 isDeletableDevice(data) computed data.created_user 與 userInfo.value.login_name 比對
分頁/排序/搜尋 pager / sort / filterTable(useTableQuery 提供) request payload.{pager,sort,filters}
新增/編輯 Dialog currentData({...item} 淺拷貝或 {}) PUT/POST payload 9 個欄位(onSaveClick 手動組 payload,不含 status——即使 DB 有此欄位,本頁表單未提供編輯入口)

儲存流程:onSaveClick 先 v$.value.$touch() 觸發前端驗證,失敗則聚焦第一個錯誤欄位並中止;通過後依 currentData.value.uid 是否存在分流 create()(POST)或 update()(PUT),成功後皆重新 getDevices() + 關閉 Dialog(onCancelClick)。刪除/批次刪除皆先 confirm.require 二次確認;批次刪除以 Promise.allSettled 逐一送出 DELETE,統計成功/失敗數各自 toast。

錯誤對應:BE error envelope → toast 顯示(showError(e))。本頁常見狀況:marshmallow 驗證失敗(缺 name/ip)→ 400;本頁無設備專屬 error code(common/code/ 各檔搜尋 DEVICE_ 前綴僅有 GRC_DEVICE_NOT_FOUND/GRC_404035,但那是 FR-032 F device picker 的 ref_id 校驗錯誤碼,非本頁 CRUD 端點使用;本頁 CRUD 失敗多半直接是 marshmallow schema 層 400 或未捕捉例外的 500)。

11. 背景行為與外部依賴

類型 內容
通知 無——本頁不觸發、也不消費任何通知
Socket 無 socket / 輪詢;操作後皆重打 getDevices()
jedi-* 套件 jedi-asset(設備 domain/infra/route/匯入匯出)、jedi-auth(UserDomainService 供 nickname enrich)、jedi-common(BaseRepositoryImpl/transaction/分頁 spec)
系統參數 無 feature flag
跨頁依賴 GET /devices/menu 被其他頁(如專案規劃頁任務卡設備 picker)引用;本頁刪除設備不檢查是否正被任務引用中,會觸發 FK CASCADE(見 §9.1)

12. 邊界情況與已知坑

  1. 刪除看能力點,不看建立者:FE 與 BE 是同一把尺——持 device.delete 就能刪同租戶任何設備。曾經只在 FE 藏刪除鈕、BE 不擋的形狀已不存在;若要恢復「只有建立者能刪」,要 BE 與 FE 一起改,不能只改畫面
  2. PUT/DELETE 對不存在 uid 回結構化 404:jedi-asset error_code enum,update 回 None / delete 回 False → raise NotFound(DEVICE_NOT_FOUND)(DEVICE_404001),呼叫方收到明確 404
  3. PUT 是部分更新(不可清空欄位),與專案規劃頁 job PUT 的 full-replace 語意不同:BaseRepositoryImpl.update() 逐欄位 if value is not None 才 setattr,意味著要清空某個選填欄位(如清空 description)在目前 FE 實作下做不到——onSaveClick 送出的 payload 若某欄位是空字串會送空字串(可清空),但若欄位是 undefined/null 則會被整個跳過、保留舊值。跨頁維護時注意:本頁與 專案規劃頁 的 job PUT 語意不同,勿混淆
  4. device_type 已改標準化下拉(2026-07-09 FR-047 六項#6):原為 InputText 自由輸入(同義詞如「筆電」vs「Laptop」並存、難統計 / 篩選)。已改 Dropdown,選項來自 system_menus group=DEVICE_TYPE(比照 remote-agent 的 AGENT_TYPE,seed 2026-07-09-device-type-menu.sql:server / workstation / laptop / network / storage / security_appliance / mobile / printer / virtual_machine / iot / other + 雙語 i18n),label 走 te() 三層 fallback、舊自由文字資料保留原值。⚠️ seed DEV + STG 已套、POC 待部署
  5. 設備「停用」UI 已補上(2026-07-09 FR-047 六項#2):原本 status 欄位(1 正常 / 0 停用)DB 有值、Response schema 有回傳,但 Dialog 無切換 UI、payload 也不帶 → 恆為 DB default 1。已在新增 / 編輯 Dialog 加 status 開關(正常 / 停用)、payload 一律帶 status(避免 jedi-device 無條件寫入時預設 1 覆寫已停用)、列表加狀態 Tag。jedi-device 早已全鏈支援 status,無套件改動
  6. IP 兩端都驗,且只收單一位址:FE ipAddress validator 與 BE DeviceRequest._validate_single_ip/匯入的 is_valid_ip 同一規則。網段不收(網段到授權掃描網段登記);含 % 的 IPv6 scope 不收——清冊是掃描目標來源,下游組連線指令時 % 後面那段就是注入口。BE 開始驗之前存進的錯誤 IP,編輯時會被擋,要先改正才能存
  7. 刪除觸發 FK CASCADE,已補「將解除 N 筆關聯」確認警示(2026-07-08 FR-047 R-2b):job_execution_devices 等三張表對 devices.id 是 ON DELETE CASCADE,刪除仍被任務引用的設備會清除該任務的設備關聯(刪除行為維持 CASCADE,採「知情同意」而非改軟刪)。已新增端點 GET /device/<uid>/references(三表 raw SQL 合計引用數,不存在回 404 DEVICE_404001);FE 刪除確認框在引用數 >0 時顯示警示,查詢失敗降級原文案不擋刪
  8. 重複 tenant index 已清理(2026-07-08 FR-047 SCHEMA,DEV + STG 已套、POC 待部署):三索引中實為 2 真重複——idx_devices_tenant ≡ idx_devices_tenant_id(皆 btree(tenant_id)),idx_devices_tenant_org 是複合 (tenant_id, org_unit_id)、非重複保留。已 DROP idx_devices_tenant、留 idx_devices_tenant_id。migration 2026-07-08-drop-dup-index-devices-tenant.sql

  9. 清冊改動會影響 agent 准連清單:刪設備、改 IP,最慢一個心跳週期(預設 5 分鐘)後 agent 就拒連舊位址;已存的任務設定若引用被刪的設備,下次存檔或按開始執行時被擋(DETECTION_TOOLS_400025)。只有內網位址的設備會被選為掃描目標與下發給 agent

  10. 匯入上限:5MB、2,000 列;確認匯入時後端重驗每一列,所以 verify 與 confirm 之間清冊被別人改過(例如同 IP 被新增),結果以 confirm 當下為準

13. 開發與驗證

  • 跑起來:BE python main.py(port 8000,log 在 log/app.log);FE 在 compliance-manager-fe/ 起 Vite dev server。BE 改 service code 後必須重啟(無 hot reload);改 jedi-asset 套件本身需依 poetry path dependency 流程(見 CLAUDE.md「外部套件異動規範」)
  • 測試帳號:dev 環境系統管理層角色帳號(密碼見 .env / 部署文件)
  • 導航路徑:登入 → 系統設定選單「設備管理」(device-manage,path /device/device-manage)
  • 前置資料:無強制前置資料即可開頁(空清單直接顯示 DataTable 空狀態)
  • E2E:compliance-manager-test/ repo(Cucumber + Playwright);本頁相關 feature 檔以 device / 設備管理 搜尋
  • 相關文件:本頁核心邏輯在 jedi-asset 套件(~/Projects/Jedicogy/module/jedi-python-package/jedi-asset/),改動需先讀套件內對應檔案;DB / API 全量見 GAI-SD-02/03;FR-032 F(device picker 起源)散見 docs/changelog/ 以 device 搜尋