角色明細(Role Detail)¶
功能群:系統管理|權限模型先讀 功能群總覽 §2|上層:角色
事實基準:2026-10-08 從 FE
src/views/access/RoleDetail.vue、src/components/access/PermissionTree.vue、src/composables/usePermissionGrouping.js/usePermissionOwnership.js+ jedi-iamrole_route.py、role_domain_service.py+ BEapi/permission_matrix/、core/host_capabilities.py+ 本機 DEV DB 掃出。
變更紀錄¶
| 日期 | FR | 變更 |
|---|---|---|
| 2026-10-08 | FR-132 | 新頁:新增/編輯/檢視角色同一頁,權限樹+可見選單即時預覽,取代角色權限管理表單頁 |
1. 功能描述¶
角色明細是 角色 列表點進來的頁(route access-role-detail,/access/roles/:uid;新增=access-role-new,/access/roles/new,複製=/access/roles/new?template=<uid>)。新增、編輯、檢視、複製四種進入方式同一頁。左邊是權限樹(勾選本角色有哪些權限),右邊即時預覽「指派此角色的人登入後看得到哪些選單」。
主要使用者:租戶管理員。
角色速覽
| 角色 | 在本頁能做什麼 |
|---|---|
持 iam.role.read |
檢視(樹唯讀) |
另持 iam.role.update/create |
編輯既有角色/建立新角色 |
另持 iam.user.assign |
「指派給人」 |
| 內建系統管理員角色 | 誰來看都唯讀,只能「複製成新角色」「匯出 JSON」 |
1.1 功能總覽¶
| # | 功能 | 說明 | 位置 | 詳述 |
|---|---|---|---|---|
| 1 | 標題與動作 | 既有角色:名稱(內建帶鎖頭)+「指派給人」「複製成新角色」「匯出 JSON」 | 頁首 | §3 |
| 2 | 名稱、描述 | 新增模式為表單欄(名稱必填 ≤50、描述 ≤255);編輯模式可改 | 頁首 | — |
| 3 | 租戶 | 新增時有子租戶可選才出現;角色只能指派給同租戶的人;換租戶會清空已勾 | 頁首 | §12 |
| 4 | 權限樹(依功能選單) | 分組 › 側欄頁 › 子頁 › 權限,三態勾選;每項權限附「此權限涵蓋」說明 | 左欄 | UC-ROLE-01 |
| 5 | 依資源視角 | 模組 × 資源 × 動作矩陣,進階用 | 左欄切換 | — |
| 6 | 全部展開/收合、搜尋 | 搜尋功能或動作 | 樹工具列 | — |
| 7 | 只給檢視 | 一鍵只勾該頁「進入此頁所需」那項 | 每頁列 | — |
| 8 | 從既有角色帶入 | 新增模式;對話框挑同租戶角色,帶入後各自獨立;已勾過會先問是否覆蓋 | 樹工具列 | UC-ROLE-02 |
| 9 | 可見選單預覽 | 依分組列全部頁:綠點可見、灰點不可見加「加入」(勾該頁門檻項);原廠專用頁標「原廠管理員專用」不給加 | 右欄 | §10 |
| 10 | 角色資料卡 | 狀態、最後修改、權限數、使用者數、「檢視指派清單」(帶角色篩選跳 指派) | 右欄 | — |
| 11 | 權限規則卡 | 角色實際存的規則唯讀清單,每條標展開幾項 | 右欄 | — |
| 12 | 儲存列 | 列出未儲存的變更(新增/移除哪幾項);「放棄變更」「儲存角色」;新增模式「建立角色」 | 底部固定 | UC-ROLE-01 |
| 13 | 離開確認 | 有未儲存變更時離開頁面先確認 | — | — |
| 14 | 指派給人 | 開 指派 同一個新增指派對話框,角色已選好;停用的角色不給按 | 頁首 | — |
展開成 UC:UC-ROLE-01(勾選並儲存)、UC-ROLE-02(以既有角色為範本新增)。
2. Use Case¶
角色速覽:同 §1。
UC-ROLE-01 勾選權限並儲存¶
| 項目 | 內容 |
|---|---|
| 角色 | 持 iam.role.update(編輯)或 iam.role.create(新增) |
| 前置條件 | 非內建系統管理員;授權未到期 |
| 產出後置 | role_capability_patterns 整組替換成勾選的權限完整名(一項一列) |
flowchart TD
A["勾選/取消權限"] --> B["右欄可見選單即時重算<br/>POST /ui-routes/visible-by-capabilities"]
B --> C["按 儲存角色 / 建立角色"]
C --> D{"新增:有名稱且至少勾一項?"}
D -- 否 --> X0["按鈕停用:填好名稱並至少勾一項"]
D -- 是 --> E["PUT /role/:uid 或 POST /role<br/>送勾選的權限 id 清單"]
E --> F{"內建系統管理員?"}
F -- 是 --> X1["409 AUTH_409019"]
F -- 否 --> G{"同租戶已有同名角色?"}
G -- 是 --> X2["409 AUTH_409004"]
G -- 否 --> H{"新加的權限都在授權照內?"}
H -- 否 --> X3["403 LICENSE_403004"]
H -- 是 --> I{"非原廠租戶角色勾了原廠權限?"}
I -- 是 --> X4["403 GRC_403060"]
I -- 否 --> OK["整組替換規則<br/>清展開快取"]
畫面不自己合成萬用字元:勾什麼就存什麼,日後新增的權限不會被自動帶進來。萬用字元規則只來自出貨角色與 JSON 匯入。
UC-ROLE-02 以既有角色為範本新增¶
| 項目 | 內容 |
|---|---|
| 角色 | 持 iam.role.create |
| 前置條件 | 無 |
| 產出後置 | 新角色帶入範本當下的勾選,之後與範本各自獨立;從列表「複製」進來時名稱預填「<原名> 複本」、建在來源角色的租戶 |
3. 權限矩陣¶
| 操作 | FE 判定 | BE 強制 | BE 檢查位置 |
|---|---|---|---|
| 進入(檢視) | access-role-detail 綁 iam.role.read(ALL) |
iam.role.read |
jedi-iam role_route.py(RoleRoute.get) |
| 進入(新增) | access-role-new 綁 iam.role.create(ALL) |
— | — |
| 權限樹底稿 | 同上 | iam.role.read |
BE api/permission_matrix/routes/permission_matrix_route.py;jedi-iam /ui-routes、/capabilities/menu |
| 儲存(編輯) | v-can iam.role.update;內建隱藏儲存列 |
iam.role.update+內建擋 |
role_route.py(RoleRoute.put)→ role_domain_service.py |
| 建立 | v-can iam.role.create |
iam.role.create |
role_route.py(RoleCreateRoute) |
| 授權照 | 未授權模組的項反灰不可勾、附原因 | 新加的項逐一檢查 | BE app/auth/service/role_app_service.py(_assert_new_capabilities_licensed) |
| 原廠權限 | 非原廠租戶角色樹上不出現原廠項 | 寫入端擋 | capabilities.is_platform;GRC_403060 |
| 指派給人 | v-can iam.user.assign |
iam.user.assign |
role_assignment_route.py |
4. 狀態機與前置條件¶
| 條件 | 效果 | 出處 |
|---|---|---|
is_system=true |
樹唯讀、無儲存列、提示「內建系統管理員擁有全部權限,不能修改;需要調整請複製成新角色」 | RoleDetail.vue;BE AUTH_409019 |
enable=0 |
「指派給人」不給按(停用的角色指派了也不生效) | RoleDetail.vue |
| 有未儲存變更 | 離開頁面先確認 | RoleDetail.vue(leave_confirm) |
| 授權到期唯讀 | 寫入鈕停用 | useLicenseReadonly |
4.1 權限樹怎麼長(規則)¶
三層結構:第一層=側欄分組(ui_routes 的根);第二層=側欄頁;內頁(ui_routes.is_menu=false)預設不上樹,它的權限上移到最近的母頁。例外:ui_routes.tree_page=true 的內頁上樹成側欄頁底下的子頁,權限留在子頁、它底下的內頁再上移進它。目前三支:專案規劃(project-planning)、專案總覽(project-auditor-overview)、稽核計畫(project-ap-list)——三個工作面各有四十幾項權限,壓成一列管理員分不出誰管什麼。
權限歸到哪一頁:
- 權限矩陣(
GET /permission-matrix)列在哪幾頁就放那幾頁;route_capabilities綁在哪頁也列在哪頁。 - 都沒有時看
capabilities.home_route_name:是頁名就放那頁;__global__放最後的「全站功能」組(如 AI 小幫手)。 - 還是沒有的落「其他動作」組——守衛測試保證每項都有家,這組正常是空的、不顯示。
歸屬只管排在哪裡,不影響誰看得到哪頁(可見性只看 route_capabilities)。
同一項權限多頁時只列在擁有者頁,其他頁標「本頁另需:<權限>(於<頁>設定)」。擁有者依序判:
route_capabilities直接綁的頁;home_route_name指到的候選頁;- 同資源其他權限最多的頁;
- 樹上先出現的頁。
只列一次是因為勾選以權限 id 共用,列兩次勾一處另一處會連動,管理員會當成兩項不同權限。依此規則:任務執行面(檢視/修改任務、任務留言、任務證據)歸專案總覽;建立/刪除/指派/匯入任務歸專案規劃;稽核寫入權限歸稽核計畫,「我的稽核」頁只留檢視稽核。
其他列示規則:
- 進入該頁所需那項(
route_capabilities的 ALL 規則,每頁至多一項)標「進入此頁所需」。 - 空殼權限(沒綁頁、沒功能點、後端沒守)不列,「依資源」視角也不列。
- 內頁只列自己的進入門檻與母頁沒列的項;同一母頁下多支內頁同門檻併成一列。
- 名稱一律走 i18n(選單
lang.menu.<name>、權限lang.capability.<模組>.<資源>.<動作>)。
5. UI 設計¶
兩欄:左 8/12 權限樹(工具列:依功能選單/依資源切換、搜尋、全部展開收合;新增模式另有「從既有角色帶入」),底部固定儲存列;右 4/12 三張卡:可見選單(不收合)、角色資料、權限規則(可收合;新增模式只有可見選單卡)。
📸 實機截圖待補(亮色模式):編輯模式整頁(含未儲存變更列)、新增模式、內建系統管理員唯讀、依資源視角、從既有角色帶入對話框。
6. API 規格¶
6.1 總清單¶
| 分類 | Method + Path | 說明 | 守門 |
|---|---|---|---|
| 明細 | GET /api/1.0/role/<uid> |
角色+capability_patterns |
iam.role.read |
| 建立 | POST /api/1.0/role |
見 §6.2 | iam.role.create |
| 更新 | PUT /api/1.0/role/<uid> |
同上 | iam.role.update |
| 停用/啟用 | PUT /api/1.0/role/status/<uid> |
內建回 409 | iam.role.update |
| 統計 | POST /api/1.0/roles/stats |
權限數、每條規則展開幾項 | iam.role.read |
| 可見選單 | POST /api/1.0/ui-routes/visible-by-capabilities |
給權限名清單或角色 uid,回可見頁;與側欄 /user/web-menu 同一條判定(含授權照過濾) |
iam.role.read |
| 樹底稿 | GET /api/1.0/ui-routes、GET /api/1.0/capabilities/menu、GET /api/1.0/permission-matrix |
頁樹、權限清單、頁 › 功能點 › 權限對照(隨版靜態、帶 ETag) | iam.role.read |
| 範本下拉 | POST /api/1.0/roles |
從既有角色帶入 | iam.role.read |
| 匯出 | POST /api/1.0/roles/export |
見 角色 §6 | iam.role.export |
6.2 建立/更新(RoleRequest)¶
{
"name": "稽核員(北區)",
"description": "北區稽核",
"tenant_id": 102,
"enable": 1,
"capabilities": [12, 15, 88]
}
| 欄位 | 型別 | 說明 |
|---|---|---|
name |
string | 必填;同租戶不可重名(409 AUTH_409004) |
description |
string | |
tenant_id |
int | 角色所屬租戶 |
capabilities |
list[int] | 勾選的權限 id;後端逐項轉完整名、整組替換 role_capability_patterns |
出處:jedi-iam api/serializers/role.py(RoleRequest)、domain/service/role_domain_service.py(_write_patterns)。
7. 前端檔案地圖(compliance-manager-fe/)¶
| 檔案 | 角色 |
|---|---|
src/views/access/RoleDetail.vue |
頁面本體 |
src/views/access/RoleDetailHeader.vue、RoleInfoFields.vue |
頁首、名稱描述 |
src/views/access/RoleSaveBar.vue |
底部儲存列 |
src/views/access/RoleTemplateDialog.vue |
從既有角色帶入 |
src/views/access/RoleInfoCard.vue、RolePatternCard.vue |
右欄卡片 |
src/components/access/PermissionTree.vue、PermissionMenuTree.vue、PermissionResourceMatrix.vue、TriStateCheckbox.vue |
權限樹兩種視角 |
src/components/access/VisibleMenuPreview.vue |
可見選單預覽 |
src/composables/usePermissionGrouping.js、usePermissionOwnership.js、usePermissionMatrix.js |
樹的分組、擁有者、矩陣(純函式) |
8. 後端檔案地圖¶
| 鏈路 | Route | Service | 底層 |
|---|---|---|---|
| 角色 CRUD | jedi-iam api/routes/role_route.py |
BE app/auth/service/role_app_service.py(授權照守門、審計暱稱)→ jedi-iam role_domain_service.py |
shipped_role_writer_impl.py 寫規則表 |
| 權限矩陣 | BE api/permission_matrix/routes/permission_matrix_route.py |
core/permission_matrix.py |
隨版靜態資料 |
| 權限宣告 | — | BE core/host_capabilities.py、jedi-* 各套件宣告 |
乾淨安裝與每次升級照宣告寫 capabilities(含 home_route_name) |
守衛測試 test/test_fr132_capability_home_route.py 擋「沒綁頁又沒填歸屬頁」與「填了不存在的頁名」。
9. DB¶
| 表 | 讀寫 | 說明 |
|---|---|---|
public.roles |
讀/寫 | 角色本體 |
public.role_capability_patterns |
讀/寫(整組替換) | 判定唯一來源 |
public.capabilities |
讀 | 權限目錄 |
public.ui_routes |
讀 | 頁樹(is_menu、tree_page) |
public.route_capabilities |
讀 | 進頁門檻(ALL/ANY) |
role_capabilities 已退役、不再寫入;判定與角色頁只讀規則表。
public.roles(FR-132 相關欄位)
| 欄位 | 型別 | 說明 |
|---|---|---|
is_system |
boolean | 內建系統管理員:每租戶至多一個,不可刪不可改,規則固定 *.*.* |
is_template |
boolean | 出貨範例:安裝時帶入、客戶可改可刪 |
is_admin |
integer | 管理員旗標 |
public.role_capability_patterns
| 欄位 | 型別 | 說明 |
|---|---|---|
role_id |
integer | 角色 |
pattern |
varchar | 萬用字元規則,例 grc.project.*、*.*.read、*.*.*;畫面勾選則存完整名 |
public.capabilities(FR-132 相關欄位)
| 欄位 | 說明 |
|---|---|
name |
三段名 <模組>.<資源>.<動作> |
module |
第一段,「依資源」視角第一層 |
is_platform |
原廠層,只有原廠租戶角色可持有 |
license_module |
授權照模組鍵 |
legacy_name |
改名前舊名(匯入舊檔對照用) |
enforce_mode |
enforce(不放行即 403)/log_only;208 項目前全為 enforce |
home_route_name |
角色樹歸屬頁(ui_routes.name 或 __global__) |
public.ui_routes.tree_page:角色樹上當子頁顯示的內頁,只對 is_menu=false 有意義。
10. 頁面邏輯與資料對應¶
sequenceDiagram
participant P as 角色明細
participant BE
P->>BE: GET /ui-routes、/capabilities/menu、/permission-matrix
alt 編輯/檢視
P->>BE: GET /role/:uid
P->>BE: POST /roles/stats
else 複製
P->>BE: GET /role/:template(帶入勾選)
end
P->>P: groupPermissions(歸頁、擁有者、上移內頁)
loop 每次勾選
P->>BE: POST /ui-routes/visible-by-capabilities
end
| 畫面元素 | FE state | API 欄位 |
|---|---|---|
| 勾選 | selected(權限 id Set) |
capabilities[] |
| 權限規則卡 | role.capability_patterns |
GET /role/:uid |
| 每條展開幾項 | stats.pattern_counts |
/roles/stats |
| 角色資料卡權限數 | 沒改動時用展開後數字(含星號展開);有改動以勾選為準 | /roles/stats capability_count |
11. 背景行為與外部依賴¶
| 類型 | 內容 |
|---|---|
| 快取 | 儲存後清規則展開快取;權限矩陣用 ETag |
| 生效時機 | 角色權限變動後,持有者下一次請求即依新規則判定;側欄選單在重新整理或重新登入後更新 |
| socket/排程 | 無 |
12. 邊界情況與已知坑¶
- 換租戶清空勾選:原廠權限只屬原廠租戶,換租戶後清掉已勾的,以免帶入不屬於新租戶的項。
- 含星號規則的角色被畫面編輯:畫面以展開後的具體項呈現;儲存會把規則整組換成勾選的完整名,星號規則不再保留,日後新增的權限也就不會自動帶入。
- 未授權模組:已勾但授權照未含的項維持設定、反灰顯示「購買後會自動恢復」;新勾則不允許。
- 選單可見與能不能操作是兩回事:可見只看進頁門檻;頁內按鈕另依各自權限隱藏。