Files
Cyrene_v2/frontend/web/src/pages/admin/DashboardPage.tsx
T
Aska 5faa955bce feat(ui): add light theme with system auto-switch + Cyrene avatar
- Miuix day/night dual theme CSS variables
- themeStore: light default, follows prefers-color-scheme
- Header: sun/moon toggle button
- All components migrated to CSS variables
- Cyrene avatar image replaces 'C' logo everywhere
- Favicon added
2026-08-12 17:42:18 +08:00

92 lines
3.8 KiB
TypeScript

import { useEffect, useState } from 'react'
import { api } from '../../api/client'
export default function DashboardPage() {
const [data, setData] = useState<any>(null)
useEffect(() => {
api.admin.dashboard().then(r => setData(r.dashboard)).catch(() => {})
}, [])
const services = data?.services || {}
const serviceCount = Object.values(services).filter(Boolean).length
const totalServices = Object.keys(services).length || 3
return (
<div className="p-6 space-y-6">
<h2 className="text-lg font-semibold">仪表盘</h2>
{/* Stats */}
<div className="grid grid-cols-4 gap-3">
<StatCard label="服务" value={`${serviceCount}/${totalServices}`} sub="运行中/总数" color='var(--mx-tertiary-text)' />
<StatCard label="插件" value="11" sub="已加载" color='var(--mx-primary)' />
<StatCard label="运行时间" value={data?.uptime_sec ? formatUptime(data.uptime_sec) : '...'} sub="since start" color='var(--mx-stat-value)' />
<StatCard label="版本" value="2.0.0-dev" sub="" color="var(--mx-text-dim)" />
</div>
{/* Service Status */}
<Section title="服务状态">
<div className="bg-[var(--mx-surface)] rounded-[18px] border border-[var(--mx-outline)] divide-y divide-[var(--mx-divider)]">
{['ai_core', 'memory', 'voice'].map(name => (
<div key={name} className="flex items-center justify-between px-4 py-3">
<div className="flex items-center gap-3">
<span className={`w-2.5 h-2.5 rounded-full ${services[name] ? 'bg-[var(--mx-tertiary-text)]' : 'bg-[var(--mx-outline)]'}`} />
<span className="text-sm text-[var(--mx-text)]">{name}</span>
</div>
<span className="text-xs text-[var(--mx-text-dim)]">{services[name] ? '运行中' : '未启动'}</span>
</div>
))}
</div>
</Section>
{/* Quick Links */}
<Section title="快捷入口">
<div className="grid grid-cols-2 gap-3">
<QuickLink to="/admin/iot" label="IoT 设备" desc="查看和操控智能家居设备" />
<QuickLink to="/admin/plugins" label="插件管理" desc="管理已加载的工具插件" />
<QuickLink to="/admin/memory" label="记忆管理" desc="搜索、添加、删除记忆" />
<QuickLink to="/admin/services" label="服务管理" desc="查看服务运行状态和日志" />
</div>
</Section>
</div>
)
}
function StatCard({ label, value, sub, color }: { label:string; value:string; sub:string; color:string }) {
return (
<div className="bg-[var(--mx-surface)] border border-[var(--mx-outline)] rounded-[18px] px-4 py-3">
<p className="text-xs text-[var(--mx-text-dim)] mb-1">{label}</p>
<p className="text-xl font-bold" style={{ color }}>{value}</p>
{sub && <p className="text-xs text-[var(--mx-text-dim)] mt-0.5">{sub}</p>}
</div>
)
}
function Section({ title, children }: { title:string; children:React.ReactNode }) {
return (
<div>
<h3 className="text-sm font-medium text-[var(--mx-text-secondary)] mb-3">{title}</h3>
{children}
</div>
)
}
import { Link } from 'react-router-dom'
function QuickLink({ to, label, desc }: { to:string; label:string; desc:string }) {
return (
<Link to={to}
className="block bg-[var(--mx-surface)] border border-[var(--mx-outline)] rounded-[18px] px-4 py-3 hover:border-[var(--mx-primary)]/50 transition-colors"
>
<p className="text-sm text-[var(--mx-text)] font-medium">{label}</p>
<p className="text-xs text-[var(--mx-text-dim)] mt-1">{desc}</p>
</Link>
)
}
function formatUptime(s: number) {
if (s < 60) return `${s}s`
if (s < 3600) return `${Math.floor(s/60)}m`
return `${Math.floor(s/3600)}h${Math.floor((s%3600)/60)}m`
}