feat: 仪表盘路径改为 /dashboard + Forbidden 权限修复

This commit is contained in:
aiyimickey 2026-07-01 23:05:27 +08:00
parent 5cd300bee8
commit de0116ddc8
5 changed files with 99 additions and 94 deletions

View File

@ -77,7 +77,7 @@ export async function GET(request: NextRequest) {
payload: { username: userinfo.preferred_username, displayName: userinfo.name, role: user.role }, payload: { username: userinfo.preferred_username, displayName: userinfo.name, role: user.role },
}) })
const response = NextResponse.redirect(new URL('/', baseUrl)) const response = NextResponse.redirect(new URL('/dashboard', baseUrl))
// 签发 tlyq_session cookie // 签发 tlyq_session cookie
response.cookies.set('tlyq_session', token, { response.cookies.set('tlyq_session', token, {

View File

@ -0,0 +1,93 @@
'use client'
// src/app/dashboard/page.tsx — 仪表盘首页(四层结构)
import { useState, useEffect } from 'react'
export default function DashboardPage() {
const [data, setData] = useState<{ services: Array<Record<string,unknown>>; counts: Record<string,number> }>({ services: [], counts: { normal: 0, abnormal: 0, unknown: 0 } })
useEffect(() => {
fetch('/api/status').then(r => r.json()).then(setData)
const timer = setInterval(() => fetch('/api/status').then(r => r.json()).then(setData), 30_000)
return () => clearInterval(timer)
}, [])
const { services, counts } = data
return (
<div className="space-y-8">
{/* 标题 */}
<div>
<h1 className="text-2xl font-semibold font-[family-name:var(--font-display)]"> <span className="text-slate-400 dark:text-slate-500 font-normal">· {services.length} </span></h1>
<p className="text-sm text-slate-400 dark:text-slate-500 mt-1"> · 30 </p>
</div>
{/* 状态统计条 */}
<div className="flex gap-px bg-slate-200 dark:bg-slate-800 rounded-xl overflow-hidden">
<div className="flex-1 bg-white dark:bg-slate-900 px-6 py-4 flex flex-col gap-1">
<span className="text-xs font-medium text-slate-400 uppercase tracking-widest"></span>
<span className="text-2xl font-semibold text-emerald-600 font-[family-name:var(--font-display)]">{counts.normal}</span>
<span className="text-xs text-slate-400"> {services.length} </span>
</div>
<div className="flex-1 bg-white dark:bg-slate-900 px-6 py-4 flex flex-col gap-1">
<span className="text-xs font-medium text-slate-400 uppercase tracking-widest"></span>
<span className="text-2xl font-semibold text-red-600 font-[family-name:var(--font-display)]">{counts.abnormal}</span>
<span className="text-xs text-slate-400">{services.filter(s => s.current_status === 'abnormal').map(s => s.name).join(', ') || '—'}</span>
</div>
<div className="flex-1 bg-white dark:bg-slate-900 px-6 py-4 flex flex-col gap-1">
<span className="text-xs font-medium text-slate-400 uppercase tracking-widest"></span>
<span className="text-2xl font-semibold text-amber-600 font-[family-name:var(--font-display)]">{counts.unknown}</span>
<span className="text-xs text-slate-400">{counts.unknown > 0 ? '待首次检查' : '全部已检查'}</span>
</div>
<div className="flex-1 bg-white dark:bg-slate-900 px-6 py-4 flex flex-col gap-1 text-right">
<span className="text-xs font-medium text-slate-400 uppercase tracking-widest"></span>
<span className="text-base font-medium text-slate-600 dark:text-slate-300"></span>
<span className="text-xs text-slate-400"> 30s</span>
</div>
</div>
{/* KPI 统计条 */}
<div className="flex gap-px bg-slate-200 dark:bg-slate-800 rounded-xl overflow-hidden">
<div className="flex-1 bg-white dark:bg-slate-900 px-6 py-4 flex flex-col gap-1">
<span className="text-xs font-medium text-slate-400 uppercase tracking-widest"></span>
<span className="text-2xl font-semibold text-emerald-600 font-[family-name:var(--font-display)]"></span>
</div>
<div className="flex-1 bg-white dark:bg-slate-900 px-6 py-4 flex flex-col gap-1">
<span className="text-xs font-medium text-slate-400 uppercase tracking-widest"></span>
<span className="text-2xl font-semibold font-[family-name:var(--font-display)]"></span>
</div>
</div>
{/* 分割线 */}
<hr className="border-t border-slate-200 dark:border-slate-800" />
{/* 服务卡片网格 */}
<div className="grid grid-cols-4 gap-5">
{services.length === 0 && (
<div className="col-span-4 text-center py-12 text-slate-400 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800">
</div>
)}
{services.map((s) => {
const isAbnormal = s.current_status === 'abnormal'
return (
<div key={String(s.id)} className={`rounded-xl p-5 border transition-all ${isAbnormal ? 'bg-red-50 dark:bg-red-500/10 border-red-600 dark:border-red-400' : 'bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-800 hover:shadow-sm'}`}>
<div className="flex items-center justify-between mb-2">
<span className="flex items-center gap-2 text-sm font-semibold">
<span className={`w-2.5 h-2.5 rounded-full ${isAbnormal ? 'bg-red-600 shadow-[0_0_8px_var(--danger)]' : 'bg-emerald-600'}`} />
{String(s.name)}
</span>
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${s.alert_level === 'critical' ? 'bg-red-100 text-red-700 dark:bg-red-500/10 dark:text-red-400' : 'bg-green-100 text-green-700 dark:bg-green-500/10 dark:text-green-400'}`}>
{String(s.alert_level)}
</span>
</div>
{isAbnormal
? <div className="text-lg font-semibold font-mono text-red-600 dark:text-red-400"></div>
: <div className="text-xs text-slate-400"></div>
}
</div>
)
})}
</div>
</div>
)
}

View File

@ -2,5 +2,5 @@
import LoginPage from '@shared/ui/login-page' import LoginPage from '@shared/ui/login-page'
export default function Page() { export default function Page() {
return <LoginPage siteName="告警监控中心" /> return <LoginPage siteName="告警监控中心" redirectPath="/dashboard" />
} }

View File

@ -1,93 +1,5 @@
'use client' import { redirect } from 'next/navigation'
// src/app/page.tsx — 仪表盘首页(四层结构)
import { useState, useEffect } from 'react'
export default function HomePage() { export default function HomePage() {
const [data, setData] = useState<{ services: Array<Record<string,unknown>>; counts: Record<string,number> }>({ services: [], counts: { normal: 0, abnormal: 0, unknown: 0 } }) redirect('/dashboard')
useEffect(() => {
fetch('/api/status').then(r => r.json()).then(setData)
const timer = setInterval(() => fetch('/api/status').then(r => r.json()).then(setData), 30_000)
return () => clearInterval(timer)
}, [])
const { services, counts } = data
return (
<div className="space-y-8">
{/* 标题 */}
<div>
<h1 className="text-2xl font-semibold font-[family-name:var(--font-display)]"> <span className="text-slate-400 dark:text-slate-500 font-normal">· {services.length} </span></h1>
<p className="text-sm text-slate-400 dark:text-slate-500 mt-1"> · 30 </p>
</div>
{/* 状态统计条 */}
<div className="flex gap-px bg-slate-200 dark:bg-slate-800 rounded-xl overflow-hidden">
<div className="flex-1 bg-white dark:bg-slate-900 px-6 py-4 flex flex-col gap-1">
<span className="text-xs font-medium text-slate-400 uppercase tracking-widest"></span>
<span className="text-2xl font-semibold text-emerald-600 font-[family-name:var(--font-display)]">{counts.normal}</span>
<span className="text-xs text-slate-400"> {services.length} </span>
</div>
<div className="flex-1 bg-white dark:bg-slate-900 px-6 py-4 flex flex-col gap-1">
<span className="text-xs font-medium text-slate-400 uppercase tracking-widest"></span>
<span className="text-2xl font-semibold text-red-600 font-[family-name:var(--font-display)]">{counts.abnormal}</span>
<span className="text-xs text-slate-400">{services.filter(s => s.current_status === 'abnormal').map(s => s.name).join(', ') || '—'}</span>
</div>
<div className="flex-1 bg-white dark:bg-slate-900 px-6 py-4 flex flex-col gap-1">
<span className="text-xs font-medium text-slate-400 uppercase tracking-widest"></span>
<span className="text-2xl font-semibold text-amber-600 font-[family-name:var(--font-display)]">{counts.unknown}</span>
<span className="text-xs text-slate-400">{counts.unknown > 0 ? '待首次检查' : '全部已检查'}</span>
</div>
<div className="flex-1 bg-white dark:bg-slate-900 px-6 py-4 flex flex-col gap-1 text-right">
<span className="text-xs font-medium text-slate-400 uppercase tracking-widest"></span>
<span className="text-base font-medium text-slate-600 dark:text-slate-300"></span>
<span className="text-xs text-slate-400"> 30s</span>
</div>
</div>
{/* KPI 统计条 */}
<div className="flex gap-px bg-slate-200 dark:bg-slate-800 rounded-xl overflow-hidden">
<div className="flex-1 bg-white dark:bg-slate-900 px-6 py-4 flex flex-col gap-1">
<span className="text-xs font-medium text-slate-400 uppercase tracking-widest"></span>
<span className="text-2xl font-semibold text-emerald-600 font-[family-name:var(--font-display)]"></span>
</div>
<div className="flex-1 bg-white dark:bg-slate-900 px-6 py-4 flex flex-col gap-1">
<span className="text-xs font-medium text-slate-400 uppercase tracking-widest"></span>
<span className="text-2xl font-semibold font-[family-name:var(--font-display)]"></span>
</div>
</div>
{/* 分割线 */}
<hr className="border-t border-slate-200 dark:border-slate-800" />
{/* 服务卡片网格 */}
<div className="grid grid-cols-4 gap-5">
{services.length === 0 && (
<div className="col-span-4 text-center py-12 text-slate-400 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800">
</div>
)}
{services.map((s) => {
const isAbnormal = s.current_status === 'abnormal'
return (
<div key={String(s.id)} className={`rounded-xl p-5 border transition-all ${isAbnormal ? 'bg-red-50 dark:bg-red-500/10 border-red-600 dark:border-red-400' : 'bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-800 hover:shadow-sm'}`}>
<div className="flex items-center justify-between mb-2">
<span className="flex items-center gap-2 text-sm font-semibold">
<span className={`w-2.5 h-2.5 rounded-full ${isAbnormal ? 'bg-red-600 shadow-[0_0_8px_var(--danger)]' : 'bg-emerald-600'}`} />
{String(s.name)}
</span>
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${s.alert_level === 'critical' ? 'bg-red-100 text-red-700 dark:bg-red-500/10 dark:text-red-400' : 'bg-green-100 text-green-700 dark:bg-green-500/10 dark:text-green-400'}`}>
{String(s.alert_level)}
</span>
</div>
{isAbnormal
? <div className="text-lg font-semibold font-mono text-red-600 dark:text-red-400"></div>
: <div className="text-xs text-slate-400"></div>
}
</div>
)
})}
</div>
</div>
)
} }

View File

@ -18,7 +18,7 @@ export default function Sidebar() {
const sections: NavSection[] = [ const sections: NavSection[] = [
{ {
items: [ items: [
{ label: '仪表盘', href: '/', icon: LayoutDashboard }, { label: '仪表盘', href: '/dashboard', icon: LayoutDashboard },
], ],
}, },
{ {
@ -49,7 +49,7 @@ export default function Sidebar() {
<aside className="fixed left-0 top-0 bottom-0 w-60 bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800 z-[200] flex flex-col"> <aside className="fixed left-0 top-0 bottom-0 w-60 bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800 z-[200] flex flex-col">
{/* 品牌区 */} {/* 品牌区 */}
<div className="h-14 flex items-center px-6 border-b border-slate-200 dark:border-slate-800 shrink-0"> <div className="h-14 flex items-center px-6 border-b border-slate-200 dark:border-slate-800 shrink-0">
<Link href="/" className="flex items-center gap-3"> <Link href="/dashboard" className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-indigo-600 flex items-center justify-center text-white font-bold text-sm">M</div> <div className="w-8 h-8 rounded-lg bg-indigo-600 flex items-center justify-center text-white font-bold text-sm">M</div>
<span className="font-semibold text-slate-900 dark:text-white">monitor-ai</span> <span className="font-semibold text-slate-900 dark:text-white">monitor-ai</span>
</Link> </Link>