手冊首頁 / 系統管理 / 儲存設定

儲存設定(Storage Config)

功能群:證據管理|兩種「證據」概念與儲存後端總覽先讀 _overview.md §3/§4

事實基準:2026-07-05 從 FE / BE code 掃出;API schema 逐條對照 marshmallow serializer 與 service 組裝碼

1. 功能描述

儲存設定頁是全租戶層級的檔案儲存後端設定頁:租戶管理者在此選擇檔案要存去哪裡(本機磁碟 / MinIO 物件儲存 / 客戶端檔案 Agent),是一份單筆 key-value 設定表單(無列表、無分頁),存檔即時生效——下一次任何檔案上傳/讀取都會照新設定解析後端。本頁設定影響全站兩種「證據」(見 _overview §3)與其他一般檔案上傳共同的儲存路徑,屬系統基礎建設而非業務資料。

主要使用者:租戶管理者(系統設定層級角色,非專案參與者)。

角色速覽(完整定義見 _overview §5):

角色 一句話
租戶管理者 系統設定層級角色(非專案 manager/auditor),本頁與雲端整合 / 檔案 Agent 管理同屬系統設定選單群組

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

# 功能 說明 位置 詳述
1 儲存類型下拉選擇 local / minio / 使用 Agent 服務(remote_agent)三選一,選項來自 system_menus(group=STORAGE_TYPE 表單頂 UC-SC-01
2 Local 欄位:本地儲存路徑 base_dir,切回 local 時自動帶入預設值 /tmp/upload/ type-conditional 區塊 §6.1
3 MinIO 欄位群 Endpoint URL / Access Key(密碼型輸入)/ Secret Key(密碼型輸入)/ Bucket 名稱 / secure 開關 type-conditional 區塊 §6.1
4 Remote Agent 欄位:客戶端檔案 Agent 下拉 從本 tenant 啟用中的 agent 清單挑一個(GET /remote-agents),儲存 agent_uid type-conditional 區塊 UC-SC-02
5 表單驗證(依 storage_type 分流) 三組 vuelidate rule-set(local / minio / remote_agent),只驗當前選中類型的必填欄位 存檔前 §12 坑 1
6 儲存 storage_type 組裝對應欄位子集送出 PUT,成功後重拍「未儲存變動」快照 底部按鈕 UC-SC-01
7 離開頁面未儲存變動確認 比對目前 config 與快照,離開路由前跳 confirm dialog 全頁(路由守衛) §12 坑 2

UC(§2)展開 1(載入+切換+存檔)與 4(remote_agent 下拉來源)兩條核心路徑;其餘欄位屬同一表單的常規 CRUD 欄位,已在總覽表列完整。

2. Use Case

角色速覽(完整定義見 _overview §5):

角色 一句話
租戶管理者 系統設定層級角色(非專案 manager/auditor),本頁與雲端整合 / 檔案 Agent 管理同屬系統設定選單群組

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

UC-SC-01 讀取並儲存儲存後端設定

項目 內容
角色 租戶管理者
前置條件 已登入;FE 選單可見性由 storage-config capability 判定(§3)
產出 / 後置條件 public.system_configs(group=STORAGE_CONFIG, key=CONFIG)該租戶列的 value JSONB upsert;下一次檔案上傳/讀取即用新設定解析 adapter
uc_sc_01_save start 進頁 onMounted load 依序載入: STORAGE_TYPE 選單 → /remote-agents → GET /system/config/STORAGE_CONFIG/CONFIG start->load notfound BE 檢查:設定是否已存在(404)? load->notfound fallback 沿用預設值 local + /tmp/upload/ notfound->fallback 是(404) render 依 config.storage_type 顯示對應欄位區塊 notfound->render fallback->render change 使用者切換類型 / 編輯欄位 render->change save 點擊存檔 change->save validate FE 判斷:當前 storage_type 對應必填欄位是否通過? save->validate blocked 表單顯示欄位級錯誤, 不送出請求 validate->blocked 未通過 put PUT /system/config/STORAGE_CONFIG/CONFIG (依類型組裝欄位子集) validate->put 通過 ok 200 成功 put->ok success 重拍未儲存變動快照 + 成功 toast ok->success fail 全域 axios-error toast (本頁無額外 catch) ok->fail
UC-SC-01 流程:進頁依序載入選單/Agent清單/現有設定→依 storage_type 顯示對應欄位→存檔前依類型分流驗證→PUT upsert

UC-SC-02 選擇 Remote Agent 儲存後端

項目 內容
角色 租戶管理者
前置條件 本 tenant 已在「檔案 Agent 管理」頁登錄至少一個 enabled=true 的 agent(FR-039)
產出 / 後置條件 儲存 {storage_type: 'remote_agent', agent_uid};之後檔案落地到該 agent 對應的客戶端設備
uc_sc_02_agent_source start 進頁載入 GET /remote-agents query BE:查本 tenant enabled=true 的 remote_agents start->query empty 清單是否為空? query->empty noagent 下拉無選項 (需先到「檔案 Agent 管理」頁登錄 agent) empty->noagent haveagent 下拉顯示 {uid, name} (不含 base_url) empty->haveagent select 使用者選擇 storage_type=remote_agent 並選一個 agent haveagent->select validate FE 必填檢查: agent_uid 是否已選? select->validate blocked 存檔被擋,顯示必填錯誤 validate->blocked 未選 save PUT 送出 {storage_type:'remote_agent', agent_uid} validate->save 已選 done 成功:後續檔案操作 改路由到該 agent 對應客戶端設備 save->done
UC-SC-02 流程:下拉資料來自本 tenant 啟用中 agent 清單;選單為空則無可選項、存檔會被必填擋下

3. 權限矩陣

操作 FE 判定 BE 強制 BE 檢查位置
選單 / 頁面可見性 ui_routes(name=storage-config)→ route_capabilities(read=ALL, create/update/delete=ANY)→ role_capabilities 授權角色 2/3/37/38(Administrator / Billows Admin / IT執行人員 / Billows 系統管理員) ⚠️ ——GET/PUT /system/config/{group}/{key}@jwt_required(),未做 capability 二次驗證 api/system_config/routes/system_config_route.pySystemConfigGroupRoute.get / .put
讀 / 存設定值 同上(頁面級门檻,非欄位級) 無角色檢查,僅 JWT + tenant RLS(system_configstenant_id/org_unit_id,讀寫自動收斂到呼叫者租戶) 同上
Remote Agent 下拉來源 無額外判定 @jwt_required();資料範圍由 tenant RLS 限縮 api/remote_agent/routes/remote_agent_route.py:28-40RemoteAgentsRoute.getlist_enabled_agents

⚠️ 已知不對齊(維護時注意):FE 選單門檻走完整 RBAC(capability + role),但 BE 兩支端點本身都只驗證 JWT 登入、不驗證 capability——任何登入使用者若已知 PUT /system/config/STORAGE_CONFIG/CONFIG 路徑,即可繞過選單直接改動全租戶的儲存後端設定。此為系統設定類頁面的既有模式(同 my-audits 的 audit.read 落差),非本次盤點新發現的缺口,但風險層級較高(可改動的是「檔案存哪裡」這種基礎設施設定,非唯讀彙整),詳見 §12 坑 3。

4. 狀態機與前置條件

本頁不涉及輪次 / AP 狀態機——是獨立於稽核業務流程之外的系統設定頁,無 phase gate。

條件 效果 出處
storage_type === 'local' 只顯示/驗證 base_dir;切換到 local 時自動帶入 /tmp/upload/ StorageConfigForm.vue:149-151onStorageTypeChange
storage_type === 'minio' 只顯示/驗證 5 個 MinIO 欄位 同檔 :284-343
storage_type === 'remote_agent' 只顯示/驗證 agent_uid;下拉選項來自 GET /remote-agents(僅本 tenant 啟用中 agent) 同檔 :100-107,346-362
GET 讀取 404(尚未設定過) FE 僅 console.log,保留 config 預設值(local + /tmp/upload/)當初始表單值 同檔 :133-147;BE 對應 SystemConfigService._load_config 同樣以 NotFound 時回退 LocalFileUploadConfigDTO()managed_file_upload_service.py:69-75

5. UI 設計

📸 實際畫面截圖待補(一律亮色模式拍攝):以 playwright 對 STG 擷取,預計補以下畫面到 assets/img/: ① 整頁 — storage_type=local 顯示狀態 storage-config-local.png ② storage_type=minio 顯示狀態(欄位群展開)storage-config-minio.png ③ storage_type=remote_agent 顯示狀態(Agent 下拉)storage-config-remote-agent.png

版面骨架與區塊職責(單一 Card 表單,無左側樹、無 Tab,下方欄位群依上方下拉選擇的類型三選一切換;彩色區塊可點跳本頁小節錨點,API 僅標代表性):

頁首標題:儲存設定(單一 Card 表單,無左側樹、無 Tab) 跳至 §2 Use Case(UC-SC-01 讀取並儲存儲存後端設定) 儲存設定 Card 表單 儲存類型下拉:local / minio / 使用 Agent 服務(remote_agent)— 三選一 GET /system/menu/STORAGE_TYPE ・ GET /system/config/STORAGE_CONFIG/CONFIG PUT /system/config/STORAGE_CONFIG/CONFIG(依類型送對應欄位子集) → §2 UC-SC-01 local 欄位 base_dir(本地儲存路徑) 切回 local 帶入 /tmp/upload/ minio 欄位群 Endpoint/Access Key/Secret Key Bucket 名稱/secure 開關 (密碼型輸入遮罩) 跳至 §2 Use Case(UC-SC-02 選擇 Remote Agent 儲存後端) remote_agent 欄位 客戶端檔案 Agent 下拉 (本 tenant 啟用中 agent,存 agent_uid) GET /remote-agents → §2 UC-SC-02 存檔 表單驗證:依 storage_type 分流三組 vuelidate rule-set,只驗當前類型必填欄位 彩色=可點跳本頁小節錨點(框內標錨點+代表性 API)|灰=本頁描述|虛線框=依 storage_type 三選一顯示的欄位區塊 API 僅列代表性 2~3 條,完整規格見 §6
儲存設定頁版面 wireframe:標題+單一 Card,內含儲存類型下拉與依類型顯示的欄位區塊(local/minio/remote_agent),底部存檔按鈕

狀態呈現:進頁 LoadingState(card 範圍,依序等待選單/Agent清單/設定三支 API 都回來才收起);存檔中 Button :loading="submitted";無 per-欄位 spinner;無 socket,存檔成功僅 toast 提示、不重新整理頁面。密碼型欄位(minio_access_key / minio_secret_key)用 PrimeVue Password:feedback="false",純遮罩輸入框,非強度檢核)。

6. API 規格

Envelope:成功 {"status": true, "data": …}、失敗 {"status": false, "error_code": "…", "msg": "…"}

6.1 總清單(本頁呼叫的全部 endpoint)

分類 Method + Path 說明 完整規格
載入 GET /system/menu/STORAGE_TYPE 儲存類型下拉選項(system_menus group=STORAGE_TYPE) §6.2
載入 GET /remote-agents 本 tenant 啟用中的 Remote Agent 清單(storage_type=remote_agent 下拉用) §6.2
載入 GET /system/config/STORAGE_CONFIG/CONFIG 讀目前儲存設定 §6.3
儲存 PUT /system/config/STORAGE_CONFIG/CONFIG 更新儲存設定(依 storage_type 送對應欄位子集) §6.3

本頁只有 4 支 endpoint、無分頁、無 detail 頁、無刪除操作(SystemConfigGroupRoute 雖有 delete 方法但本頁 UI 未使用)。

6.2 選單載入類

[GET] /system/menu/{group}(group=STORAGE_TYPE)

Response data(陣列):{key: string, value: string}——key 是下拉直接顯示的 label(無 i18n,FE optionLabel="key"),value 是實際存入 storage_type 的值(local / minio / remote_agent)。資料來源 public.system_menus(group=STORAGE_TYPE),目前三筆:本地磁碟/localMinio 物件儲存/minio使用 Agent 服務/remote_agent(後者由 FR-039 seed,見 scripts/sql/2026-06-18-fr039-storage-type-remote-agent-menu.sql)。出處:app/system_menu(常規 CRUD,細節見 GAI-SD-02 / Swagger)。

[GET] /remote-agents

無 query 參數。Response data(陣列,只回 2 欄,不曝光內網 base_url):

欄位 型別 說明
uid string Remote Agent 識別碼,存進 agent_uid
name string 顯示名稱(下拉 optionLabel

出處:api/remote_agent/serializers/remote_agent.py:4-7RemoteAgentResponse)、api/remote_agent/routes/remote_agent_route.py:28-40RemoteAgentsRoute.getRemoteAgentService.list_enabled_agentsdomain.get_remote_agents(enabled=True)app/remote_agent/service/remote_agent_service.py:56-59)。

6.3 儲存設定類(本頁核心)

[GET] /system/config/{group}/{key}

group='STORAGE_CONFIG', key='CONFIG'。無 body。

Response data

欄位 型別 說明
uid string system_configs.uid(本設定列的識別碼,FE 未使用)
group / key string 固定 STORAGE_CONFIG / CONFIG
value object storage_type 形狀不同,見下表
created_at / updated_at datetime 審計欄

valuestorage_type 的三種形狀:

storage_type 欄位
local {storage_type: "local", base_dir: string}
minio {storage_type: "minio", minio_endpoint, minio_access_key, minio_secret_key, minio_bucket, minio_secure: bool}
remote_agent {storage_type: "remote_agent", agent_uid: string}(另有 legacy 相容欄位 base_url,見 §12 坑 4)

出處:api/system_config/serializers/system_config.py:9-15SystemConfigResponsevaluefields.Raw 未強型別,形狀由 BE service 層 JSONB 直通);BE 讀取時的欄位語意對照見 app/upload_file/service/managed_file_upload_service.py:21-41(docstring 明列三種 JSONB 形狀)。

[PUT] /system/config/{group}/{key}

Request body(無獨立 marshmallow schema 強制欄位,SystemConfigRequest 僅寬鬆 wrapper;實際依 storage_type 送出對應欄位子集,FE 組裝邏輯):

storage_type 送出欄位
local storage_type, base_dir
minio storage_type, minio_endpoint, minio_access_key, minio_secret_key, minio_bucket, minio_secure
remote_agent storage_type, agent_uid

行為:upsert by group+keyupdate_config_by_group_key 內部若不存在會建立,見 domain service),非本欄位級 partial update——每次送出都是該 storage_type 對應欄位的完整覆蓋 value(未送出的其他 storage_type 專屬欄位不會殘留在新 value 裡,因為 FE 每次都重組一份全新 payload object)。Response:同 GET。

出處:api/system_config/routes/system_config_route.py:107-119SystemConfigGroupRoute.put)→ SystemConfigService.update_config_by_group_keyjedi_system_config/app/service/system_config_service.py:142-149);FE 組裝邏輯 StorageConfigForm.vue:202-227update())。

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

檔案 角色
src/views/storage-config/StorageConfigForm.vue 唯一檢視(約 370 行;下拉切換、三組驗證、載入/存檔)
src/config/router/index.js:288-299 route storage-config,path /system/storage-config
src/config/api/api.js:328,330 SYSTEM_CONFIG/system/configREMOTE_AGENTS/remote-agents
src/stores/menuStore.js:196-204 fetchStorageTypeMenu——每次進頁重抓(不用舊快取,避免新增 storage_type 選項時被持久化清單擋住)
src/service/BaseService.js axios 封裝(本頁 get() / put()
src/config/locales/i18n/{zh-tw,en}/storage-config.json 頁面 i18n(欄位 label / placeholder / 驗證訊息)

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

鏈路 Route App Service 底層
讀 / 寫儲存設定 api/system_config/routes/system_config_route.py::SystemConfigGroupRoute(GET/PUT /system/config/{group}/{key} jedi_system_config 套件 SystemConfigService.get_system_config_by_key / update_config_by_group_key@transaction SystemConfigDomainServiceSystemConfigRepoImplpublic.system_configs(tenant RLS)
Remote Agent 下拉 api/remote_agent/routes/remote_agent_route.py::RemoteAgentsRoute(GET /remote-agents app/remote_agent/service/remote_agent_service.py::RemoteAgentService.list_enabled_agents@transaction RemoteAgentDomainService.get_remote_agents(enabled=True)infra/remote_agent/repository/remote_agent_repo_impl.py
儲存類型選單 api/system_menu/routes/system_menu_route.py jedi_system_menu 套件 SystemMenuService.get_system_menu(group) public.system_menus(無 tenant 欄位,全域共用選單)
消費端(本頁設定的下游) 不在本頁鏈路內 app/upload_file/service/managed_file_upload_service.py::ManagedFileUploadService._load_config 每次檔案操作時即時讀 STORAGE_CONFIG/CONFIGUploadConfigDTO → 選對應 IUploadFileProvider adapter(jedi-file-upload 套件)

DDD 提醒SystemConfigGroupRoutejedi_system_config 套件內建的通用 CRUD route,非本專案 api/ 層另寫的業務 route——套件本身不認識「STORAGE_CONFIG」這個 group 語意,語意詮釋(三種 value 形狀、remote_agent 二擇一解析)全部在主專案 ManagedFileUploadService 完成。新增本頁欄位時只需改 FE payload 組裝 + _build_config_dto 的形狀解析,不要在套件層加 STORAGE_CONFIG 專屬邏輯(違反套件不認識 caller 業務知識的既有分工,見 CLAUDE.md 外部套件異動規範)。

9. DB

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

讀/寫 說明 欄位詳述
public.system_configs (upsert by group+key) 儲存設定值本體(本頁唯一寫入表;JSONB value 形狀依 storage_type 而異) §9.3
public.system_menus 儲存類型下拉選項(group=STORAGE_TYPE,全域共用、無 tenant 欄位) §9.3
compliance.remote_agents Remote Agent 下拉來源(僅 enabled=true,tenant RLS 收斂) §9.3

表名已對照 scripts/deliverables/out/db_schema.json 驗證system_configspublic schema(非 compliance),無獨立 storage_configs 表——是通用 key-value 設定表的其中一列(group='STORAGE_CONFIG', key='CONFIG'),不是專屬儲存設定的表。

9.2 ER 圖(本頁讀寫範圍)

storage_er cluster_write 本頁寫入(public) cluster_menu 下拉來源(讀) cluster_consumer 消費端下游(非本頁鏈路) sc system_configs group=STORAGE_CONFIG, key=CONFIG (value JSONB,per-tenant 一列) sm system_menus group=STORAGE_TYPE (全域共用,無 tenant 欄) sc->sm value.storage_type 對應 選單 value ra remote_agents enabled=true(tenant RLS) sc->ra value.agent_uid 參照 (remote_agent 型) uf ManagedFileUploadService 每次檔案操作即時讀 sc uf->sc 即時讀取,無快取
儲存設定資料模型:綠 = 本頁寫入(system_configs 一列);黃 = 下拉來源(system_menus 全域無 tenant、remote_agents tenant-scoped);虛線 = 消費端下游(不在本頁鏈路,僅供理解全貌)

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

public.system_configs — 系統配置表(本頁唯一寫入表)

DB comment:「系統配置表:儲存系統設定參數,以群組和鍵值對方式組織」。本頁只碰 group='STORAGE_CONFIG', key='CONFIG' 這一列(每租戶各自一列,tenant_id 由 RLS/db_mw 自動注入,非本頁欄位)。

欄位 型別 說明
id integer 主鍵
uid varchar(50) 配置唯一識別碼 UUID
group varchar(255) 配置群組(本頁固定 STORAGE_CONFIG
key varchar(255) 配置鍵名(本頁固定 CONFIG
value jsonb 配置值——本頁讀寫的實際內容,形狀依 storage_type 而異(見 §6.3)
tenant_id integer|null RLS 租戶欄(TenantScopedMixinModel 帶入,非 API 欄位)
org_unit_id integer|null RLS 部門欄(同上)
created_at / updated_at timestamp 審計欄(無 tz)

created_user/updated_user 欄位——system_configs 表本身未含審計使用者欄,套件層未實作(本頁不受「審計欄位回傳規範」約束,因為欄位根本不存在)。

public.system_menus — 系統選單表(本頁只讀,供下拉)

DB comment:無獨立表註解(欄位皆有 comment,見下)。無 tenant 欄位——是全域共用的選單資料,非租戶層設定。

欄位 型別 說明
group varchar(255) 菜單群組(本頁固定 STORAGE_TYPE
key varchar(255) 菜單鍵名(下拉顯示文字,無 i18n)
value varchar(255) 菜單值(存入 storage_type 的實際值)
sort integer 排序順序
enable integer 是否啟用:1 啟用,0 停用
public integer 是否公開:1 公開,0 私有

compliance.remote_agents — Remote Agent registry(本頁只讀,供下拉)

FR-039 客戶端檔案 Agent registry;完整欄位與管理操作見 檔案 Agent 管理。本頁只用到:

欄位 型別 說明
uid varchar(50) Agent 識別碼 → response uid,存入本頁 agent_uid
name varchar(255) 顯示名稱 → response name
enabled boolean 本頁下拉只列 enabled=true 的 agent
tenant_id integer RLS 收斂到呼叫者租戶

base_url(Agent 內網位址)欄位存在於表中,但本頁下拉刻意不回傳RemoteAgentResponse 只 dump uid/name,避免內網位址曝光給前端)。

10. 頁面邏輯與資料對應

載入時序(三支 API 依序等待,非平行):

storage_load_sequence mounted onMounted:loading=true menu ① fetchStorageTypeMenu GET /system/menu/STORAGE_TYPE (每次重抓,不用持久化快取) mounted->menu agents ② getRemoteAgents GET /remote-agents menu->agents config ③ getConfig GET /system/config/STORAGE_CONFIG/CONFIG agents->config snapshot 拍 originalConfig 快照 (成功用 res.value;失敗沿用預設值) config->snapshot done loading=false,渲染表單 snapshot->done
頁面載入時序:依序 fetchStorageTypeMenu → getRemoteAgents → getConfig,全程覆蓋 card loading;三支都完成才收起

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

畫面元素 FE state API 欄位
儲存類型下拉 config.storage_type GET/PUT value.storage_type;選項來自 /system/menu/STORAGE_TYPE{key,value}
本地儲存路徑 config.base_dir value.base_dir
MinIO 5 欄 config.minio_endpoint / minio_access_key / minio_secret_key / minio_bucket / minio_secure value.minio_* 同名欄位
Agent 下拉 config.agent_uid;選項來自 remoteAgents/remote-agents 回傳) value.agent_uid
「未儲存變動」判定 originalConfig(載入/存檔成功時的 JSON 字串快照)vs 當前 config 純 FE state,無對應 API 欄位

儲存流程:無整頁多欄位合併儲存的概念——onSaveClick 先依 storage_type 跑對應 vuelidate rule-set,全數通過才呼叫 update()update()storage_type 重新組裝一份只含該類型欄位的 payload(送出的物件不含其他兩種類型的殘留欄位)→ PUT 覆蓋 value。存檔成功後重拍 originalConfig 快照(離開頁面的未儲存變動判定基準),並顯示成功 toast。

錯誤對應:GET 讀取失敗(含 404 尚未設定過)僅 console.log,不顯示 toast、表單維持預設值(local + /tmp/upload/);PUT 存檔失敗走 .finally() 收 loading 但未見顯式 catch 顯示錯誤 toast——與其他頁面統一走 axios 全域 axios-error toast 的慣例一致(本頁未額外攔截,失敗會落到全域 interceptor),細節見 §12 坑 5。

11. 背景行為與外部依賴

類型 內容
通知 無——本頁不觸發、也不消費任何通知
Socket 無 socket / 輪詢;純表單載入 + 存檔
jedi-* 套件 jedi-system-configsystem_configs 表 CRUD 通用套件)、jedi-system-menusystem_menus 通用套件)、jedi-file-uploadIUploadFileProvider adapter 介面,本頁設定的下游消費者)、jedi-auth(JWT)
系統參數 無 feature flag;STORAGE_TYPE 選單本身是可調整的系統參數(新增儲存類型需先在 system_menus seed,再擴充 FE type-conditional 區塊與 BE _build_config_dto 解析)
消費端(重要但不在本頁鏈路) 任何檔案上傳/下載/轉檔操作(ManagedFileUploadService)都會即時讀本頁寫入的設定值決定 adapter;本頁存檔生效無延遲、無快取(每次操作都重讀 system_configs,見 CLAUDE.md 對此頁的既有描述「per-tenant RLS、re-read each request」)

12. 邊界情況與已知坑

  1. 表單驗證僅擋當前選中類型的必填欄位,其餘類型欄位不驗證也不清空:切換 storage_type 時舊欄位值仍留在 config state 裡(僅 local 有特殊「切回時重設 base_dir」邏輯),但因 update() 組裝 payload 時只挑當前類型欄位送出,不會誤存其他類型的殘留值——UI 顯示雖可能有「幽靈」舊值殘留在未顯示的欄位,但不影響實際存檔內容
  2. 「未儲存變動」判定用 JSON 字串比對整個 config 物件originalConfig/isDirty() 是全物件序列化比對,換言之即使只是點開下拉又選回原值,只要物件序列化結果不同(如 key 順序,理論上同一物件不會變動順序,實務風險低)都可能誤判有變動;此為 FE 已知取捨(解決舊版「存檔後沒歸位」等三種誤判),非新發現的 bug
  3. BE 兩支端點都無角色/capability 檢查(僅 JWT):見 §3 ⚠️——任何已登入使用者原則上都能直接打 API 改動全租戶儲存後端設定,繞過 FE 選單的 RBAC 門檻。因涉及基礎設施設定(儲存後端切換可能造成既有檔案「找不到」的表面故障),風險層級高於純讀頁面(如 my-audits 的類似落差),若日後要補強建議在 route 層加 capability 檢查而非僅靠選單隱藏
  4. remote_agent 的 value 有兩種形狀(inline vs registry)ManagedFileUploadService._resolve_agent 支援舊版 {storage_type:'remote_agent', base_url:'...'}(inline,FR-039 Phase 2 demo 遺留相容)與新版 {storage_type:'remote_agent', agent_uid:'...'}(registry,FE 本頁實際只會存這種)——若 DB 裡殘留舊版 inline 格式的 row,本頁 GET 讀回來的 value 不會有 agent_uid,下拉會顯示未選中狀態,儲存後即覆蓋為新版格式
  5. PUT 失敗無顯式錯誤提示update().then() 只處理成功情境,.finally() 只收 submitted loading 旗標,未見顯式 .catch() 顯示錯誤訊息——若全域 axios interceptor 沒攔截到,使用者可能存檔失敗卻只看到 loading 消失、無任何提示
  6. 本頁與雲端整合 / 檔案 Agent 管理同群但概念獨立:儲存設定只決定「檔案的位元組存在哪個後端」,Google Drive OAuth 連接(雲端整合)與 Remote Agent 的註冊/生命週期管理檔案 Agent 管理)是另外兩個獨立頁面——本頁只消費「已註冊的 agent 清單」做下拉選擇,不做 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 環境租戶管理者角色帳號(密碼見 .env / 部署文件)
  • 導航路徑:登入 → 側邊選單「系統設定」群組 → 「儲存設備設定」(/system/storage-config
  • 前置資料:測試 remote_agent 選項前,需先在「檔案 Agent 管理」頁登錄至少一個 enabled=true 的 agent(FR-039),否則下拉為空、選中該類型會被必填擋下無法存檔
  • E2Ecompliance-manager-test/ repo(Cucumber + Playwright);本頁相關 feature 檔以 儲存設定 / storage-config 搜尋
  • 相關文件:FR-039(分散式檔案 Agent,docs/features/FR-039-2606-distributed-file-agent/)、FR-042(系統儲存 scope,app/upload_file/service/managed_file_upload_service.py docstring);DB / API 全量見 GAI-SD-02/03;docs/api/system-config/frontend-spec.md(舊文件,僅供背景參考,完整度不保證)