手冊首頁 / 系統管理 / 設備管理

設備管理(Device Manage)

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

事實基準:2026-07-05 從 FE / BE code 掃出(DDD 層級全數位於外部套件 jedi-device,主專案僅 route + app service 薄殼);API schema 逐條對照 marshmallow serializer 與 service 組裝碼

變更紀錄:2026-07-09 FR-047 已知坑修復 —— device_typesystem_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 等其他頁面以下拉選單引用。本頁本身是單一 DataTable + 新增/編輯 Dialog 的標準管理頁,無左側樹、無 Tab、無狀態機

主要使用者:系統管理層使用者(頁面可見性由 RBAC 選單控管,見 §3);刪除動作額外由 FE 限縮為「僅設備建立者本人」。

角色速覽

角色 一句話
系統管理層使用者 本頁唯一操作者——選單可見性由 RBAC 控管(見 _overview §2),BE 端點層僅檢查 JWT 登入

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 刪除設備(單筆) 逐列「刪除」按鈕,僅設備建立者本人可見;二次確認 逐列「功能」欄 UC-DM-02
7 批次刪除 勾選多列後批次刪除;非本人建立的列會被自動濾除並提示 表格 checkbox + Header「批次刪除」 UC-DM-02
8 建立者/修改者顯示 顯示 nickname(非 login_name),BE app service 層批次 enrich 清單「建立者」「最後修改者」欄 §6.2

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

2. Use Case

角色速覽

角色 一句話
系統管理層使用者 本頁唯一操作者——選單可見性由 RBAC 控管(見 _overview §2),BE 端點層僅檢查 JWT 登入

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

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

項目 內容
角色 系統管理層使用者(頁面可見性由 RBAC 選單控管)
前置條件 已登入;device-manage 選單可見
產出 / 後置條件 public.devices 新增一列或更新既有列;清單重新整理
device_uc_crud start open 開「新增/編輯」Dialog start->open input 填寫名稱/IP(必填)+ 作業系統/版本/製造商/ 生產日期/主機名稱/類型/ 用途/描述(選填) open->input valid FE 前端驗證: 名稱非空?IP 格式正確? input->valid fix 紅字提示,聚焦錯誤欄位 valid->fix submit 送出 POST/PUT /device[/uid] valid->submit fix->input jwt BE 檢查:JWT 已登入? (無角色/租戶檢查) submit->jwt deny 401 未登入 jwt->deny ok upsert devices 列 回傳最新 DeviceResponse (enrich created/updated_user_name) jwt->ok
UC-DM-01 流程:FE 驗證名稱/IP 必填格式 → 送出 → BE 僅檢查 JWT、無角色或租戶檢查 → upsert

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

項目 內容
角色 設備建立者本人(FE 判定);⚠️ BE 端點對任意登入者皆開放(見 §3)
前置條件 已登入;該設備列存在於清單
產出 / 後置條件 public.devices 刪除一列;FK ON DELETE CASCADE 連帶刪除該設備在任務關聯表的列(見 §9.1)
device_uc_delete start listrow 清單逐列 / 勾選批次 start->listrow fecheck FE 判定: created_user === 目前登入者? listrow->fecheck hidden 「刪除」按鈕不顯示 (批次勾選會被自動濾除+提示) fecheck->hidden confirm 彈二次確認框 fecheck->confirm del DELETE /device/{uid} (可 Promise.allSettled 批次逐一送) confirm->del benone BE 檢查: 僅 JWT?(⚠️ 無擁有者/角色檢查, 繞過 FE 任何登入者皆可刪任一設備) del->benone cascade FK CASCADE: job_execution_devices 等 關聯任務設備列被連帶刪除 benone->cascade 找到,通過 notfound uid 不存在 → data:false(非 404,success 包裝) benone->notfound uid 找不到 ok 刪除成功 重新整理清單 cascade->ok
UC-DM-02 流程:FE 依 created_user 判定是否顯示刪除鈕 → 二次確認 → BE 僅 JWT 無擁有者檢查 → FK CASCADE 連帶刪除任務設備關聯 → uid 找不到回傳 data:false(非 404)

3. 權限矩陣

頁面可見性依 RBAC 三層模型基調,見 _overview §2。本頁端點層的實際檢查點:

操作 FE 判定 BE 強制 BE 檢查位置
選單 / 頁面可見性 RBAC 選單資料決定(route meta 無 capability 欄位,純靠 ui_routes/capabilities 回傳結果) 不適用(選單層問題,非本頁端點) jedi-auth web-menu(見 _overview §2.1
讀清單 / 讀單筆 / menu 無額外前端判斷 ⚠️ @jwt_required(),無角色/租戶檢查 api/device/routes/device_route.pyDevicesRoute / DeviceRoute.get / DeviceMenuRoute
新增 / 編輯 表單前端驗證(名稱/IP 必填 + IP 格式) ⚠️ @jwt_required(),無角色/租戶檢查;DeviceRequest 僅做型別/必填驗證,不做業務規則 api/device/routes/device_route.py::DeviceRoute.post/putapp/device/service/device_app_service.py:60-69
刪除(單筆/批次) isDeletableDevicedata.created_user === 目前登入者 login_nameDeviceManage.vue:36),僅擁有者可見刪除鈕;批次勾選時非擁有者列自動濾除(DeviceManage.vue:39-47 ⚠️ @jwt_required(),完全無擁有者或角色檢查——delete_device 直接依 uid 刪除,任何登入者知道 uid 即可刪除任一設備(含他人建立的) api/device/routes/device_route.py::DeviceRoute.deleteapp/device/service/device_app_service.py:71-73、jedi-device device_domain_service.py:57-58delete_by_uid 無條件刪除)

⚠️ 已知不一致(維護時注意):刪除操作的「僅建立者可刪」完全是 FE 單方面的顯示層限制,BE 對任何已登入使用者一視同仁——這比本群其餘管理頁(如 remote-agent-manage)「刻意不做角色檢查」更進一步:連 FE 想表達的「擁有者限定」語意在 BE 都沒有對應實作。詳見 §12 坑 1。

4. 狀態機與前置條件

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

條件 效果 出處
name / ip 為空 FE 表單驗證擋下,紅字提示,不送出 API DeviceManage.vue:72-77rules
ip 不符合 IP 格式 FE ipAddress validator 擋下 同上
created_user !== 目前登入者 該列「刪除」按鈕不渲染(FE only) DeviceManage.vue:36, 332
BE 寫入 無任何前置條件檢查(無角色、無租戶歸屬驗證、無「是否被任務引用中」檢查) app/device/service/device_app_service.py 全檔

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:全域關鍵字搜尋(9 欄 ILIKE)+ 重置查詢 + 批次刪除(checkbox)+ 新增設備 單欄版面:無左側樹、無 Tab;查詢狀態依 {使用者uid}::device-manage 持久化 開啟:設備管理 §6 API 規格 設備清單 DataTable:15 欄(名稱/IP/OS/製造商/用途/建立者…),lazy 分頁+多欄排序 POST /devices (分頁清單,_fill_user_names enrich nickname) GET /devices/menu (供他頁 device picker 下拉) 逐列「刪除」按鈕僅 FE 判斷建立者本人可見(BE 無擁有者檢查) → #6-api-規格(點我開啟) 開啟:UC-DM-01 新增/編輯設備 新增/編輯設備 Dialog 名稱*/IP* + 8 個選填欄(OS/製造商/用途…) POST /device ・ PUT /device/{uid}(部分更新,非 full-replace) IP 格式僅 FE 檢查,BE 不驗證 → UC-DM-01(點我開啟) 開啟:UC-DM-02 刪除設備(單筆/批次) 刪除設備(單筆/批次)二次確認 非建立者列於批次勾選時自動濾除並提示 DELETE /device/{uid}(Promise.allSettled 逐一送出) ⚠️ BE 無擁有者檢查;FK CASCADE 連帶清任務設備關聯 → UC-DM-02(點我開啟) 彩色=可點跳轉本頁 §6/UC 的區塊|灰=本頁結構描述|虛線=Dialog/條件顯示層 API 僅列代表性 2~3 條,完整規格見 §6
設備管理頁版面骨架:Header 搜尋/批次刪除/新增、DataTable 15 欄(連 §6 API)、新增/編輯 Dialog(連 UC-DM-01)、刪除單筆/批次確認(連 UC-DM-02)

狀態呈現:DataTable loadingdeviceLoading)遮罩載入中;批次刪除單獨 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} 刪除設備(無擁有者/角色檢查,見 §3) §6.4

以下 §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

出處:api/device/serializers/device_serializers.py:69-70DevicePageQueryResponse)+ :48-67DeviceResponse);_fill_user_namesapp/device/service/device_app_service.py:20-44)。

6.3 CRUD 類

[POST] /device

Request(DeviceRequest):

欄位 型別 必填 說明
name string 設備名稱
ip string IP 位址(schema 層僅檢查非空字串,無格式驗證——IP 格式檢查只在 FE ipAddress validator)
description / os / os_version / hostname / device_type / purpose / manufacture string 選填文字欄位
manufacture_at date YYYY-MM-DD
status int 預設 DB 層 1

Response:同 §6.2 單筆形狀。出處:api/device/serializers/device_serializers.py:21-32DeviceRequest)、add_device(jedi-device app/service/device_service.py:53-61)。

[PUT] /device/{uid}

Request:欄位同 POST(DeviceRequest 共用同一 schema,實務上省略欄位視同傳 None)。更新語意(jedi-device base_repository_impl.updateupdate_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 → jedi-device update() 回傳 NoneDeviceResponse().dump(None) 序列化為全空物件(非 404,見 §12 坑 2)。出處:app/device/service/device_app_service.py:65-69、jedi-device infra/repository/device_repo_impl.py:62-79

[DELETE] /device/{uid}

無 body。Response data:bool(BooleanResponse)——true=刪除成功;uid 不存在時回傳 data: falseHTTP 200,非 404(見 §12 坑 2)。刪除無任何擁有者或角色檢查(見 §3)。出處:app/common/schema/common_schema.py:4-5BooleanResponse)、delete_device(jedi-device app/service/device_service.py:74-77domain/service/device_domain_service.py:57-58BaseRepositoryImpl.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)下拉使用。出處:api/device/serializers/device_serializers.py:39-46DeviceMenuResponse)、get_device_menu(jedi-device infra/repository/device_repo_impl.py:26-38load_only 限縮欄位)。

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

檔案 角色
src/views/device/DeviceManage.vue 唯一檢視檔(約 270 行;無子元件,DataTable + 新增/編輯 Dialog 全在此)
src/config/router/index.js:594-607 route device-manage,path /device/device-managemeta 無 capability 欄位,可見性由 RBAC 選單資料決定
src/config/api/api.js:320-323 DEVICES / DEVICES_MENU / DEVICE 常數
src/service/BaseService.js 本頁未使用專屬 service 檔,直接用 BaseService 泛型 get/post/put/deleteAPI.*
src/composables/useTableQuery.js 共用分頁/排序/搜尋 state(per-scope + per-user 持久化)
src/stores/queryStore.js useTableQuery 背後的 Pinia store(resetCurrent() 供「重置查詢」使用)
src/utils/userUtil.js useUserUtil()userInfo(含 login_name),供刪除鈕擁有者判定使用
src/config/locales/i18n/{zh-tw,en}/device.json 頁面 i18n,namespace lang.device_manage.*

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

鏈路 Route App Service 底層
清單 api/device/routes/device_route.py::DevicesRoute app/device/service/device_app_service.py::get_devices_and_pager@transaction jedi-device DeviceService.get_devices_and_pagerDeviceDomainServiceDeviceRepoImpl(繼承 BaseRepositoryImpl
menu 同檔 DeviceMenuRoute get_device_menu jedi-device DeviceRepoImpl.get_menuload_only 限縮欄位)
CRUD 同檔 DeviceRoute(GET/POST/PUT/DELETE) get_device / add_device / update_device / delete_device(皆 @transaction 同上鏈路;add_device/update_device 額外呼叫 _fill_user_names 批次 enrich nickname

DDD 提醒:本模組是「主專案薄殼 + 外部套件扛全部 domain/infra」的典型範例——domain/device/infra/device/ 在主專案不存在,全數在 jedi-device 套件內(~/Projects/Jedicogy/module/jedi-python-package/jedi-device/)。主專案僅有 route(HTTP 邊界)+ app service(一層 @transaction 包裝 + _fill_user_names 這個主專案專屬的 enrich 邏輯)。改設備核心行為(新增欄位、驗證規則、刪除邏輯)要去 jedi-device 套件改,改動需遵守 CLAUDE.md「外部套件異動規範」——套件屬於設備 domain 的欄位/行為可直接在套件層改。route 層無 DB 存取(守規範);app service 層無直接 ORM import(守規範)。

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_mappingcompliance.job_execution_devicescompliance.project_job_execution_device_mapping——皆為任務指派設備時寫入的關聯表(見 專案規劃頁 §9)。

9.2 ER 圖(本頁讀寫範圍)

device_er devices public.devices (本頁唯一寫入表) id / uid / name / ip / os / device_type / ... tenants public.tenants devices->tenants tenant_id (RESTRICT) org_units public.org_units devices->org_units org_unit_id (RESTRICT) users public.users (讀,nickname enrich) devices->users created_user/updated_user (login_name,soft ref) jed compliance.job_execution_devices jed->devices device_id (CASCADE) jedm compliance.job_execution_device_mapping jedm->devices device_id (CASCADE) pjedm compliance.project_job_execution_device_mapping pjedm->devices device_id (CASCADE)
設備管理 ER:devices 為本頁唯一寫入表;虛線 = 被其他模組表以 CASCADE FK 引用(非本頁寫入,僅供了解刪除影響面)

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

public.devices — 設備主檔

DB comment:「設備表:儲存設備資訊,包含設備名稱、作業系統、IP、製造商等資訊」。有 FKtenant_id / org_unit_idON 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.idON DELETE RESTRICT),有索引(3 個獨立 index,疑歷史殘留)
org_unit_id integer|null org_units.idON 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.valuepager.valuemeta 回填)。使用者 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_useruserInfo.value.login_name 比對
分頁/排序/搜尋 pager / sort / filterTableuseTableQuery 提供) request payload.{pager,sort,filters}
新增/編輯 Dialog currentData{...item} 淺拷貝或 {} PUT/POST payload 9 個欄位(onSaveClick 手動組 payload不含 status——即使 DB 有此欄位,本頁表單未提供編輯入口)

儲存流程onSaveClickv$.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 codecommon/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-device(設備 domain/infra 核心,含 entity/repo/mapper)、jedi-authUserDomainService 供 nickname enrich)、jedi-commonBaseRepositoryImpl/transaction/分頁 spec)
系統參數 無 feature flag
跨頁依賴 GET /devices/menu 被其他頁(如專案規劃頁任務卡設備 picker)引用;本頁刪除設備不檢查是否正被任務引用中,會觸發 FK CASCADE(見 §9.1)

12. 邊界情況與已知坑

  1. 刪除「僅擁有者」完全是 FE 顯示層限制,BE 對任何登入者一視同仁isDeletableDevice 只決定刪除鈕是否渲染/批次選取是否被濾除,DELETE /device/{uid} 端點本身不比對 created_user,任何已登入使用者只要知道 uid(如透過瀏覽器開發工具或直接呼叫 API)即可刪除他人建立的設備。這比本群其餘管理頁常見的「無角色檢查」更進一步——連「擁有者限定」這個 FE 想表達的語意,BE 端完全沒有對應實作。修正需額外評估是否要在 add_device/delete_device 補 tenant/owner 檢查,屬另開 bug/改動 session 的範圍,本頁 spec 僅記錄不修
  2. PUT/DELETE 對不存在 uid 已回結構化 404(2026-07-08 FR-047 ECODE,jedi-device):原本 update() 找不到列回 None→序列化成「全空欄位」200、deletedata:false(皆非明確錯誤訊號)。jedi-device 已新建 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:BE DeviceRequest.ipfields.String(required=True),不做格式檢查;ipAddress validator 純粹是 @vuelidate/validators 前端擋,繞過 FE 直接打 API 可寫入任意字串到 ip 欄位
  7. 刪除觸發 FK CASCADE,已補「將解除 N 筆關聯」確認警示(2026-07-08 FR-047 R-2b)job_execution_devices 等三張表對 devices.idON 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_tenantidx_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

13. 開發與驗證

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