feat(web): show IwoooS tenant scope in AwoooP

This commit is contained in:
Your Name
2026-05-20 19:31:18 +08:00
parent b088f77bee
commit e71a25c85d
8 changed files with 310 additions and 3 deletions

View File

@@ -3764,6 +3764,34 @@
"item4": "Open the AwoooP work map"
}
},
"tenants": {
"securityTenantScopeCandidate": {
"title": "IwoooS Tenant Security Scope Read-only Candidate",
"subtitle": "Tenant management only displays the protection scope for the AWOOOI first tenant and the IwoooS security mirror. This is not a migration mode change and does not modify tenant policy.",
"badge": "Tenant scope",
"scopeRefsTitle": "Read-only scope refs",
"boundaryLabel": "Tenant Boundary",
"boundaryTitle": "No tenant settings are changeable here",
"boundaryDetail": "This panel does not change migration mode, modify tenant policy, write to the platform tenants API, call GitHub / Gitea / Kali, or add scan, execute, deploy, primary switch, or refs actions.",
"openIwooos": "Open IwoooS",
"metrics": {
"primaryTenant": "Primary Tenant",
"primaryTenantDetail": "AWOOOI is the first runtime tenant in AwoooP. This only displays scope and does not change settings.",
"securityEntry": "Security Entry",
"securityEntryDetail": "IwoooS remains the read-only Information Security entrypoint and posture mirror.",
"hostCoverage": "Host coverage",
"hostCoverageDetail": "Kali 112, Dev 168, and Dev 111 are in observe-only view.",
"policyMutations": "Tenant policy changes",
"policyMutationsDetail": "Currently 0. Do not change policy before owner response and a runtime gate."
},
"scopeRefs": {
"awoooiTenant": "Under the AwoooP platform identity, AWOOOI remains the first tenant / runtime host, not a synonym for the whole platform.",
"iwooosMirror": "IwoooS displays security mirror posture, progress, evidence refs, and forbidden actions.",
"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."
}
}
},
"contracts": {
"securityContractCandidate": {
"title": "IwoooS Security Contract Read-only Candidate",

View File

@@ -3765,6 +3765,34 @@
"item4": "查看 AwoooP 工作鏈路地圖"
}
},
"tenants": {
"securityTenantScopeCandidate": {
"title": "IwoooS 租戶資安範圍只讀候選",
"subtitle": "租戶管理只顯示 AWOOOI 第一租戶與 IwoooS security mirror 的保護範圍;這不是 migration mode change也不會改 tenant policy。",
"badge": "租戶範圍",
"scopeRefsTitle": "只讀 scope refs",
"boundaryLabel": "租戶邊界",
"boundaryTitle": "目前沒有租戶設定可變更",
"boundaryDetail": "這個面板不變更 migration mode、不改 tenant policy、不寫入 platform tenants API、不呼叫 GitHub / Gitea / Kali也不新增 scan、execute、deploy、primary switch 或 refs action。",
"openIwooos": "開啟 IwoooS",
"metrics": {
"primaryTenant": "第一租戶",
"primaryTenantDetail": "AWOOOI 是 AwoooP 的第一個 runtime tenant此處只顯示 scope不改設定。",
"securityEntry": "資安入口",
"securityEntryDetail": "IwoooS 仍是 Information Security 的只讀入口與 posture mirror。",
"hostCoverage": "Host coverage",
"hostCoverageDetail": "Kali 112、Dev 168、Dev 111 已納入 observe-only 視野。",
"policyMutations": "Tenant policy changes",
"policyMutationsDetail": "目前為 0owner response 與 runtime gate 前不得改 policy。"
},
"scopeRefs": {
"awoooiTenant": "AwoooP 平台身分下AWOOOI 仍是第一個 tenant / runtime host不是全平台的同義詞。",
"iwooosMirror": "IwoooS 顯示 security mirror posture、progress、evidence refs 與 forbidden actions。",
"hostCoverage": "三台指定主機目前只納入資安視野與 evidence readiness不做 SSH、更新、credentialed scan 或 blocking control。",
"ownerResponse": "S4.9-S4.12 owner response received / accepted 仍為 0tenant scope 顯示不等於批准。"
}
}
},
"contracts": {
"securityContractCandidate": {
"title": "IwoooS 資安契約只讀候選",

View File

@@ -6,13 +6,17 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { useTranslations } from "next-intl";
import { Link } from "@/i18n/routing";
import {
ArrowRight,
Building2,
RefreshCw,
AlertCircle,
DollarSign,
Ban,
CheckCircle2,
ShieldCheck,
} from "lucide-react";
import { cn } from "@/lib/utils";
@@ -162,6 +166,152 @@ function TenantRow({ tenant }: { tenant: Tenant }) {
);
}
function SecurityTenantScopeCandidatePanel() {
const t = useTranslations("awooop.tenants.securityTenantScopeCandidate");
const metrics = [
{
label: t("metrics.primaryTenant"),
value: "AWOOOI",
detail: t("metrics.primaryTenantDetail"),
},
{
label: t("metrics.securityEntry"),
value: "IwoooS",
detail: t("metrics.securityEntryDetail"),
},
{
label: t("metrics.hostCoverage"),
value: "3",
detail: t("metrics.hostCoverageDetail"),
},
{
label: t("metrics.policyMutations"),
value: "0",
detail: t("metrics.policyMutationsDetail"),
},
];
const scopes = [
{
name: "AWOOOI first tenant",
detail: t("scopeRefs.awoooiTenant"),
},
{
name: "IwoooS security mirror",
detail: t("scopeRefs.iwooosMirror"),
},
{
name: "Kali 112 / Dev 168 / Dev 111",
detail: t("scopeRefs.hostCoverage"),
},
{
name: "S4.9-S4.12 owner response waiting",
detail: t("scopeRefs.ownerResponse"),
},
];
return (
<section className="overflow-hidden border border-[#e0ddd4] bg-white">
<div className="sm:hidden">
<div className="border-b border-[#e0ddd4] bg-[#faf9f3] px-2 py-2">
<p className="text-[11px] font-semibold leading-4 text-[#141413]">{t("title")}</p>
<span className="mt-1 inline-flex border border-[#9bb6d9] bg-[#eef5ff] px-1.5 py-0.5 text-[10px] font-semibold text-[#1f5b9b]">
{t("badge")}
</span>
</div>
<div className="grid gap-px bg-[#e0ddd4] text-[10px]">
<div className="bg-white px-2 py-2">
<p className="font-semibold text-[#77736a]">AWOOOI</p>
<p className="mt-1 font-mono text-lg font-semibold text-[#141413]">tenant</p>
</div>
<div className="bg-white px-2 py-2">
<p className="font-semibold text-[#77736a]">IwoooS</p>
<p className="mt-1 font-mono text-lg font-semibold text-[#141413]">mirror</p>
</div>
<div className="bg-white px-2 py-2">
<p className="font-semibold text-[#77736a]">hosts</p>
<p className="mt-1 font-mono text-lg font-semibold text-[#141413]">3</p>
</div>
<div className="bg-white px-2 py-2">
<p className="font-semibold text-[#77736a]">policy</p>
<p className="mt-1 font-mono text-lg font-semibold text-[#141413]">0</p>
</div>
</div>
<div className="space-y-2 bg-[#faf9f3] px-2 py-2 text-[10px] leading-4 text-[#5f5b52]">
<p className="font-semibold text-[#141413]">{t("boundaryTitle")}</p>
<p>4 gates = false</p>
<Link
href="/iwooos"
className="inline-flex items-center gap-1 border border-[#d8d3c7] bg-white px-2 py-1 font-semibold text-[#141413] hover:border-[#d97757]"
>
IwoooS
<ArrowRight className="h-3 w-3 text-[#77736a]" aria-hidden="true" />
</Link>
</div>
</div>
<div className="hidden sm:block">
<div className="flex flex-wrap items-start justify-between gap-3 border-b border-[#e0ddd4] bg-[#faf9f3] px-4 py-3">
<div className="flex items-start gap-2">
<ShieldCheck className="mt-0.5 h-4 w-4 text-[#d97757]" aria-hidden="true" />
<div>
<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>
<span className="inline-flex border border-[#9bb6d9] bg-[#eef5ff] px-2 py-0.5 text-xs font-semibold text-[#1f5b9b]">
{t("badge")}
</span>
</div>
<div className="grid gap-px bg-[#e0ddd4] md:grid-cols-2 xl:grid-cols-4">
{metrics.map((item) => (
<div key={item.label} className="bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{item.label}</p>
<p className="mt-2 break-words font-mono text-2xl font-semibold text-[#141413]">{item.value}</p>
<p className="mt-2 text-xs leading-5 text-[#5f5b52]">{item.detail}</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]">
{scopes.map((item) => (
<div key={item.name} className="px-4 py-3">
<p className="break-words font-mono text-xs font-semibold text-[#141413]">{item.name}</p>
<p className="mt-1 text-xs leading-5 text-[#5f5b52]">{item.detail}</p>
</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 font-mono text-xs text-[#141413]">
<span>tenant_migration_mode_changed=false</span>
<span>tenant_policy_mutation_authorized=false</span>
<span>runtime_execution_authorized=false</span>
<span>action_buttons_allowed=false</span>
</div>
<Link
href="/iwooos"
className="mt-4 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>
</section>
);
}
// =============================================================================
// Main Component
// =============================================================================
@@ -214,6 +364,8 @@ export default function TenantsPage() {
</button>
</div>
<SecurityTenantScopeCandidatePanel />
{/* Error State */}
{error && (
<div className="flex items-start gap-3 p-4 bg-red-900/20 border border-red-800/40 rounded-lg">