設備管理(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-02 刪除設備(單筆 / 批次)¶
| 項目 | 內容 |
|---|---|
| 角色 | 持 device.delete(同租戶任何設備都可刪,不看建立者) |
| 前置條件 | 已登入;該設備列存在於清單 |
| 產出 / 後置條件 | public.devices 刪除一列;FK ON DELETE CASCADE 連帶刪除該設備在任務關聯表的列(見 §9.1) |
UC-DM-03 匯入設備清冊¶
| 項目 | 內容 |
|---|---|
| 角色 | 持 device.create;勾「更新既有設備」另需 device.update |
| 前置條件 | 已登入;頁面可見 |
| 產出 / 後置條件 | public.devices 新增或更新 N 列;稽核事件 DEVICE_IMPORTED(6160)記誰、幾筆、什麼格式 |
流程:
- 下載範本(
GET /devices/import-template):Excel 單一工作表「設備匯入」,第一列為欄名(欄名與欄序來自套件IMPORT_COLUMNS,範本、解析、匯出三方共用同一份),「設備類型」欄掛下拉(選項來自system_menusgroup=DEVICE_TYPE,顯示中文)。 - 選檔驗證(
POST /devices/import/verify,multipartfile)——不寫 DB: - 檔案層:不是 xlsx/csv →
DEVICE_400001;表頭與範本不符 →DEVICE_400002;沒有資料列 →DEVICE_400003;超過 2,000 列 →DEVICE_400004;超過 5MB →DEVICE_400007。 - 逐列:名稱、IP 必填(
DEVICE_400010);IP 必須是單一 IPv4/IPv6 位址、不收網段、含%的 IPv6 scope 一律拒(DEVICE_400011);設備類型不在選項內(DEVICE_400012,中文顯示名或英文代碼皆收,存檔一律存代碼);超長(DEVICE_400013)。 - 重複:同檔案內 IP 重複,第二列起標
duplicate(DEVICE_409001);IP 已在清冊內標duplicate(DEVICE_409002)並帶existing_uid。IP 比對用正規化形式(::0001與::1視為同一台)。 - 確認匯入(
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):
狀態呈現: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 圖(本頁讀寫範圍)¶
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. 邊界情況與已知坑¶
- 刪除看能力點,不看建立者:FE 與 BE 是同一把尺——持
device.delete就能刪同租戶任何設備。曾經只在 FE 藏刪除鈕、BE 不擋的形狀已不存在;若要恢復「只有建立者能刪」,要 BE 與 FE 一起改,不能只改畫面 - PUT/DELETE 對不存在
uid回結構化 404:jedi-asset error_code enum,update回 None /delete回 False →raise NotFound(DEVICE_NOT_FOUND)(DEVICE_404001),呼叫方收到明確 404 - PUT 是部分更新(不可清空欄位),與專案規劃頁 job PUT 的 full-replace 語意不同:
BaseRepositoryImpl.update()逐欄位if value is not None才 setattr,意味著要清空某個選填欄位(如清空description)在目前 FE 實作下做不到——onSaveClick送出的 payload 若某欄位是空字串會送空字串(可清空),但若欄位是undefined/null則會被整個跳過、保留舊值。跨頁維護時注意:本頁與 專案規劃頁 的 job PUT 語意不同,勿混淆 device_type已改標準化下拉(2026-07-09 FR-047 六項#6):原為InputText自由輸入(同義詞如「筆電」vs「Laptop」並存、難統計 / 篩選)。已改Dropdown,選項來自system_menusgroup=DEVICE_TYPE(比照 remote-agent 的AGENT_TYPE,seed2026-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 待部署- 設備「停用」UI 已補上(2026-07-09 FR-047 六項#2):原本
status欄位(1 正常 / 0 停用)DB 有值、Response schema 有回傳,但 Dialog 無切換 UI、payload 也不帶 → 恆為 DB default1。已在新增 / 編輯 Dialog 加 status 開關(正常 / 停用)、payload 一律帶status(避免 jedi-device 無條件寫入時預設1覆寫已停用)、列表加狀態 Tag。jedi-device 早已全鏈支援 status,無套件改動 - IP 兩端都驗,且只收單一位址:FE
ipAddressvalidator 與 BEDeviceRequest._validate_single_ip/匯入的is_valid_ip同一規則。網段不收(網段到授權掃描網段登記);含%的 IPv6 scope 不收——清冊是掃描目標來源,下游組連線指令時%後面那段就是注入口。BE 開始驗之前存進的錯誤 IP,編輯時會被擋,要先改正才能存 - 刪除觸發 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 合計引用數,不存在回 404DEVICE_404001);FE 刪除確認框在引用數 >0 時顯示警示,查詢失敗降級原文案不擋刪 -
重複 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)、非重複保留。已 DROPidx_devices_tenant、留idx_devices_tenant_id。migration2026-07-08-drop-dup-index-devices-tenant.sql -
清冊改動會影響 agent 准連清單:刪設備、改 IP,最慢一個心跳週期(預設 5 分鐘)後 agent 就拒連舊位址;已存的任務設定若引用被刪的設備,下次存檔或按開始執行時被擋(
DETECTION_TOOLS_400025)。只有內網位址的設備會被選為掃描目標與下發給 agent - 匯入上限: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搜尋