設備管理(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_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
等其他頁面以下拉選單引用。本頁本身是單一 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 新增一列或更新既有列;清單重新整理 |
UC-DM-02 刪除設備(單筆 / 批次)
| 項目 | 內容 |
|---|---|
| 角色 | 設備建立者本人(FE 判定);⚠️ BE 端點對任意登入者皆開放(見 §3) |
| 前置條件 | 已登入;該設備列存在於清單 |
| 產出 / 後置條件 | public.devices 刪除一列;FK
ON DELETE CASCADE 連帶刪除該設備在任務關聯表的列(見
§9.1) |
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.py(DevicesRoute
/ DeviceRoute.get / DeviceMenuRoute) |
| 新增 / 編輯 | 表單前端驗證(名稱/IP 必填 + IP 格式) | ⚠️ 僅
@jwt_required(),無角色/租戶檢查;DeviceRequest
僅做型別/必填驗證,不做業務規則 |
api/device/routes/device_route.py::DeviceRoute.post/put、app/device/service/device_app_service.py:60-69 |
| 刪除(單筆/批次) | isDeletableDevice:data.created_user === 目前登入者 login_name(DeviceManage.vue:36),僅擁有者可見刪除鈕;批次勾選時非擁有者列自動濾除(DeviceManage.vue:39-47) |
⚠️ 僅
@jwt_required(),完全無擁有者或角色檢查——delete_device
直接依 uid 刪除,任何登入者知道 uid
即可刪除任一設備(含他人建立的) |
api/device/routes/device_route.py::DeviceRoute.delete、app/device/service/device_app_service.py:71-73、jedi-device
device_domain_service.py:57-58(delete_by_uid
無條件刪除) |
⚠️ 已知不一致(維護時注意):刪除操作的「僅建立者可刪」完全是 FE 單方面的顯示層限制,BE 對任何已登入使用者一視同仁——這比本群其餘管理頁(如 remote-agent-manage)「刻意不做角色檢查」更進一步:連 FE 想表達的「擁有者限定」語意在 BE 都沒有對應實作。詳見 §12 坑 1。
4. 狀態機與前置條件
本頁無狀態機、無輪次/階段概念(設備是靜態主檔資源,不屬於稽核輪次生命週期)。僅有欄位層級的前置條件:
| 條件 | 效果 | 出處 |
|---|---|---|
name / ip 為空 |
FE 表單驗證擋下,紅字提示,不送出 API | DeviceManage.vue:72-77(rules) |
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):
狀態呈現: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} |
刪除設備(無擁有者/角色檢查,見 §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-70(DevicePageQueryResponse)+
:48-67(DeviceResponse);_fill_user_names(app/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-32(DeviceRequest)、add_device(jedi-device
app/service/device_service.py:53-61)。
[PUT] /device/{uid}
Request:欄位同 POST(DeviceRequest 共用同一
schema,實務上省略欄位視同傳
None)。更新語意(jedi-device
base_repository_impl.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 → jedi-device update() 回傳
None,DeviceResponse().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: false,HTTP 200,非
404(見 §12 坑
2)。刪除無任何擁有者或角色檢查(見
§3)。出處:app/common/schema/common_schema.py:4-5(BooleanResponse)、delete_device(jedi-device
app/service/device_service.py:74-77 →
domain/service/device_domain_service.py:57-58 →
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)下拉使用。出處:api/device/serializers/device_serializers.py:39-46(DeviceMenuResponse)、get_device_menu(jedi-device
infra/repository/device_repo_impl.py:26-38,load_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-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() 取 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_pager →
DeviceDomainService → DeviceRepoImpl(繼承
BaseRepositoryImpl) |
| menu | 同檔 DeviceMenuRoute |
get_device_menu |
jedi-device
DeviceRepoImpl.get_menu(load_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_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-device(設備 domain/infra
核心,含
entity/repo/mapper)、jedi-auth(UserDomainService
供 nickname
enrich)、jedi-common(BaseRepositoryImpl/transaction/分頁
spec) |
| 系統參數 | 無 feature flag |
| 跨頁依賴 | GET /devices/menu 被其他頁(如專案規劃頁任務卡設備
picker)引用;本頁刪除設備不檢查是否正被任務引用中,會觸發 FK
CASCADE(見 §9.1) |
12. 邊界情況與已知坑
- 刪除「僅擁有者」完全是 FE 顯示層限制,BE
對任何登入者一視同仁:
isDeletableDevice只決定刪除鈕是否渲染/批次選取是否被濾除,DELETE /device/{uid}端點本身不比對created_user,任何已登入使用者只要知道uid(如透過瀏覽器開發工具或直接呼叫 API)即可刪除他人建立的設備。這比本群其餘管理頁常見的「無角色檢查」更進一步——連「擁有者限定」這個 FE 想表達的語意,BE 端完全沒有對應實作。修正需額外評估是否要在add_device/delete_device補 tenant/owner 檢查,屬另開 bug/改動 session 的範圍,本頁 spec 僅記錄不修 - PUT/DELETE 對不存在
uid已回結構化 404(2026-07-08 FR-047 ECODE,jedi-device):原本update()找不到列回None→序列化成「全空欄位」200、delete回data:false(皆非明確錯誤訊號)。jedi-device 已新建 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:BE
DeviceRequest.ip僅fields.String(required=True),不做格式檢查;ipAddressvalidator 純粹是@vuelidate/validators前端擋,繞過 FE 直接打 API 可寫入任意字串到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
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 空狀態)
- E2E:
compliance-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搜尋