diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index d9d4c76e..33c12e4c 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -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": { diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index d9d4c76e..33c12e4c 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -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": { diff --git a/apps/web/src/app/[locale]/awooop/tenants/page.tsx b/apps/web/src/app/[locale]/awooop/tenants/page.tsx index 0a679c6d..63feb256 100644 --- a/apps/web/src/app/[locale]/awooop/tenants/page.tsx +++ b/apps/web/src/app/[locale]/awooop/tenants/page.tsx @@ -268,6 +268,26 @@ const ASSET_STATUS_KEYS: Record = { owner_response_required: "ownerResponseRequired", }; +const SOURCE_RISK_KEYS: Record = { + HIGH: "high", + MEDIUM: "medium", + LOW: "low", +}; + +const SOURCE_READINESS_KEYS: Record = { + 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 = { + 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) { + const key = SOURCE_RISK_KEYS[risk] ?? "unknown"; + return t(`sourceRisk.${key}` as never); +} + +function sourceReadinessLabel(readiness: string, t: ReturnType) { + const key = SOURCE_READINESS_KEYS[readiness] ?? "unknown"; + return t(`sourceReadiness.${key}` as never); +} + +function sourceActionLabel(readiness: string, t: ReturnType) { + 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")}
- +
+ {sourceRepos.map((repo) => ( - + - - + + ))} diff --git a/scripts/security/security-mirror-progress-guard.py b/scripts/security/security-mirror-progress-guard.py index 87982c3b..7404c68a 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -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)
{t("columns.repo")} {t("columns.product")} {t("columns.risk")} {t("columns.readiness")}{t("columns.nextControl")}
{repo.source_scope_id} +
{repo.source_scope_id}
+
+ {t("labels.redactedScope")} +
+
{repo.product_name}{repo.risk} - {repo.readiness_state} · blockers={repo.blocker_count} · namespace_redacted={String(repo.source_namespace_redacted)} + + + {sourceRiskLabel(repo.risk, t)} + + +
+ {sourceReadinessLabel(repo.readiness_state, t)} +
+
+ {t("labels.blockers", { count: repo.blocker_count })} +
+
+ {sourceActionLabel(repo.readiness_state, t)}