fix(awooop): 脫敏 tenants 風險管控顯示
All checks were successful
Code Review / ai-code-review (push) Successful in 15s
CD Pipeline / tests (push) Successful in 1m39s
CD Pipeline / build-and-deploy (push) Successful in 5m52s
CD Pipeline / post-deploy-checks (push) Successful in 1m57s

This commit is contained in:
Your Name
2026-06-15 03:05:53 +08:00
parent 634dadac70
commit d388e5b477
4 changed files with 138 additions and 13 deletions

View File

@@ -8632,7 +8632,9 @@
"labels": {
"routes": "入口",
"repos": "來源",
"gates": "閘門"
"gates": "閘門",
"redactedScope": "已脫敏範圍",
"blockers": "阻塞項 {count}"
},
"columns": {
"domain": "網站 / 服務入口",
@@ -8640,8 +8642,9 @@
"status": "納管狀態",
"routeShape": "入口形態",
"repo": "範圍代號",
"risk": "風險",
"readiness": "就緒狀態"
"risk": "風險等級",
"readiness": "管控狀態",
"nextControl": "下一步管控"
},
"categories": {
"corePlatform": "核心平台",
@@ -8659,6 +8662,26 @@
"readOnlyPendingSmoke": "待 smoke 證據",
"ownerResponseRequired": "待負責人回覆",
"unknown": "待分類"
},
"sourceRisk": {
"high": "高風險",
"medium": "中風險",
"low": "低風險",
"unknown": "待分級"
},
"sourceReadiness": {
"refsParity": "待參照一致性證據",
"targetDecision": "待目標與可見性決策",
"internalRemoteDecision": "待內部 remote 用途決策",
"scopeReview": "待範圍審查",
"unknown": "待負責人補證"
},
"sourceActions": {
"refsParity": "先取得分支 / 標籤參照一致性、部署真相來源與 rollback owner 的脫敏證據;未接受前不得同步 refs 或切主來源。",
"targetDecision": "先取得 GitHub 目標、可見性、標準負責人與後續 owner 的脫敏回覆;未接受前不得建立專案庫或改可見性。",
"internalRemoteDecision": "先確認內部 remote 用途、維運 owner、移除或保留理由與 rollback 方式;未接受前不得改 remote、workflow 或 secret。",
"scopeReview": "先確認是否納入 IwoooS 管控範圍;未接受前只保留觀測,不升級為主來源候選。",
"unknown": "先補 owner role / team、decision、decision reason、affected scope 與 redacted evidence refs。"
}
},
"securityTenantScopeCandidate": {

View File

@@ -8632,7 +8632,9 @@
"labels": {
"routes": "入口",
"repos": "來源",
"gates": "閘門"
"gates": "閘門",
"redactedScope": "已脫敏範圍",
"blockers": "阻塞項 {count}"
},
"columns": {
"domain": "網站 / 服務入口",
@@ -8640,8 +8642,9 @@
"status": "納管狀態",
"routeShape": "入口形態",
"repo": "範圍代號",
"risk": "風險",
"readiness": "就緒狀態"
"risk": "風險等級",
"readiness": "管控狀態",
"nextControl": "下一步管控"
},
"categories": {
"corePlatform": "核心平台",
@@ -8659,6 +8662,26 @@
"readOnlyPendingSmoke": "待 smoke 證據",
"ownerResponseRequired": "待負責人回覆",
"unknown": "待分類"
},
"sourceRisk": {
"high": "高風險",
"medium": "中風險",
"low": "低風險",
"unknown": "待分級"
},
"sourceReadiness": {
"refsParity": "待參照一致性證據",
"targetDecision": "待目標與可見性決策",
"internalRemoteDecision": "待內部 remote 用途決策",
"scopeReview": "待範圍審查",
"unknown": "待負責人補證"
},
"sourceActions": {
"refsParity": "先取得分支 / 標籤參照一致性、部署真相來源與 rollback owner 的脫敏證據;未接受前不得同步 refs 或切主來源。",
"targetDecision": "先取得 GitHub 目標、可見性、標準負責人與後續 owner 的脫敏回覆;未接受前不得建立專案庫或改可見性。",
"internalRemoteDecision": "先確認內部 remote 用途、維運 owner、移除或保留理由與 rollback 方式;未接受前不得改 remote、workflow 或 secret。",
"scopeReview": "先確認是否納入 IwoooS 管控範圍;未接受前只保留觀測,不升級為主來源候選。",
"unknown": "先補 owner role / team、decision、decision reason、affected scope 與 redacted evidence refs。"
}
},
"securityTenantScopeCandidate": {

View File

@@ -268,6 +268,26 @@ const ASSET_STATUS_KEYS: Record<string, string> = {
owner_response_required: "ownerResponseRequired",
};
const SOURCE_RISK_KEYS: Record<string, string> = {
HIGH: "high",
MEDIUM: "medium",
LOW: "low",
};
const SOURCE_READINESS_KEYS: Record<string, string> = {
blocked_waiting_refs_parity: "refsParity",
blocked_waiting_target_decision: "targetDecision",
blocked_waiting_internal_remote_decision: "internalRemoteDecision",
observe_scope_review: "scopeReview",
};
const SOURCE_ACTION_KEYS: Record<string, string> = {
blocked_waiting_refs_parity: "refsParity",
blocked_waiting_target_decision: "targetDecision",
blocked_waiting_internal_remote_decision: "internalRemoteDecision",
observe_scope_review: "scopeReview",
};
function assetToneClass(tone: AssetTone) {
if (tone === "steady") return "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]";
if (tone === "locked") return "border-[#b7add8] bg-[#f8f6fc] text-[#5b4b91]";
@@ -295,6 +315,28 @@ function assetCategoryLabel(category: string, t: ReturnType<typeof useTranslatio
return t(`categories.${key}` as never);
}
function sourceRiskLabel(risk: string, t: ReturnType<typeof useTranslations>) {
const key = SOURCE_RISK_KEYS[risk] ?? "unknown";
return t(`sourceRisk.${key}` as never);
}
function sourceReadinessLabel(readiness: string, t: ReturnType<typeof useTranslations>) {
const key = SOURCE_READINESS_KEYS[readiness] ?? "unknown";
return t(`sourceReadiness.${key}` as never);
}
function sourceActionLabel(readiness: string, t: ReturnType<typeof useTranslations>) {
const key = SOURCE_ACTION_KEYS[readiness] ?? "unknown";
return t(`sourceActions.${key}` as never);
}
function sourceRiskClass(risk: string) {
if (risk === "HIGH") return "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]";
if (risk === "MEDIUM") return "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]";
if (risk === "LOW") return "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]";
return "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]";
}
const MIGRATION_MODE_CONFIG: Record<
MigrationMode,
{ label: string; bg: string; text: string; border: string }
@@ -954,23 +996,41 @@ function GlobalAssetCoveragePanel({ inventory }: { inventory: TenantAssetInvento
{t("reposTitle")}
</div>
<div className="overflow-x-auto">
<table className="w-full min-w-[760px] text-left text-xs">
<table className="w-full min-w-[860px] text-left text-xs">
<thead className="bg-[#faf9f3] text-[#77736a]">
<tr>
<th className="px-3 py-2 font-semibold">{t("columns.repo")}</th>
<th className="px-3 py-2 font-semibold">{t("columns.product")}</th>
<th className="px-3 py-2 font-semibold">{t("columns.risk")}</th>
<th className="px-3 py-2 font-semibold">{t("columns.readiness")}</th>
<th className="px-3 py-2 font-semibold">{t("columns.nextControl")}</th>
</tr>
</thead>
<tbody className="divide-y divide-[#eee9dd]">
{sourceRepos.map((repo) => (
<tr key={`${repo.source_scope_id}:${repo.product_id}`}>
<td className="px-3 py-2 font-mono text-[#141413]">{repo.source_scope_id}</td>
<td className="px-3 py-2">
<div className="font-mono text-[#141413]">{repo.source_scope_id}</div>
<div className="mt-1 text-[10px] font-semibold text-[#77736a]">
{t("labels.redactedScope")}
</div>
</td>
<td className="px-3 py-2 text-[#5f5b52]">{repo.product_name}</td>
<td className="px-3 py-2 font-mono text-[#5f5b52]">{repo.risk}</td>
<td className="px-3 py-2 font-mono text-[#5f5b52]">
{repo.readiness_state} · blockers={repo.blocker_count} · namespace_redacted={String(repo.source_namespace_redacted)}
<td className="px-3 py-2">
<span className={cn("inline-flex border px-2 py-0.5 font-semibold", sourceRiskClass(repo.risk))}>
{sourceRiskLabel(repo.risk, t)}
</span>
</td>
<td className="px-3 py-2 text-[#5f5b52]">
<div className="font-semibold text-[#141413]">
{sourceReadinessLabel(repo.readiness_state, t)}
</div>
<div className="mt-1 font-mono text-[11px] text-[#77736a]">
{t("labels.blockers", { count: repo.blocker_count })}
</div>
</td>
<td className="px-3 py-2 text-[#5f5b52]">
{sourceActionLabel(repo.readiness_state, t)}
</td>
</tr>
))}

View File

@@ -11672,9 +11672,19 @@ def validate(root: Path) -> None:
for text in [
"source_scope_id",
"source_namespace_redacted",
"namespace_redacted={String(repo.source_namespace_redacted)}",
"sourceRiskLabel(repo.risk, t)",
"sourceReadinessLabel(repo.readiness_state, t)",
"sourceActionLabel(repo.readiness_state, t)",
"redactedScope",
"nextControl",
]:
assert_text_contains("awooop_tenants_page.source_namespace_redaction", awooop_tenants_page, text)
for text in [
"{repo.risk}",
"{repo.readiness_state}",
"namespace_redacted={String(repo.source_namespace_redacted)}",
]:
assert_text_not_contains("awooop_tenants_page.raw_source_control_status_render", awooop_tenants_page, text)
assert_text_not_contains(
"awooop_tenants_page.raw_repo_namespace_personal_owner",
awooop_tenants_page,
@@ -11703,7 +11713,16 @@ def validate(root: Path) -> None:
tenant_global_assets_messages_en = json.dumps(
web_messages_en["awooop"]["tenants"]["globalAssets"], ensure_ascii=False
)
for text in ["脫敏原始碼範圍", "範圍代號", "脫敏範圍代號"]:
for text in [
"脫敏原始碼範圍",
"範圍代號",
"脫敏範圍代號",
"已脫敏範圍",
"下一步管控",
"待參照一致性證據",
"待目標與可見性決策",
"未接受前不得建立專案庫或改可見性",
]:
assert_text_contains("web_messages.zh-TW.awooop.tenants.global_assets_redaction", tenant_global_assets_messages_zh, text)
assert_text_contains("web_messages.en.awooop.tenants.global_assets_redaction", tenant_global_assets_messages_en, text)