257 lines
10 KiB
TypeScript
257 lines
10 KiB
TypeScript
import {
|
||
Calendar,
|
||
Check,
|
||
FileText,
|
||
LayoutGrid,
|
||
Settings,
|
||
type LucideIcon,
|
||
} from "lucide-react";
|
||
import { useAuth, usePermission } from "../../context/AuthContext";
|
||
|
||
const TREND_VALUES = [38, 55, 44, 73, 61, 86, 78] as const;
|
||
const TREND_LABELS = ["周一", "周二", "周三", "周四", "周五", "周六", "今天"] as const;
|
||
|
||
const ACTIVITY_ROWS = [
|
||
["数据探索.ipynb 发布稳定版本 v3.0", "张三", "成功", "16:42"],
|
||
["每日模型训练流程完成调度运行", "Scheduler", "成功", "15:25"],
|
||
["批量预测.py 更新工作副本", "王五", "已同步", "14:18"],
|
||
["风险验证流程完成 DAG 校验", "李四", "成功", "11:06"],
|
||
] as const;
|
||
|
||
function LegendDot({ className }: { className: string }) {
|
||
return <i className={`inline-block size-[7px] rounded-full ${className}`} />;
|
||
}
|
||
|
||
export function DashboardPage({
|
||
scriptCount,
|
||
online,
|
||
onNavigate,
|
||
}: {
|
||
scriptCount: number;
|
||
online: boolean;
|
||
onNavigate: (page: "scripts" | "schedules" | "system") => void;
|
||
}) {
|
||
const { user, currentWorkspace } = useAuth();
|
||
const hasSystemView = usePermission("system:view");
|
||
const notebookCount = Math.max(1, Math.round(scriptCount * 0.67));
|
||
const pythonCount = Math.max(0, scriptCount - Math.round(scriptCount * 0.67));
|
||
|
||
return (
|
||
<section className="h-full overflow-auto bg-bg p-6">
|
||
<div className="dashboard-hero-shell flex items-center justify-between rounded-xl px-[34px] py-[34px] text-white shadow-[0_12px_30px_rgb(19_80_137/18%)]">
|
||
<div>
|
||
<span className="text-[10px] tracking-[0.12em] opacity-80">
|
||
MODEL DEVELOPMENT PLATFORM
|
||
</span>
|
||
<h2 className="my-2 mb-[5px] text-[25px]">
|
||
下午好,{user?.display_name ?? "用户"}
|
||
</h2>
|
||
<p className="m-0 text-xs opacity-90">
|
||
当前位于 {currentWorkspace?.workspace_name ?? "(未选择 Workspace)"}
|
||
,可以继续构建脚本或配置调度。
|
||
</p>
|
||
</div>
|
||
<span className="rounded-[20px] bg-white/16 px-3 py-2 text-sm">
|
||
{online ? "服务正常" : "服务连接中"}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="mt-[18px] grid grid-cols-4 gap-3.5">
|
||
{(
|
||
[
|
||
{ Icon: FileText, value: scriptCount, label: "工作副本" },
|
||
{ Icon: LayoutGrid, value: 2, label: "Workspace" },
|
||
{ Icon: Settings, value: 4, label: "平台用户" },
|
||
{
|
||
Icon: Check,
|
||
value: online ? "正常" : "检查中",
|
||
label: "平台状态",
|
||
},
|
||
] as Array<{ Icon: LucideIcon; value: number | string; label: string }>
|
||
).map((item) => (
|
||
<article
|
||
key={item.label}
|
||
className="flex items-center gap-3.5 rounded-[9px] border border-[#e0e8ef] bg-white p-5"
|
||
>
|
||
<span className="text-brand">
|
||
<item.Icon />
|
||
</span>
|
||
<span className="flex flex-col">
|
||
<b className="text-[21px] text-[#20364c]">{item.value}</b>
|
||
<small className="text-[#8796a6]">{item.label}</small>
|
||
</span>
|
||
</article>
|
||
))}
|
||
</div>
|
||
|
||
<div className="mt-[18px] flex gap-3">
|
||
<button
|
||
type="button"
|
||
className="flex cursor-pointer items-center gap-2 rounded-[7px] border border-[#d8e4ef] bg-white px-[18px] py-[13px] text-[#346587]"
|
||
onClick={() => onNavigate("scripts")}
|
||
>
|
||
<FileText />
|
||
构建脚本
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className="flex cursor-pointer items-center gap-2 rounded-[7px] border border-[#d8e4ef] bg-white px-[18px] py-[13px] text-[#346587]"
|
||
onClick={() => onNavigate("schedules")}
|
||
>
|
||
<Calendar />
|
||
调度配置
|
||
</button>
|
||
{hasSystemView && (
|
||
<button
|
||
type="button"
|
||
className="flex cursor-pointer items-center gap-2 rounded-[7px] border border-[#d8e4ef] bg-white px-[18px] py-[13px] text-[#346587]"
|
||
onClick={() => onNavigate("system")}
|
||
>
|
||
<Settings />
|
||
系统管理
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
<div className="mt-[18px] grid gap-3.5 pb-[25px] max-[1200px]:grid-cols-1 min-[1201px]:grid-cols-[minmax(0,1.6fr)_minmax(310px,0.9fr)]">
|
||
<section className="rounded-[10px] border border-[#dfe7ee] bg-white shadow-[0_5px_16px_rgb(33_65_97/5%)]">
|
||
<header className="flex items-center justify-between border-b border-line-soft px-5 py-[17px]">
|
||
<div>
|
||
<span className="text-[9px] font-extrabold tracking-[0.09em] text-[#7890a6]">
|
||
运行趋势
|
||
</span>
|
||
<h3 className="mt-1 text-sm text-[#263b50]">近 7 天调度执行</h3>
|
||
</div>
|
||
<b className="rounded-[13px] bg-[#e8f8f1] px-[9px] py-[5px] text-[10px] text-[#16855a]">
|
||
成功率 92.6%
|
||
</b>
|
||
</header>
|
||
<div className="trend-chart-bg flex h-[190px] items-end gap-[18px] px-7 pb-[17px] pt-[26px]">
|
||
{TREND_VALUES.map((value, index) => (
|
||
<div
|
||
className="flex h-full flex-1 flex-col items-center justify-end gap-[5px]"
|
||
key={TREND_LABELS[index]}
|
||
>
|
||
<span className="text-[9px] text-[#7890a3]">
|
||
{Math.round(value / 7)}
|
||
</span>
|
||
<i
|
||
className="block min-h-3 w-[min(36px,72%)] rounded-t-[5px] rounded-b-[2px] bg-gradient-to-b from-[#49a0eb] to-[#207bd1] shadow-[0_4px_9px_rgb(32_123_209/18%)]"
|
||
style={{ height: `${value}%` }}
|
||
/>
|
||
<small className="text-[9px] text-[#7890a3]">
|
||
{TREND_LABELS[index]}
|
||
</small>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<footer className="flex justify-center gap-[22px] p-2.5 text-[9px] text-[#708397]">
|
||
<span className="flex items-center gap-[5px]">
|
||
<LegendDot className="bg-[#27b87c]" />
|
||
成功 75
|
||
</span>
|
||
<span className="flex items-center gap-[5px]">
|
||
<LegendDot className="bg-[#ed7777]" />
|
||
失败 6
|
||
</span>
|
||
</footer>
|
||
</section>
|
||
|
||
<section className="rounded-[10px] border border-[#dfe7ee] bg-white shadow-[0_5px_16px_rgb(33_65_97/5%)]">
|
||
<header className="flex items-center justify-between border-b border-line-soft px-5 py-[17px]">
|
||
<div>
|
||
<span className="text-[9px] font-extrabold tracking-[0.09em] text-[#7890a6]">
|
||
脚本资产
|
||
</span>
|
||
<h3 className="mt-1 text-sm text-[#263b50]">类型分布</h3>
|
||
</div>
|
||
</header>
|
||
<div className="flex min-h-[226px] items-center justify-center gap-7 p-5">
|
||
<div className="dashboard-donut relative grid size-32 shrink-0 place-items-center rounded-full">
|
||
<span className="z-[1] flex flex-col items-center">
|
||
<b className="text-xl text-[#273d53]">{scriptCount}</b>
|
||
<small className="text-[8px] text-[#8a9aaa]">全部脚本</small>
|
||
</span>
|
||
</div>
|
||
<div className="flex flex-col gap-[15px]">
|
||
{[
|
||
{
|
||
color: "bg-[#318de0]",
|
||
title: "Notebook",
|
||
detail: `${notebookCount} 个 · 67%`,
|
||
},
|
||
{
|
||
color: "bg-[#72c9b0]",
|
||
title: "Python",
|
||
detail: `${pythonCount} 个 · 33%`,
|
||
},
|
||
{
|
||
color: "bg-[#f3b75c]",
|
||
title: "稳定版本",
|
||
detail: "3 个已发布",
|
||
},
|
||
].map((item) => (
|
||
<span
|
||
key={item.title}
|
||
className="grid grid-cols-[8px_1fr] items-center gap-x-[7px]"
|
||
>
|
||
<LegendDot className={item.color} />
|
||
<b className="text-[10px] text-[#40566c]">{item.title}</b>
|
||
<small className="col-start-2 text-[8px] text-[#8a9baa]">
|
||
{item.detail}
|
||
</small>
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="col-span-full rounded-[10px] border border-[#dfe7ee] bg-white shadow-[0_5px_16px_rgb(33_65_97/5%)] max-[1200px]:col-auto">
|
||
<header className="flex items-center justify-between border-b border-line-soft px-5 py-[17px]">
|
||
<div>
|
||
<span className="text-[9px] font-extrabold tracking-[0.09em] text-[#7890a6]">
|
||
ACTIVITY
|
||
</span>
|
||
<h3 className="mt-1 text-sm text-[#263b50]">最近平台活动</h3>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
className="border-0 bg-transparent text-[10px] text-[#257bcb]"
|
||
>
|
||
查看全部
|
||
</button>
|
||
</header>
|
||
<div>
|
||
<div className="grid grid-cols-[2fr_0.7fr_0.55fr_0.5fr] items-center gap-[15px] bg-[#f7f9fb] px-5 py-[11px] text-[9px] font-bold text-[#8595a5]">
|
||
<span>操作内容</span>
|
||
<span>执行人</span>
|
||
<span>状态</span>
|
||
<span>时间</span>
|
||
</div>
|
||
{ACTIVITY_ROWS.map((row) => (
|
||
<div
|
||
className="grid grid-cols-[2fr_0.7fr_0.55fr_0.5fr] items-center gap-[15px] border-t border-line-soft px-5 py-[11px] text-[10px] text-[#607488]"
|
||
key={row[0]}
|
||
>
|
||
<span className="flex items-center gap-[9px] text-[#344c63]">
|
||
<i className="grid size-[25px] place-items-center rounded-md bg-[#eaf4ff] text-[#277fcc]">
|
||
<Check size={13} />
|
||
</i>
|
||
{row[0]}
|
||
</span>
|
||
<span>{row[1]}</span>
|
||
<span>
|
||
<b className="rounded-[10px] bg-[#e9f8f2] px-[7px] py-[3px] text-[9px] text-[#16855a]">
|
||
{row[2]}
|
||
</b>
|
||
</span>
|
||
<span>{row[3]}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|