feat(web): bridge security pages to IwoooS
This commit is contained in:
@@ -990,7 +990,35 @@
|
||||
"issue": "Issue",
|
||||
"count": "Count",
|
||||
"error": "Load failed",
|
||||
"noData": "No security events"
|
||||
"noData": "No security events",
|
||||
"iwooosBridge": {
|
||||
"title": "Included in the IwoooS read-only security mirror",
|
||||
"subtitle": "This existing security / compliance page now shows its IwoooS scope in reverse; it only displays progress, framework maturity, and runtime boundaries without adding scan, repair, approval, or deploy buttons.",
|
||||
"compactTitle": "IwoooS",
|
||||
"compactDetail": "58% / gate 0",
|
||||
"openIwooos": "Open IwoooS",
|
||||
"sourceLabel": "Integration source",
|
||||
"sourceDetail": "SecurityPanel, CompliancePanel, standalone /security, and /compliance keep their original data sources; IwoooS only indexes the security mesh and aggregates mirror-only posture.",
|
||||
"boundaryLabel": "Execution boundary",
|
||||
"metrics": {
|
||||
"overall": {
|
||||
"label": "Overall mesh",
|
||||
"detail": "headline progress"
|
||||
},
|
||||
"framework": {
|
||||
"label": "Framework maturity",
|
||||
"detail": "docs / schema / read-only evidence"
|
||||
},
|
||||
"runtimeGates": {
|
||||
"label": "Runtime gates",
|
||||
"detail": "not active"
|
||||
},
|
||||
"actions": {
|
||||
"label": "Action buttons",
|
||||
"detail": "not provided"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"iwooos": {
|
||||
"eyebrow": "Information Security Mesh",
|
||||
|
||||
@@ -991,7 +991,35 @@
|
||||
"issue": "問題",
|
||||
"count": "次數",
|
||||
"error": "載入失敗",
|
||||
"noData": "無安全事件"
|
||||
"noData": "無安全事件",
|
||||
"iwooosBridge": {
|
||||
"title": "已納入 IwoooS 只讀資安鏡像",
|
||||
"subtitle": "這個既有安全 / 合規頁面現在會反向標示 IwoooS 納管狀態;只顯示整體進度、框架成熟度與 runtime 邊界,不新增掃描、修復、批准或部署按鈕。",
|
||||
"compactTitle": "IwoooS",
|
||||
"compactDetail": "58% / gate 0",
|
||||
"openIwooos": "開啟 IwoooS",
|
||||
"sourceLabel": "整合來源",
|
||||
"sourceDetail": "SecurityPanel、CompliancePanel、standalone /security 與 /compliance 仍保留原本資料來源;IwoooS 只做資安網索引與 mirror-only 姿態彙整。",
|
||||
"boundaryLabel": "執行邊界",
|
||||
"metrics": {
|
||||
"overall": {
|
||||
"label": "整體資安網",
|
||||
"detail": "headline progress"
|
||||
},
|
||||
"framework": {
|
||||
"label": "框架成熟度",
|
||||
"detail": "docs / schema / read-only evidence"
|
||||
},
|
||||
"runtimeGates": {
|
||||
"label": "Runtime gates",
|
||||
"detail": "未開啟"
|
||||
},
|
||||
"actions": {
|
||||
"label": "執行按鈕",
|
||||
"detail": "未提供"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"iwooos": {
|
||||
"eyebrow": "Information Security Mesh",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { AppLayout } from '@/components/layout'
|
||||
import { IwoooSReadOnlyBridge } from '@/components/security/iwooos-read-only-bridge'
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
|
||||
|
||||
@@ -56,6 +57,8 @@ export default function CompliancePage({ params }: { params: { locale: string }
|
||||
<p style={{ fontSize: 12, color: '#87867f', margin: '4px 0 0', fontFamily: 'var(--font-body), monospace' }}>{t('subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<IwoooSReadOnlyBridge />
|
||||
|
||||
{loading ? (
|
||||
<div style={{ padding: '32px', textAlign: 'center', color: '#87867f', fontFamily: 'var(--font-body), monospace', fontSize: 13 }}>{t('loading')}</div>
|
||||
) : error ? (
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { AppLayout } from '@/components/layout'
|
||||
import { IwoooSReadOnlyBridge } from '@/components/security/iwooos-read-only-bridge'
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
|
||||
|
||||
@@ -72,6 +73,8 @@ export default function SecurityPage({ params }: { params: { locale: string } })
|
||||
<p style={{ fontSize: 12, color: '#87867f', margin: '4px 0 0', fontFamily: 'var(--font-body), monospace' }}>{t('subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<IwoooSReadOnlyBridge />
|
||||
|
||||
{loading ? (
|
||||
<div style={{ padding: '32px', textAlign: 'center', color: '#87867f', fontFamily: 'var(--font-body), monospace', fontSize: 13 }}>{t('loading')}</div>
|
||||
) : error ? (
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { IwoooSReadOnlyBridge } from '@/components/security/iwooos-read-only-bridge'
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
|
||||
|
||||
@@ -55,6 +56,8 @@ export function CompliancePanel() {
|
||||
<p style={{ fontSize: 12, color: '#87867f', margin: '4px 0 0' }}>{t('subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<IwoooSReadOnlyBridge />
|
||||
|
||||
{loading ? (
|
||||
<div style={{ padding: '32px', textAlign: 'center', color: '#87867f', fontSize: 13 }}>{t('loading')}</div>
|
||||
) : error ? (
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { IwoooSReadOnlyBridge } from '@/components/security/iwooos-read-only-bridge'
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
|
||||
|
||||
@@ -61,6 +62,8 @@ export function SecurityPanel() {
|
||||
<p style={{ fontSize: 12, color: '#87867f', margin: '4px 0 0' }}>{t('subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<IwoooSReadOnlyBridge />
|
||||
|
||||
{loading ? (
|
||||
<div style={{ padding: '32px', textAlign: 'center', color: '#87867f', fontSize: 13 }}>{t('loading')}</div>
|
||||
) : error ? (
|
||||
|
||||
196
apps/web/src/components/security/iwooos-read-only-bridge.tsx
Normal file
196
apps/web/src/components/security/iwooos-read-only-bridge.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* IwoooSReadOnlyBridge — 既有資安頁面到 IwoooS 的只讀橋接
|
||||
* @created 2026-05-20 Codex Asia/Taipei
|
||||
*/
|
||||
|
||||
import Link from 'next/link'
|
||||
import { ArrowRight, ShieldCheck } from 'lucide-react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
const metrics = [
|
||||
{ key: 'overall', value: '58%' },
|
||||
{ key: 'framework', value: '80-85%' },
|
||||
{ key: 'runtimeGates', value: '0' },
|
||||
{ key: 'actions', value: '0' },
|
||||
] as const
|
||||
|
||||
const boundaries = [
|
||||
'runtime_execution_authorized=false',
|
||||
'active_runtime_gate_count=0',
|
||||
'action_buttons_allowed=false',
|
||||
'not_authorization=true',
|
||||
] as const
|
||||
|
||||
export function IwoooSReadOnlyBridge() {
|
||||
const t = useTranslations('security.iwooosBridge')
|
||||
|
||||
return (
|
||||
<>
|
||||
<style jsx>{`
|
||||
@media (max-width: 640px) {
|
||||
.iwooos-bridge-full {
|
||||
display: none !important;
|
||||
}
|
||||
.iwooos-bridge-compact {
|
||||
display: flex !important;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<section
|
||||
className="iwooos-bridge-compact"
|
||||
data-testid="iwooos-read-only-bridge"
|
||||
style={{
|
||||
display: 'none',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
gap: 6,
|
||||
background: '#fff',
|
||||
border: '0.5px solid #e0ddd4',
|
||||
borderRadius: 8,
|
||||
marginBottom: 12,
|
||||
padding: '10px 8px',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}>
|
||||
<ShieldCheck size={15} strokeWidth={1.8} color="#d97757" style={{ flex: '0 0 auto' }} />
|
||||
<div style={{ fontSize: 12, fontWeight: 700, color: '#141413', lineHeight: 1.2, minWidth: 0 }}>
|
||||
{t('compactTitle')}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ fontSize: 10, color: '#87867f', lineHeight: 1.35 }}>
|
||||
{t('compactDetail')}
|
||||
</div>
|
||||
<Link
|
||||
href="/iwooos"
|
||||
title={t('openIwooos')}
|
||||
aria-label={t('openIwooos')}
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minWidth: 0,
|
||||
height: 26,
|
||||
border: '0.5px solid #d97757',
|
||||
background: '#fff',
|
||||
color: '#141413',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
<ArrowRight size={14} strokeWidth={1.8} />
|
||||
</Link>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="iwooos-bridge-full"
|
||||
data-testid="iwooos-read-only-bridge"
|
||||
style={{
|
||||
background: '#fff',
|
||||
border: '0.5px solid #e0ddd4',
|
||||
borderRadius: 12,
|
||||
marginBottom: 16,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'space-between',
|
||||
gap: 12,
|
||||
padding: '14px 16px',
|
||||
borderBottom: '0.5px solid #e0ddd4',
|
||||
background: '#faf9f3',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, minWidth: 0 }}>
|
||||
<ShieldCheck size={18} strokeWidth={1.8} color="#d97757" style={{ marginTop: 2, flex: '0 0 auto' }} />
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 700, color: '#141413', lineHeight: 1.4 }}>
|
||||
{t('title')}
|
||||
</div>
|
||||
<p style={{ fontSize: 12, color: '#5f5d56', margin: '4px 0 0', lineHeight: 1.65 }}>
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Link
|
||||
href="/iwooos"
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
minHeight: 32,
|
||||
padding: '0 10px',
|
||||
border: '0.5px solid #d97757',
|
||||
background: '#fff',
|
||||
color: '#141413',
|
||||
textDecoration: 'none',
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{t('openIwooos')}
|
||||
<ArrowRight size={14} strokeWidth={1.8} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(120px, 1fr))',
|
||||
gap: 0,
|
||||
borderBottom: '0.5px solid #e0ddd4',
|
||||
}}
|
||||
>
|
||||
{metrics.map(metric => (
|
||||
<div key={metric.key} style={{ padding: '12px 16px', borderRight: '0.5px solid #f0ede4' }}>
|
||||
<div style={{ fontSize: 10, color: '#87867f', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 4 }}>
|
||||
{t(`metrics.${metric.key}.label`)}
|
||||
</div>
|
||||
<div style={{ fontSize: 20, fontWeight: 700, color: '#141413', lineHeight: 1.2 }}>
|
||||
{metric.value}
|
||||
</div>
|
||||
<div style={{ fontSize: 11, color: '#87867f', marginTop: 4, lineHeight: 1.5 }}>
|
||||
{t(`metrics.${metric.key}.detail`)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
|
||||
gap: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: '12px 16px', borderRight: '0.5px solid #f0ede4' }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: '#141413', marginBottom: 8 }}>
|
||||
{t('sourceLabel')}
|
||||
</div>
|
||||
<p style={{ fontSize: 12, color: '#5f5d56', margin: 0, lineHeight: 1.65 }}>
|
||||
{t('sourceDetail')}
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ padding: '12px 16px' }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: '#141413', marginBottom: 8 }}>
|
||||
{t('boundaryLabel')}
|
||||
</div>
|
||||
<div style={{ display: 'grid', gap: 5 }}>
|
||||
{boundaries.map(boundary => (
|
||||
<code key={boundary} style={{ fontSize: 11, color: '#141413', wordBreak: 'break-word' }}>
|
||||
{boundary}
|
||||
</code>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user