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:
37
apps/web/src/app/[locale]/automation/page.tsx
Normal file
37
apps/web/src/app/[locale]/automation/page.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
23
apps/web/src/app/[locale]/knowledge/page.tsx
Normal file
23
apps/web/src/app/[locale]/knowledge/page.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
92
apps/web/src/app/[locale]/observability/page.tsx
Normal file
92
apps/web/src/app/[locale]/observability/page.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
41
apps/web/src/app/[locale]/operations/page.tsx
Normal file
41
apps/web/src/app/[locale]/operations/page.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
35
apps/web/src/app/[locale]/security-compliance/page.tsx
Normal file
35
apps/web/src/app/[locale]/security-compliance/page.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user