feat(web): show GitHub readiness in AwoooP tenants
This commit is contained in:
@@ -3955,6 +3955,40 @@
|
||||
"hostCoverage": "The three named hosts are included only for security visibility and evidence readiness; no SSH, updates, credentialed scans, or blocking controls are performed.",
|
||||
"ownerResponse": "S4.9-S4.12 owner response received / accepted remain 0. Tenant scope display is not approval."
|
||||
}
|
||||
},
|
||||
"githubTenantReadinessScope": {
|
||||
"title": "GitHub Primary Readiness Tenant Scope",
|
||||
"subtitle": "Tenant management mirrors the source-control owner scope gap between the AWOOOI first tenant and the Gitea-to-GitHub path. This is not tenant policy, repo creation, or primary switch authorization.",
|
||||
"badge": "Read-only scope",
|
||||
"openIwooos": "Open IwoooS",
|
||||
"scopeRefsTitle": "Owner Scope Refs",
|
||||
"boundaryLabel": "Tenant / GitHub Boundary",
|
||||
"boundaryTitle": "Tenant scope still waits for owner response",
|
||||
"boundaryDetail": "This panel only displays the relation between tenant scope and source-control readiness. It does not change tenant migration mode, modify tenant policy, create GitHub repos, mutate refs, collect secret values, switch primary, or disable Gitea.",
|
||||
"metrics": {
|
||||
"candidateRepos": {
|
||||
"label": "Candidate Repos",
|
||||
"detail": "8 candidate repos are scope visibility only and do not authorize GitHub repo creation."
|
||||
},
|
||||
"inScopeRepos": {
|
||||
"label": "In-scope Repos",
|
||||
"detail": "7 repos still require owner scope decision and source-control response."
|
||||
},
|
||||
"ownerResponses": {
|
||||
"label": "Owner Responses",
|
||||
"detail": "22 response templates remain 0 received / 0 accepted."
|
||||
},
|
||||
"tenantScopeChanges": {
|
||||
"label": "Tenant Scope Changes",
|
||||
"detail": "Currently 0. Readiness display must not change tenant policy or migration mode."
|
||||
}
|
||||
},
|
||||
"scopeRefs": {
|
||||
"tenantSourceScope": "The AWOOOI first tenant only maps to source-control readiness scope; it does not mean the whole platform or GitHub primary is accepted.",
|
||||
"giteaInventoryOwner": "S4.9 still waits for Gitea inventory owner attestation. Repo scope must not be filled as accepted before coverage is accepted.",
|
||||
"githubTargetOwner": "S4.10 still waits for GitHub target owner decision. Do not create repos or change visibility before the target owner accepts.",
|
||||
"workflowSecretOwner": "S4.12 only waits for workflow / secret name owner response. Secret values must not be collected and GitHub secrets must not be changed."
|
||||
}
|
||||
}
|
||||
},
|
||||
"runs": {
|
||||
|
||||
@@ -3956,6 +3956,40 @@
|
||||
"hostCoverage": "三台指定主機目前只納入資安視野與 evidence readiness;不做 SSH、更新、credentialed scan 或 blocking control。",
|
||||
"ownerResponse": "S4.9-S4.12 owner response received / accepted 仍為 0,tenant scope 顯示不等於批准。"
|
||||
}
|
||||
},
|
||||
"githubTenantReadinessScope": {
|
||||
"title": "GitHub Primary Readiness 租戶範圍",
|
||||
"subtitle": "租戶管理同步顯示 AWOOOI first tenant 與 Gitea 轉 GitHub 的 source-control owner scope 缺口;這不是租戶 policy、repo 建立或 primary 切換授權。",
|
||||
"badge": "只讀 scope",
|
||||
"openIwooos": "開啟 IwoooS",
|
||||
"scopeRefsTitle": "Owner scope refs",
|
||||
"boundaryLabel": "租戶 / GitHub 邊界",
|
||||
"boundaryTitle": "租戶範圍仍等待 owner response",
|
||||
"boundaryDetail": "這個面板只呈現租戶與 source-control readiness 的對應關係;不變更 tenant migration mode、不改 tenant policy、不建立 GitHub repo、不改 refs、不收 secret value、不切 primary,也不停用 Gitea。",
|
||||
"metrics": {
|
||||
"candidateRepos": {
|
||||
"label": "Candidate repos",
|
||||
"detail": "8 個候選 repo 只作為 scope visibility,不代表可建立 GitHub repo。"
|
||||
},
|
||||
"inScopeRepos": {
|
||||
"label": "In-scope repos",
|
||||
"detail": "7 個 repo 仍需 owner scope decision 與 source-control response。"
|
||||
},
|
||||
"ownerResponses": {
|
||||
"label": "Owner responses",
|
||||
"detail": "22 個 response templates 仍為 0 received / 0 accepted。"
|
||||
},
|
||||
"tenantScopeChanges": {
|
||||
"label": "Tenant scope changes",
|
||||
"detail": "目前為 0;不得因顯示 readiness 而改 tenant policy 或 migration mode。"
|
||||
}
|
||||
},
|
||||
"scopeRefs": {
|
||||
"tenantSourceScope": "AWOOOI first tenant 只對應 source-control readiness 範圍,不代表全平台或 GitHub primary 已接受。",
|
||||
"giteaInventoryOwner": "S4.9 仍等待 Gitea inventory owner attestation;coverage accepted 前不得補寫 repo scope。",
|
||||
"githubTargetOwner": "S4.10 仍等待 GitHub target owner decision;target owner 未接受前不得建立 repo 或改 visibility。",
|
||||
"workflowSecretOwner": "S4.12 只等待 workflow / secret name owner response;仍不得收 secret value 或修改 GitHub secrets。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"runs": {
|
||||
|
||||
@@ -16,6 +16,10 @@ import {
|
||||
DollarSign,
|
||||
Ban,
|
||||
CheckCircle2,
|
||||
GitBranch,
|
||||
ListChecks,
|
||||
Lock,
|
||||
SearchCheck,
|
||||
ShieldCheck,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -46,12 +50,67 @@ interface ApiResponse {
|
||||
total: number;
|
||||
}
|
||||
|
||||
type GitHubTenantReadinessMetric = {
|
||||
key: string;
|
||||
value: string;
|
||||
icon: typeof ShieldCheck;
|
||||
tone: "neutral" | "warn";
|
||||
};
|
||||
|
||||
type GitHubTenantReadinessScope = {
|
||||
key: string;
|
||||
name: string;
|
||||
status: string;
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// 常數
|
||||
// =============================================================================
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? "";
|
||||
|
||||
const githubTenantReadinessMetrics: GitHubTenantReadinessMetric[] = [
|
||||
{ key: "candidateRepos", value: "8", icon: GitBranch, tone: "neutral" },
|
||||
{ key: "inScopeRepos", value: "7", icon: ListChecks, tone: "warn" },
|
||||
{ key: "ownerResponses", value: "0/22", icon: ShieldCheck, tone: "warn" },
|
||||
{ key: "tenantScopeChanges", value: "0", icon: Lock, tone: "warn" },
|
||||
];
|
||||
|
||||
const githubTenantReadinessScopes: GitHubTenantReadinessScope[] = [
|
||||
{
|
||||
key: "tenantSourceScope",
|
||||
name: "AWOOOI first tenant source-control scope",
|
||||
status: "waiting",
|
||||
},
|
||||
{
|
||||
key: "giteaInventoryOwner",
|
||||
name: "S4.9 Gitea inventory owner attestation",
|
||||
status: "0/5",
|
||||
},
|
||||
{
|
||||
key: "githubTargetOwner",
|
||||
name: "S4.10 GitHub target owner decision",
|
||||
status: "0/7",
|
||||
},
|
||||
{
|
||||
key: "workflowSecretOwner",
|
||||
name: "S4.12 workflow / secret name owner response",
|
||||
status: "0/5",
|
||||
},
|
||||
];
|
||||
|
||||
const githubTenantReadinessBoundaries = [
|
||||
"tenant_source_control_scope_accepted=false",
|
||||
"repo_owner_response_accepted=false",
|
||||
"repo_creation_authorized=false",
|
||||
"refs_mutation_authorized=false",
|
||||
"github_primary_switch_authorized=false",
|
||||
"gitea_disablement_authorized=false",
|
||||
"tenant_policy_mutation_authorized=false",
|
||||
"runtime_execution_authorized=false",
|
||||
"action_buttons_allowed=false",
|
||||
];
|
||||
|
||||
const MIGRATION_MODE_CONFIG: Record<
|
||||
MigrationMode,
|
||||
{ label: string; bg: string; text: string; border: string }
|
||||
@@ -312,6 +371,106 @@ function SecurityTenantScopeCandidatePanel() {
|
||||
);
|
||||
}
|
||||
|
||||
function GitHubTenantReadinessScopePanel() {
|
||||
const t = useTranslations("awooop.tenants.githubTenantReadinessScope");
|
||||
|
||||
return (
|
||||
<section className="overflow-hidden border border-[#e0ddd4] bg-white">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3 border-b border-[#e0ddd4] bg-[#faf9f3] px-4 py-3">
|
||||
<div className="flex min-w-0 items-start gap-2">
|
||||
<GitBranch className="mt-0.5 h-4 w-4 shrink-0 text-[#d97757]" aria-hidden="true" />
|
||||
<div className="min-w-0">
|
||||
<h3 className="text-sm font-semibold text-[#141413]">{t("title")}</h3>
|
||||
<p className="mt-1 max-w-3xl text-xs leading-5 text-[#77736a]">{t("subtitle")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="inline-flex border border-[#d9b36f] bg-[#fff7e8] px-2 py-0.5 text-xs font-semibold text-[#8a5a08]">
|
||||
{t("badge")}
|
||||
</span>
|
||||
<Link
|
||||
href="/iwooos"
|
||||
className="inline-flex items-center gap-2 border border-[#d8d3c7] bg-white px-3 py-1.5 text-xs font-semibold text-[#141413] hover:border-[#d97757]"
|
||||
>
|
||||
{t("openIwooos")}
|
||||
<ArrowRight className="h-3.5 w-3.5 text-[#77736a]" aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-px bg-[#e0ddd4] md:grid-cols-2 xl:grid-cols-4">
|
||||
{githubTenantReadinessMetrics.map((metric) => {
|
||||
const Icon = metric.icon;
|
||||
return (
|
||||
<div key={metric.key} className="min-h-[122px] bg-white px-4 py-3">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-semibold text-[#77736a]">
|
||||
{t(`metrics.${metric.key}.label` as never)}
|
||||
</p>
|
||||
<p className="mt-2 break-words font-mono text-2xl font-semibold text-[#141413]">
|
||||
{metric.value}
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
className={cn(
|
||||
"flex h-8 w-8 shrink-0 items-center justify-center border",
|
||||
metric.tone === "warn" && "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
|
||||
metric.tone === "neutral" && "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]"
|
||||
)}
|
||||
>
|
||||
<Icon className="h-4 w-4" aria-hidden="true" />
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-3 text-xs leading-5 text-[#5f5b52]">
|
||||
{t(`metrics.${metric.key}.detail` as never)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="grid min-w-0 gap-4 p-4 xl:grid-cols-[1.1fr_0.9fr]">
|
||||
<div className="min-w-0 border border-[#e0ddd4] bg-white">
|
||||
<div className="border-b border-[#e0ddd4] bg-[#faf9f3] px-4 py-3 text-xs font-semibold text-[#141413]">
|
||||
{t("scopeRefsTitle")}
|
||||
</div>
|
||||
<div className="divide-y divide-[#eee9dd]">
|
||||
{githubTenantReadinessScopes.map((scope) => (
|
||||
<div key={scope.key} className="grid gap-3 px-4 py-3 md:grid-cols-[minmax(0,1fr)_96px]">
|
||||
<div className="min-w-0">
|
||||
<p className="break-words font-mono text-xs font-semibold text-[#141413]">
|
||||
{scope.name}
|
||||
</p>
|
||||
<p className="mt-1 text-xs leading-5 text-[#5f5b52]">
|
||||
{t(`scopeRefs.${scope.key}` as never)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="inline-flex border border-[#d9b36f] bg-[#fff7e8] px-2 py-0.5 font-mono text-xs font-semibold text-[#8a5a08]">
|
||||
{scope.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 border border-[#e0ddd4] bg-[#faf9f3] px-4 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{t("boundaryLabel")}</p>
|
||||
<p className="mt-2 text-sm font-semibold text-[#141413]">{t("boundaryTitle")}</p>
|
||||
<p className="mt-2 text-xs leading-5 text-[#5f5b52]">{t("boundaryDetail")}</p>
|
||||
<div className="mt-4 grid gap-2 break-all font-mono text-xs text-[#141413]">
|
||||
{githubTenantReadinessBoundaries.map((boundary) => (
|
||||
<span key={boundary}>{boundary}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Main Component
|
||||
// =============================================================================
|
||||
@@ -366,6 +525,8 @@ export default function TenantsPage() {
|
||||
|
||||
<SecurityTenantScopeCandidatePanel />
|
||||
|
||||
<GitHubTenantReadinessScopePanel />
|
||||
|
||||
{/* Error State */}
|
||||
{error && (
|
||||
<div className="flex items-start gap-3 p-4 bg-red-900/20 border border-red-800/40 rounded-lg">
|
||||
|
||||
Reference in New Issue
Block a user