feat(web): Sprint 5 Phase 3 — 5 個整合頁面 + Sidebar 路由更新

新增頁面:
- /observability: 服務監控 + APM + 錯誤追蹤 + 應用 + 服務目錄 (5 Tab)
- /automation: 自動修復 + 神經指揮 + Drift (3 Tab)
- /operations: 部署 + 工單 + 成本 + 行動日誌 + 計費 (5 Tab)
- /security-compliance: 安全 + 合規 (2 Tab)
- /knowledge: 知識庫

所有 Tab 用 React.lazy + Suspense 載入現有頁面內容
零假數據: 每個 Tab 都是現有真實頁面

Sidebar 路由更新指向新整合頁面

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-08 22:09:53 +08:00
parent 8b5db2f58e
commit 1413804378
6 changed files with 234 additions and 6 deletions

View File

@@ -0,0 +1,37 @@
'use client'
/**
* 自動化 (/automation) — Sprint 5 整合頁面
* 整合: 自動修復 + 神經指揮 + Drift 偵測
* 零假數據: 全部載入現有頁面內容
* 建立時間: 2026-04-08 (台北時區)
*/
import { lazy, Suspense } from 'react'
import { useTranslations } from 'next-intl'
import { AppLayout } from '@/components/layout'
import { PageTabs, type TabConfig } from '@/components/layout/page-tabs'
const AutoRepairContent = lazy(() => import('@/app/[locale]/auto-repair/page'))
const NeuralCommandContent = lazy(() => import('@/app/[locale]/neural-command/page'))
const DriftContent = lazy(() => import('@/app/[locale]/drift/page'))
function Loading() {
return <div style={{ padding: 32, textAlign: 'center', color: '#87867f' }}>...</div>
}
export default function AutomationPage({ params }: { params: { locale: string } }) {
const t = useTranslations('nav')
const tabs: TabConfig[] = [
{ id: 'repair', label: t('autoRepair'), content: <Suspense fallback={<Loading />}><AutoRepairContent params={params} /></Suspense> },
{ id: 'neural', label: t('neuralCommand'), content: <Suspense fallback={<Loading />}><NeuralCommandContent params={params} /></Suspense> },
{ id: 'drift', label: t('drift'), content: <Suspense fallback={<Loading />}><DriftContent params={params} /></Suspense> },
]
return (
<AppLayout locale={params.locale}>
<PageTabs tabs={tabs} defaultTab="repair" syncWithUrl={true} />
</AppLayout>
)
}

View File

@@ -0,0 +1,23 @@
'use client'
/**
* 知識 (/knowledge) — Sprint 5 整合頁面
* 直接載入現有 knowledge-base 內容
* 零假數據: 原封不動使用現有頁面
* 建立時間: 2026-04-08 (台北時區)
*/
import { lazy, Suspense } from 'react'
import { AppLayout } from '@/components/layout'
const KnowledgeBaseContent = lazy(() => import('@/app/[locale]/knowledge-base/page'))
export default function KnowledgePage({ params }: { params: { locale: string } }) {
return (
<AppLayout locale={params.locale}>
<Suspense fallback={<div style={{ padding: 32, textAlign: 'center', color: '#87867f' }}>...</div>}>
<KnowledgeBaseContent params={params} />
</Suspense>
</AppLayout>
)
}

View File

@@ -0,0 +1,92 @@
'use client'
/**
* 可觀測性 (/observability) — Sprint 5 整合頁面
* ================================================
* 整合: 服務監控 + APM + 錯誤追蹤 + 應用 + 服務目錄
* 使用 PageTabs 共用元件,每個 Tab 載入對應的現有頁面內容
*
* 零假數據: 所有 Tab 內容串接現有真實 API
*
* 建立時間: 2026-04-08 (台北時區)
* 建立者: Claude Code (Sprint 5 Phase 3)
*/
import { lazy, Suspense } from 'react'
import { useTranslations } from 'next-intl'
import { AppLayout } from '@/components/layout'
import { PageTabs, type TabConfig } from '@/components/layout/page-tabs'
// 延遲載入現有頁面內容 (避免一次載入所有 Tab)
// 注意: 這些直接 import 現有頁面的內部邏輯,不是 Mock
const MonitoringContent = lazy(() => import('@/app/[locale]/monitoring/page').then(m => ({ default: m.default })))
const APMContent = lazy(() => import('@/app/[locale]/apm/page').then(m => ({ default: m.default })))
const ErrorsContent = lazy(() => import('@/app/[locale]/errors/page').then(m => ({ default: m.default })))
const AppsContent = lazy(() => import('@/app/[locale]/apps/page').then(m => ({ default: m.default })))
const ServicesContent = lazy(() => import('@/app/[locale]/services/page').then(m => ({ default: m.default })))
function TabSkeleton() {
return (
<div style={{ padding: 32, textAlign: 'center', color: '#87867f' }}>
...
</div>
)
}
export default function ObservabilityPage({ params }: { params: { locale: string } }) {
const t = useTranslations('nav')
const tabs: TabConfig[] = [
{
id: 'monitoring',
label: t('monitoring'),
content: (
<Suspense fallback={<TabSkeleton />}>
<MonitoringContent params={params} />
</Suspense>
),
},
{
id: 'apm',
label: t('apm'),
content: (
<Suspense fallback={<TabSkeleton />}>
<APMContent params={params} />
</Suspense>
),
},
{
id: 'errors',
label: t('errors'),
content: (
<Suspense fallback={<TabSkeleton />}>
<ErrorsContent params={params} />
</Suspense>
),
},
{
id: 'apps',
label: t('apps'),
content: (
<Suspense fallback={<TabSkeleton />}>
<AppsContent params={params} />
</Suspense>
),
},
{
id: 'services',
label: t('services'),
content: (
<Suspense fallback={<TabSkeleton />}>
<ServicesContent params={params} />
</Suspense>
),
},
]
return (
<AppLayout locale={params.locale}>
<PageTabs tabs={tabs} defaultTab="monitoring" syncWithUrl={true} />
</AppLayout>
)
}

View File

@@ -0,0 +1,41 @@
'use client'
/**
* 營運 (/operations) — Sprint 5 整合頁面
* 整合: 部署管理 + 工單 + 成本分析 + 行動日誌 + 計費
* 零假數據: 全部載入現有頁面內容
* 建立時間: 2026-04-08 (台北時區)
*/
import { lazy, Suspense } from 'react'
import { useTranslations } from 'next-intl'
import { AppLayout } from '@/components/layout'
import { PageTabs, type TabConfig } from '@/components/layout/page-tabs'
const DeploymentsContent = lazy(() => import('@/app/[locale]/deployments/page'))
const TicketsContent = lazy(() => import('@/app/[locale]/tickets/page'))
const CostContent = lazy(() => import('@/app/[locale]/cost/page'))
const ActionLogsContent = lazy(() => import('@/app/[locale]/action-logs/page'))
const BillingContent = lazy(() => import('@/app/[locale]/billing/page'))
function Loading() {
return <div style={{ padding: 32, textAlign: 'center', color: '#87867f' }}>...</div>
}
export default function OperationsPage({ params }: { params: { locale: string } }) {
const t = useTranslations('nav')
const tabs: TabConfig[] = [
{ id: 'deployments', label: t('deployments'), content: <Suspense fallback={<Loading />}><DeploymentsContent params={params} /></Suspense> },
{ id: 'tickets', label: t('tickets'), content: <Suspense fallback={<Loading />}><TicketsContent params={params} /></Suspense> },
{ id: 'cost', label: t('cost'), content: <Suspense fallback={<Loading />}><CostContent params={params} /></Suspense> },
{ id: 'logs', label: t('actions'), content: <Suspense fallback={<Loading />}><ActionLogsContent params={params} /></Suspense> },
{ id: 'billing', label: t('billing'), content: <Suspense fallback={<Loading />}><BillingContent params={params} /></Suspense> },
]
return (
<AppLayout locale={params.locale}>
<PageTabs tabs={tabs} defaultTab="deployments" syncWithUrl={true} />
</AppLayout>
)
}

View File

@@ -0,0 +1,35 @@
'use client'
/**
* 安全合規 (/security-compliance) — Sprint 5 整合頁面
* 整合: 安全掃描 + 合規報告
* 零假數據: 全部載入現有頁面內容
* 建立時間: 2026-04-08 (台北時區)
*/
import { lazy, Suspense } from 'react'
import { useTranslations } from 'next-intl'
import { AppLayout } from '@/components/layout'
import { PageTabs, type TabConfig } from '@/components/layout/page-tabs'
const SecurityContent = lazy(() => import('@/app/[locale]/security/page'))
const ComplianceContent = lazy(() => import('@/app/[locale]/compliance/page'))
function Loading() {
return <div style={{ padding: 32, textAlign: 'center', color: '#87867f' }}>...</div>
}
export default function SecurityCompliancePage({ params }: { params: { locale: string } }) {
const t = useTranslations('nav')
const tabs: TabConfig[] = [
{ id: 'security', label: t('security'), content: <Suspense fallback={<Loading />}><SecurityContent params={params} /></Suspense> },
{ id: 'compliance', label: t('compliance'), content: <Suspense fallback={<Loading />}><ComplianceContent params={params} /></Suspense> },
]
return (
<AppLayout locale={params.locale}>
<PageTabs tabs={tabs} defaultTab="security" syncWithUrl={true} />
</AppLayout>
)
}

View File

@@ -80,12 +80,12 @@ const NAV_SECTIONS: NavSection[] = [
sectionKey: 'main', sectionKey: 'main',
sectionLabel: '', sectionLabel: '',
items: [ items: [
{ id: 'command-center', href: '/', labelKey: 'commandCenter', Icon: LayoutDashboard }, { id: 'command-center', href: '/', labelKey: 'commandCenter', Icon: LayoutDashboard },
{ id: 'observability', href: '/monitoring', labelKey: 'observability', Icon: Monitor }, { id: 'observability', href: '/observability', labelKey: 'observability', Icon: Monitor },
{ id: 'automation', href: '/auto-repair', labelKey: 'automation', Icon: Wrench }, { id: 'automation', href: '/automation', labelKey: 'automation', Icon: Wrench },
{ id: 'operations', href: '/deployments', labelKey: 'operations', Icon: Package }, { id: 'operations', href: '/operations', labelKey: 'operations', Icon: Package },
{ id: 'security-compliance', href: '/security', labelKey: 'securityCompliance',Icon: Shield }, { id: 'security-compliance', href: '/security-compliance', labelKey: 'securityCompliance',Icon: Shield },
{ id: 'knowledge', href: '/knowledge-base', labelKey: 'knowledge', Icon: BookOpen }, { id: 'knowledge', href: '/knowledge', labelKey: 'knowledge', Icon: BookOpen },
], ],
}, },
{ {