83 lines
4.3 KiB
TypeScript
83 lines
4.3 KiB
TypeScript
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
|
|
async function getData() {
|
|
const DB = "/home/fzq1228/polyscout/data/polyscout.db";
|
|
const { execSync } = await import("child_process");
|
|
const q = (sql: string) => {
|
|
const py = `import sqlite3,json; conn=sqlite3.connect('${DB}'); c=conn.cursor(); c.execute("""${sql}"""); print(json.dumps(c.fetchall(), ensure_ascii=False, default=str)); conn.close()`;
|
|
try { return JSON.parse(execSync(`python3 -c ${JSON.stringify(py)}`, { encoding: "utf8" })); }
|
|
catch { return []; }
|
|
};
|
|
const mCount = q("SELECT COUNT(*) FROM markets")[0]?.[0] ?? 0;
|
|
const sCount = q("SELECT COUNT(*) FROM signals")[0]?.[0] ?? 0;
|
|
const lastSync = q("SELECT fetched_at FROM markets ORDER BY fetched_at DESC LIMIT 1")[0]?.[0] ?? "N/A";
|
|
const topSignals = q("SELECT market_id, score, expected_edge, risk, created_at FROM signals ORDER BY score DESC LIMIT 5");
|
|
return { mCount, sCount, lastSync, topSignals };
|
|
}
|
|
|
|
const riskMap: Record<string, string> = { low: "低", medium: "中", high: "高" };
|
|
const riskVariant: Record<string, "default" | "secondary" | "destructive"> = {
|
|
low: "default", medium: "secondary", high: "destructive"
|
|
};
|
|
|
|
export default async function DashboardPage() {
|
|
const { mCount, sCount, lastSync, topSignals } = await getData();
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h1 className="text-2xl font-bold">仪表盘</h1>
|
|
<p className="text-muted-foreground text-sm mt-1">PolyScout 实时套利信号监控</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
<Card>
|
|
<CardHeader className="pb-2"><CardTitle className="text-sm font-medium text-muted-foreground">监控市场数</CardTitle></CardHeader>
|
|
<CardContent><div className="text-3xl font-bold">{mCount}</div></CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="pb-2"><CardTitle className="text-sm font-medium text-muted-foreground">信号总数</CardTitle></CardHeader>
|
|
<CardContent><div className="text-3xl font-bold">{sCount}</div></CardContent>
|
|
</Card>
|
|
<Card className="col-span-2 md:col-span-1">
|
|
<CardHeader className="pb-2"><CardTitle className="text-sm font-medium text-muted-foreground">最后同步</CardTitle></CardHeader>
|
|
<CardContent><div className="text-sm font-medium">{String(lastSync).slice(0, 16)}</div></CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<div>
|
|
<h2 className="text-lg font-semibold mb-3">最新高分信号 (Top 5)</h2>
|
|
<Card>
|
|
<CardContent className="p-0">
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-sm">
|
|
<thead>
|
|
<tr className="border-b border-border">
|
|
<th className="text-left px-4 py-3 text-muted-foreground font-medium text-xs uppercase tracking-wider">市场</th>
|
|
<th className="text-left px-4 py-3 text-muted-foreground font-medium text-xs uppercase tracking-wider">评分</th>
|
|
<th className="text-left px-4 py-3 text-muted-foreground font-medium text-xs uppercase tracking-wider">预期收益</th>
|
|
<th className="text-left px-4 py-3 text-muted-foreground font-medium text-xs uppercase tracking-wider">风险</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{topSignals.length === 0 ? (
|
|
<tr><td colSpan={4} className="text-center py-8 text-muted-foreground">暂无信号</td></tr>
|
|
) : topSignals.map((r: any[], i: number) => (
|
|
<tr key={i} className="border-b border-border last:border-0 hover:bg-accent/50 transition-colors">
|
|
<td className="px-4 py-3 font-mono text-xs text-muted-foreground">{String(r[0]).slice(0, 16)}</td>
|
|
<td className="px-4 py-3 font-bold">{r[1]}</td>
|
|
<td className="px-4 py-3 text-green-400">+{r[2]}%</td>
|
|
<td className="px-4 py-3"><Badge variant={riskVariant[r[3]] ?? "default"}>{riskMap[r[3]] ?? r[3]}</Badge></td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|