議題整合設定(Issue Integrate Config)
功能群:系統管理|
system_configskey-value 慣例先讀 功能群總覽 §4事實基準:2026-07-05 從 FE
src/views/issue-integrate-config/IssueIntegrateConfigForm.vue+ BEapi/system_config/+ jedi-system-config 套件 +app/feedback/service/feedback_service.py/issue_service.py+ jedi-issue 套件 + routes.json / db_schema.json 掃出;DEV DB 實查ISSUE_INTEGRATE_CONFIG兩列存在(僅欄位名,未讀值)。
1. 功能描述
議題整合設定頁是 GitLab / GitHub 連線參數維護頁:管理員在此設定外部 issue tracker 的連線資訊,設定值是回饋管理頁建立 / 編輯 / 刪除回饋時,是否與如何把本地回饋鏡像到外部 GitLab / GitHub issue 的依據——本頁是那條鏡像管線的設定端,回饋管理 §11 提到的「議題整合」即指本頁。頁面只有單一表單(無列表、無分頁),以兩張並排 Card 呈現 GitLab / GitHub 兩組獨立設定,非 TabView(沒有 tab 切換,兩張 Card 同時顯示、各自獨立儲存)。
主要使用者:系統管理員(頁面本身無角色守門,見 §3)。
角色速覽:
| 角色 | 一句話 |
|---|---|
| 系統管理員(可見本選單者) | 維護 GitLab / GitHub 連線設定 |
| 一般使用者 | 無此選單(ui_routes 可見性未命中);但可直接打 API(見
§3 ⚠️) |
1.1 功能總覽(本頁全部功能)
| # | 功能 | 說明 | 位置 | 詳述 |
|---|---|---|---|---|
| 1 | 載入既有設定 | 進頁 onMounted 依序 GET GitLab、GitHub
兩組設定回填表單,載入期間顯示 LoadingState |
整頁 | §6.1 |
| 2 | GitLab 啟用開關 | InputSwitch,控制回饋鏡像時是否呼叫 GitLab |
Card「GitLab」 | §6.2 |
| 3 | GitLab URL 欄位 | GitLab 伺服器網址 | Card「GitLab」 | §6.2 |
| 4 | GitLab Private Token 欄位 | GitLab 私人存取權杖(純文字輸入框,非遮罩) | Card「GitLab」 | §6.2、§12 |
| 5 | GitLab API Version 欄位 | GitLab API 版本字串 | Card「GitLab」 | §6.2 |
| 6 | GitLab Project 編號欄位 | project_code;表單顯示鍵名為「編號」,回饋鏡像實際引用的正是這個值 |
Card「GitLab」 | §6.2、§11 |
| 7 | GitLab 儲存鈕 | 送出整張 Card 全部欄位(含未顯示的 project) |
Card「GitLab」底部 | UC-ISSUEINT-01 |
| 8 | GitHub 啟用開關 | InputSwitch,控制回饋鏡像時是否呼叫 GitHub |
Card「GitHub」 | §6.2 |
| 9 | GitHub Private Token 欄位 | GitHub 私人存取權杖(純文字輸入框,非遮罩) | Card「GitHub」 | §6.2、§12 |
| 10 | GitHub Project 名稱欄位 | project;表單顯示鍵名為「名稱」 |
Card「GitHub」 | §6.2 |
| 11 | GitHub 儲存鈕 | 送出整張 Card 全部欄位(含未顯示的 project_code) |
Card「GitHub」底部 | UC-ISSUEINT-01 |
| 12 | 離開頁面確認 | hasChange 為真時離開觸發確認
dialog(onBeforeRouteLeave) |
全域 | — |
UI 隱藏欄位(程式碼註解掉、未渲染但 payload 仍會帶出既有值):GitLab 的
project(gitlab_projecttemplate block 被註解)、GitHub 的project_code(github_project_codetemplate block 被註解)。這兩欄存在於ref()初始值與 DB 資料裡,只是使用者在畫面上看不到、也改不到——儲存時{...gitlab.value}/{...github.value}整包送出,等於「原樣回填再送」,不會把這兩欄清空。見 §12 坑 1。UC(§2)只展開 1(儲存,GitLab / GitHub 共用同一流程);讀取已併入儲存流程圖的載入段。回饋鏡像消費端流程另見下方獨立圖。
2. Use Case
角色速覽:
| 角色 | 一句話 |
|---|---|
| 系統管理員(可見本選單者) | 維護 GitLab / GitHub 連線設定 |
| 一般使用者 | 無此選單(ui_routes 可見性未命中);但可直接打 API(見
§3 ⚠️) |
流程圖視覺慣例:菱形 = 判斷、橘底 = 例外/擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點。
UC-ISSUEINT-01 儲存 GitLab / GitHub 整合設定
| 項目 | 內容 |
|---|---|
| 角色 | 系統管理員(BE 僅檢查登入,見 §3) |
| 前置條件 | 已登入;無前端必填驗證(所有欄位皆可留空送出) |
| 產出 / 後置條件 | public.system_configs(group=ISSUE_INTEGRATE_CONFIG,key=GITLAB
或 GITHUB)新增或更新一列 |
回饋鏡像消費端(非本頁操作,供理解本頁設定的實際作用)
本頁儲存的設定不在本頁被「使用」,而是在回饋管理頁建立 / 編輯 /
刪除回饋時,由 FeedbackService / IssueService
於建構時讀取一次並快取:
3. 權限矩陣
| 操作 | FE 判定 | BE 強制 | BE 檢查位置 |
|---|---|---|---|
| 進入本頁 | ui_routes web-menu
可見性(issue-integrate-config.read
能力,requirement=ALL) |
無端點層強制(僅 @jwt_required) |
api/system_config/routes/system_config_route.py:96 |
| 讀取設定(GET) | 有選單即可 | ⚠️ 僅 @jwt_required,無角色 /
能力守門 |
SystemConfigGroupRoute.get(system_config_route.py:92-104) |
| 儲存設定(PUT) | 有選單即可 | ⚠️ 僅 @jwt_required,無角色 /
能力守門 |
SystemConfigGroupRoute.put(system_config_route.py:106-119) |
⚠️ 本頁兩個端點全部僅
@jwt_required(),無任何角色 / 平台管理員守門——任何已登入使用者(不限系統管理員)直接打 API 都能讀取(含 token 明文,見 §12 坑 2)/ 覆寫全系統 GitLab / GitHub 整合設定。與 _overview §5、ldap-config.md §3 一致(本群system_configs類頁面通病)。本 spec 只記錄不修,收進 §12。FE 選單可見性由
route_capabilities(route_id=32)綁定issue-integrate-config.read(ALL)/.create/.update/.delete(皆 ANY)四個能力點(DEV DB 實查),但如上表,這只決定選單顯不顯示,不影響 BE 端點層。
4. 狀態機與前置條件
本頁無狀態機,扁平的讀取 / 儲存表單:
| 條件 | 效果 | 出處 |
|---|---|---|
| 無前端必填驗證 | 所有欄位(含啟用開關)皆可留空 / 停用狀態送出儲存 | IssueIntegrateConfigForm.vue(無 validate 邏輯) |
enable=false |
設定仍會儲存,但回饋鏡像時 _is_gitlab_enable() /
_is_github_enable() 判斷為否,略過該 provider |
feedback_service.py:350-360 |
| GET 對應 group/key 不存在 | BE get_system_config_by_key 內部走
get_config_by_group_and_key,查無回 None →
route 直接 SystemConfigResponse().dump(None);FE
res.value 對 None 取值會拋前端例外(DEV
環境目前兩列皆已存在,未實測此路徑) |
system_config_route.py:97-98;system_config_domain_service.py:49-52 |
| PUT 對應 group/key 不存在 | BE update_config_by_group_key 是
upsert:查無則新增,非拋錯 |
jedi-system-config
system_config_domain_service.py:118-132 |
5. UI 設計
📸 實機截圖待補(一律亮色模式拍攝):以 playwright 對 STG 擷取,預計補一張到
assets/img/:議題整合設定頁整頁(GitLab / GitHub 兩張 Card 並列,含各自啟用開關與儲存鈕)。
版面骨架(單頁兩張獨立 Card,非 TabView):
狀態呈現:兩張 Card 各自獨立
LoadingState(size="panel")覆蓋於載入期間;儲存鈕各自獨立
loading(submitted 全域旗標,兩張 Card
共用同一顆,儲存其一時另一顆按鈕也會顯示 loading,見 §12 坑 3)。無
socket,儲存後重打對應 GET 回填。
6. API 規格
Envelope:成功 {"code": 1, "data": …}、失敗
{"code": 0, "msg": "…"}。
6.1 總清單(本頁呼叫的全部 endpoint)
| 分類 | Method + Path | 說明 | 完整規格 |
|---|---|---|---|
| 讀取 | GET /system/config/ISSUE_INTEGRATE_CONFIG/GITLAB |
取得 GitLab 設定 | §6.2 |
| 讀取 | GET /system/config/ISSUE_INTEGRATE_CONFIG/GITHUB |
取得 GitHub 設定 | §6.2 |
| 儲存 | PUT /system/config/ISSUE_INTEGRATE_CONFIG/GITLAB |
更新(不存在則新增)GitLab 設定 | §6.3 |
| 儲存 | PUT /system/config/ISSUE_INTEGRATE_CONFIG/GITHUB |
更新(不存在則新增)GitHub 設定 | §6.3 |
endpoint 存在性對
routes.json核實:實際路由是通用/api/1.0/system/config/<string:group>/<string:key>(SystemConfigGroupRoute),本頁固定帶group=ISSUE_INTEGRATE_CONFIG、key=GITLAB|GITHUB,無本頁專屬 endpoint。同一組路由也服務smtp-config/ldap-config等其餘system_configs設定頁(各自不同 group)。無專屬連線測試 endpoint(LDAP 頁有LdapConnectTestRoute,本頁沒有對應的 GitLab/GitHub 連線測試 API)。
6.2 [GET] /system/config/ISSUE_INTEGRATE_CONFIG/{GITLAB|GITHUB}
Response data(SystemConfigResponse):
| 欄位 | 型別 | 說明 |
|---|---|---|
| uid | string | 設定列 UUID |
| group | string | 固定 ISSUE_INTEGRATE_CONFIG |
| key | string | GITLAB 或 GITHUB |
| value | dict | 見下方兩表(provider 專屬欄位)——只描述欄位,不寫實際 token / URL 值 |
| created_at / updated_at | datetime | %Y-%m-%d %H:%M:%S |
value(GITLAB):
| 欄位 | 型別 | 說明 |
|---|---|---|
| enable | bool | 是否啟用 GitLab 鏡像 |
| url | string | GitLab 伺服器網址(值不落地本文件) |
| private_token | string | GitLab 私人存取權杖(值不落地本文件;FE 純文字框顯示,非遮罩,見 §12) |
| api_version | string | GitLab API 版本 |
| project | string | Project 名稱(FE 未渲染此欄,見 §1.1 隱藏欄位) |
| project_code | string | Project 編號;回饋鏡像實際引用此值(issue_service.py
多處 config["GITLAB"]["project_code"]) |
value(GITHUB):
| 欄位 | 型別 | 說明 |
|---|---|---|
| enable | bool | 是否啟用 GitHub 鏡像 |
| private_token | string | GitHub 私人存取權杖(值不落地本文件) |
| project | string | Project 名稱;回饋鏡像引用此值 |
| project_code | string | Project 編號(FE 未渲染此欄,見 §1.1 隱藏欄位;目前無 GitHub 消費端引用此欄) |
注意:SystemConfigRequest/SystemConfigResponse(api/system_config/serializers/system_config.py)schema
本身是通用 Dict 型別,不對 GitLab/GitHub
專屬欄位做逐一宣告——上表欄位形狀依 FE ref() 初始結構與 DEV
DB 實查的 jsonb_object_keys 對照而得,非 marshmallow schema
強制。
出處:api/system_config/routes/system_config_route.py:92-104;api/system_config/serializers/system_config.py:9-15;FE
IssueIntegrateConfigForm.vue:23-36
6.3 [PUT] /system/config/ISSUE_INTEGRATE_CONFIG/{GITLAB|GITHUB}
Request body:直接送整包 value 形狀(即上表 GITLAB /
GITHUB 欄位,非包一層 value key——update()
方法把 gitlab.value / github.value 整包當
payload 送出)。無差異更新語意,送什麼就存什麼(非
full-replace 陣列類欄位,是整份 dict 覆蓋)。
Response:同 §6.2 GET。
BE
鏈路:SystemConfigGroupRoute.put(system_config_route.py:106-119)→
jedi-system-config
SystemConfigService.update_config_by_group_key(group, key, value)
→
SystemConfigDomainService.update_config_by_group_key(upsert:get_by_group_and_key
查無則 add_config 新增,查有則 update_config
更新)。注意:此 domain method 支援
changePwd 旗標(值為 false 時沿用資料庫既有
secret 欄),但本頁 payload
形狀(private_token 明文欄,非
secret/changePwd
結構)未使用這個保留舊密碼機制——每次儲存都是整包覆蓋,FE
若未先 GET 回填就直接改單一欄位送出,其餘欄位會被清空成 FE
ref() 初始值(空字串 / false)。
出處:system_config_route.py:111-119;jedi-system-config
system_config_domain_service.py:118-132
7. 前端檔案地圖(compliance-manager-fe/)
| 檔案 | 角色 |
|---|---|
src/views/issue-integrate-config/IssueIntegrateConfigForm.vue |
唯一頁面元件:GitLab / GitHub 兩張 Card、各自 GET/PUT、離開確認 |
src/config/router/index.js:260-272 |
route /system/issue-integrate-config,name
issue-integrate-config |
src/config/api/api.js:328 |
SYSTEM_CONFIG
端點常數(/system/config),本頁組
${SYSTEM_CONFIG}/ISSUE_INTEGRATE_CONFIG/${type} |
src/service/BaseService.js |
直接使用(無本頁專屬 service 檔) |
src/config/locales/i18n/{zh-tw,en}/issue-integrate-config.json |
i18n(namespace lang.issue_config.*),另有
menu.json 的 issue-integrate-config
選單標籤(「回饋整合設定」) |
src/components/common/LoadingState.vue |
載入中覆蓋層 |
8. 後端檔案地圖(本頁核心鏈路)
| 鏈路 | Route | App Service | 底層 |
|---|---|---|---|
| 讀取 / 儲存本頁設定 | api/system_config/routes/system_config_route.py::SystemConfigGroupRoute |
直接委派(無主專案 app service 包裝) | jedi-system-config SystemConfigService →
SystemConfigDomainService →
SystemConfigRepoImpl →
public.system_configs |
| 回饋鏡像消費端(非本頁 route,供理解設定用途) | 無 route(由回饋 CRUD 觸發) | app/feedback/service/feedback_service.py(_get_integrate_config
/ _is_gitlab_enable /
_is_github_enable)、app/feedback/service/issue_service.py |
infra/system_config/system_config_root_reader.py::read_root_config_value(super-admin
繞 RLS 讀 ROOT tenant)→ jedi-issue JediIssueService →
GitLab/GitHub adapter |
DDD 提醒:本頁 route 直接注入外部套件
SystemConfigService、無主專案 app service 層——與 LDAP 設定 / SMTP
設定 等其餘 system_configs
頁一致,屬既有模式非本頁特例。回饋鏡像消費端的
read_root_config_value 是唯一允許用 raw SQL / 獨立
session 的 infra 層 fallback reader(繞 RLS 讀 ROOT tenant
平台設定),新功能要讀跨 tenant 平台設定應複用它,不要另寫第二份繞 RLS
邏輯(CLAUDE.md「新增 method 前先找既有能力」鐵則)。
9. DB
9.1 資料表總清單(本頁讀寫的全部表)
| 表 | 讀/寫 | 說明 | 欄位詳述 |
|---|---|---|---|
public.system_configs |
讀 / 寫 | 本頁讀寫的唯一表;group='ISSUE_INTEGRATE_CONFIG'、key IN ('GITLAB','GITHUB')(DEV
DB 實查兩列皆存在、tenant_id=1) |
§9.3 |
回饋鏡像消費端額外讀同一張表(
read_root_config_valuefallback,繞 RLS 讀 ROOT tenant 列),但那是回饋管理頁的行為,不計入本頁讀寫範圍。
9.2 ER 圖
本頁只讀寫單一表,無關聯結構需要 ER 圖;system_configs
欄位角色見 _overview §6
核心資料模型。
9.3
核心表欄位(取自 db_schema.json,含 DB comment)
public.system_configs(comment:「系統配置表:儲存系統設定參數,以群組和鍵值對方式組織」):
| 欄位 | 型別 | 說明(DB comment) |
|---|---|---|
| id | integer | 主鍵ID |
| uid | varchar(50) | 配置唯一識別碼UUID |
| group | varchar(255) | 配置群組(本頁固定 ISSUE_INTEGRATE_CONFIG) |
| key | varchar(255) | 配置鍵名(本頁 GITLAB / GITHUB) |
| value | jsonb | 配置值(JSON格式)——本頁兩組欄位形狀見 §6.2 |
| tenant_id | integer(nullable) | 租戶(DEV DB 實查本頁兩列皆 tenant_id=1,即 ROOT
tenant;回饋鏡像消費端固定讀 ROOT,非依呼叫者租戶) |
| org_unit_id | integer(nullable) | 部門(本頁兩列皆 NULL) |
| created_at / updated_at | timestamp | 建立 / 更新時間 |
10. 頁面邏輯與資料對應
載入時序:onMounted →
getConfig() → 依序 await getGitlabConfig() 再
await getGithubConfig()(序列,非併行)→
兩者皆完成才關閉 isLoading。
關鍵欄位對應(畫面 ↔︎ API):
| 畫面元素 | FE state | API 欄位 |
|---|---|---|
| GitLab 啟用開關 | gitlab.enable |
value.enable |
| GitLab URL | gitlab.url |
value.url |
| GitLab Private Token | gitlab.private_token |
value.private_token |
| GitLab API Version | gitlab.api_version |
value.api_version |
| GitLab Project 編號 | gitlab.project_code |
value.project_code |
| (隱藏)GitLab Project 名稱 | gitlab.project |
value.project(表單未渲染,原樣回填送出) |
| GitHub 啟用開關 | github.enable |
value.enable |
| GitHub Private Token | github.private_token |
value.private_token |
| GitHub Project 名稱 | github.project |
value.project |
| (隱藏)GitHub Project 編號 | github.project_code |
value.project_code(表單未渲染,原樣回填送出) |
儲存流程:onSaveClick(type) →
update(type):組 payload = {...gitlab.value}
或 {...github.value}(整包 spread,含隱藏欄位)→
PUT /system/config/ISSUE_INTEGRATE_CONFIG/${type} →
成功後重新呼叫對應 get*Config() 回填(確保顯示的是 DB
實際落地值)→ hasChange / passwordHasChange
重置為 false。GitLab / GitHub 儲存完全獨立,互不影響。
錯誤對應:BE envelope → 全域 axios interceptor 顯示 toast;設定不存在時 GET 回傳形狀依 §4 表列(DEV 環境未實測,因兩列皆已存在);PUT 恆為 upsert 不會回 404。
11. 背景行為與外部依賴
| 類型 | 內容 |
|---|---|
| 回饋鏡像(本頁設定的實際消費端) | 回饋管理頁建立 / 編輯
/ 刪除回饋時,FeedbackService.__init__ 與
IssueService.__init__ 各自呼叫
_get_integrate_config()
讀取本頁設定(服務實例建構時快取一次,非每次操作即時讀);enable=true
才呼叫對應 provider,失敗 best-effort 只 log 不擋本地回饋寫入 |
| Socket | 本頁無 socket / 輪詢 |
| jedi-* 套件 | jedi-system-config(system_configs CRUD /
upsert)、jedi-issue(GitLab/GitHub
adapter:gitlab / PyGithub 三方
SDK)、jedi-auth(JWT) |
| 系統參數 | 無 feature flag;enable
開關本身即是本頁對外部依賴的啟停控制 |
12. 邊界情況與已知坑
- 儲存是整包覆蓋、非差異更新:
update()送{...gitlab.value}/{...github.value}整包(含 FE 未渲染的隱藏欄位project/project_code)。因為 FE 進頁必先 GET 回填才可能編輯,正常操作下隱藏欄位值會被原樣帶回、不會遺失;但若繞過 FE 直接打 PUT 只送部分欄位,其餘欄位會被覆蓋消失(BEupdate_config_by_group_key不支援 partial merge,changePwd保留舊密碼機制本頁未使用,見 §6.3) - Token 明文顯示 + BE
無隱藏:
private_token欄位是一般InputText(非 password 型別,無遮罩),且 BESystemConfigGroupRoute.get的HIDDEN_SECRET清單(system_config_route.py:18)只含SMTP/THIRD_PARTY_LOGIN/NOTIFY_CONFIG三組,不含ISSUE_INTEGRATE_CONFIG——GET 回應會把 GitLab / GitHub token 明文回傳給前端,任何有選單可見性(甚至僅 JWT 就能打 API)的使用者都能讀到完整 token。與 §3 的「BE 無角色守門」缺口疊加,風險較 SMTP/LDAP 更高(後兩者至少 GET 會濾除secret) - 儲存鈕 loading 旗標共用:兩張 Card 共用同一個
submittedref,儲存 GitLab 時 GitHub 的儲存鈕也會一起顯示 loading 動畫(純視覺,不影響實際送出對象;type參數決定實際 PUT 哪組) - 無連線測試功能:對照 LDAP
設定 有專屬
LdapConnectTestRoute可在儲存前驗證連線,本頁儲存後才會在下一次回饋提交時實際發現 token / URL 是否有效(best-effort 失敗只 log,管理員不會在本頁直接得到錯誤回饋) create_github_issue疑似取錯 config key:app/feedback/service/issue_service.py:224在建立 GitHub issue 時讀的是self.config["GITLAB"]["project"](而非self.config["GITHUB"]["project"])——若兩組project值不同,GitHub 鏡像會建到錯的專案或因值不存在而失敗。本 spec 只記錄不修(屬回饋管理鏡像消費端的 bug,非本頁設定頁本身缺陷;修復建議另開 bug session)- 平台層共用、非租戶級:DEV DB 實查本頁兩列
tenant_id=1(ROOT),回饋鏡像消費端固定用read_root_config_value繞 RLS 讀 ROOT tenant——即使頁面本身可能被非 root 租戶使用者存取(見 §3 無角色守門),寫入的設定實際上對全系統所有租戶的回饋鏡像生效,非該使用者所屬租戶專屬設定 - 與 _overview
§5、ldap-config.md §3
一致的通病:
system_configs系列設定頁 BE 端點普遍僅@jwt_required(),本頁不例外
13. 開發與驗證
- 跑起來:BE
python main_socketio.py(port 8000,log 在log/app.log);FE 在compliance-manager-fe/起 Vite dev server。BE 改 service code 後必須重啟(無 hot reload;因FeedbackService/IssueService建構時快取設定,改設定後是否需重啟才生效視 DI 生命週期而定,未實測確認) - 測試帳號:系統管理員帳號見 memory
reference_dev_login/.env(憑證不寫入本文件) - 導航路徑:登入 →
側選單「系統管理」→「議題整合設定」(
/system/issue-integrate-config) - 前置資料:無強制前置資料;DEV DB 已有
ISSUE_INTEGRATE_CONFIG的 GITLAB / GITHUB 兩列(tenant_id=1) - 驗證鏡像效果需搭配:回饋管理頁提交 / 編輯 /
刪除回饋,觀察是否成功建立 / 更新 / 關閉對應外部
issue(best-effort,失敗只在
log/app.log出現 warning) - 相關文件:回饋管理
spec(鏡像消費端);
system_configs共用慣例 _overview §4;同機制姊妹頁 LDAP 設定;DB / API 全量見 GAI-SD-02/03