- 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
81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { getToken } from '../../api/client'
|
|
|
|
export default function SystemPage() {
|
|
const [info, setInfo] = useState<any>({})
|
|
const [sessions, setSessions] = useState<any[]>([])
|
|
const [plugins, setPlugins] = useState<any[]>([])
|
|
|
|
useEffect(() => {
|
|
fetchAll()
|
|
const interval = setInterval(fetchAll, 10000)
|
|
return () => clearInterval(interval)
|
|
}, [])
|
|
|
|
const fetchAll = async () => {
|
|
const token = getToken()
|
|
const headers = { Authorization: `Bearer ${token}` }
|
|
try {
|
|
const [sysRes, sessionRes, pluginRes] = await Promise.all([
|
|
fetch('/api/v1/admin/system', { headers }),
|
|
fetch('/api/v1/admin/sessions', { headers }),
|
|
fetch('/api/v1/admin/plugins', { headers }),
|
|
])
|
|
if (sysRes.ok) setInfo((await sysRes.json()).system)
|
|
if (sessionRes.ok) setSessions((await sessionRes.json()).sessions || [])
|
|
if (pluginRes.ok) setPlugins((await pluginRes.json()).plugins || [])
|
|
} catch {}
|
|
}
|
|
|
|
return (
|
|
<div className="p-6 space-y-6">
|
|
<h2 className="text-lg font-semibold">系统信息</h2>
|
|
|
|
{/* System Info */}
|
|
<div className="grid grid-cols-4 gap-3">
|
|
<StatRow label="版本" value={info.version || '-'} />
|
|
<StatRow label="Go 版本" value={info.go_version || '-'} />
|
|
<StatRow label="运行时间" value={info.uptime_sec ? `${Math.floor(info.uptime_sec/60)}m` : '-'} />
|
|
<StatRow label="WS 连接" value={info.ws_clients ?? 0} />
|
|
</div>
|
|
|
|
{/* Plugins list */}
|
|
<div>
|
|
<h3 className="text-sm font-medium text-[var(--mx-text-secondary)] mb-3">已加载插件 ({plugins.length})</h3>
|
|
<div className="flex flex-wrap gap-2">
|
|
{plugins.map((p, i) => (
|
|
<span key={i} className="px-3 py-1.5 bg-[var(--mx-surface)] border border-[var(--mx-outline)] rounded-full text-xs text-[var(--mx-text)]">
|
|
{p.name}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Sessions */}
|
|
<div>
|
|
<h3 className="text-sm font-medium text-[var(--mx-text-secondary)] mb-3">活跃会话 ({sessions.length})</h3>
|
|
{sessions.length === 0 ? (
|
|
<p className="text-xs text-[var(--mx-text-dim)]">暂无活跃会话</p>
|
|
) : (
|
|
<div className="space-y-1">
|
|
{sessions.map((s: any, i) => (
|
|
<div key={i} className="bg-[var(--mx-surface)] border border-[var(--mx-outline)] rounded-[14px] px-4 py-2 text-xs text-[var(--mx-text-dim)]">
|
|
{s.session_id || s.id || '-'}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function StatRow({ label, value }: { label:string; value:string|number }) {
|
|
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-lg font-bold text-[var(--mx-text)]">{value}</p>
|
|
</div>
|
|
)
|
|
}
|