feat: 接入运维真实数据与三角色权限链路

- 新增 model_deploy 与 model_operations 双库查询,支持模型列表、模型详情、单月监控结果和运维工作台真实接口。

- 关闭运维模块生产 Mock 数据,补充缺表/空数据降级、工作台空状态和首条纵向链路测试。

- 按业务团队、模型团队、管理员权限矩阵接入菜单、页面、操作按钮和后端接口权限校验,支持 business_team 角色。

- 更新工作台布局、深色欢迎卡片、全宽页面适配、顶部回退,以及权限分组展示。

- 新增架构实现基线、周目标完成情况和角色权限矩阵初始化 SQL 文档。
This commit is contained in:
郑龙捷
2026-09-02 15:02:47 +08:00
parent 6d044784ec
commit 74dd97428f
51 changed files with 2142 additions and 1062 deletions
@@ -7,12 +7,11 @@ import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle }
import { Input } from "~/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
import { exportRowsToExcel } from "~/lib/exportExcel";
import { usePersistentState } from "~/hooks/use-persistent-state";
import { AbnormalBadge, FilterSelect, GradeBadge, OperationsPageHeader } from "./OperationsUi";
import { MODEL_CATEGORIES, type ModelCategoryId, type ModelGrade, type ModelRecord } from "./modelData";
import { BASE_THRESHOLDS, MONITORING_RULES, RULE_VERSIONS, type MonitoringRule, type ThresholdConfig } from "./governanceData";
import { useOperationsData } from "./OperationsDataContext";
import { isOperationsActionVisibleForRole, useOperationsRole } from "./operationsRole";
import { useCanOperationsAction } from "./operationsRole";
type CategoryKey = "all" | ModelCategoryId;
@@ -42,13 +41,12 @@ function NumberField({ label, value, disabled, onChange }: { label: string; valu
export default function RuleManagementPage() {
const { models } = useOperationsData();
const operationsRole = useOperationsRole();
const canManage = isOperationsActionVisibleForRole(operationsRole, "rules:manage");
const canManage = useCanOperationsAction("rules:manage");
const initialConfigs: Record<CategoryKey, ThresholdConfig | null> = { all: { ...BASE_THRESHOLDS }, std: null, bai: null, big: null, afd: null };
const [category, setCategory] = useState<CategoryKey>("all");
const [configs, setConfigs] = usePersistentState("a-card-rule-configs", initialConfigs);
const [configs, setConfigs] = useState(initialConfigs);
const [simulation, setSimulation] = useState<ThresholdConfig>({ ...BASE_THRESHOLDS });
const [versions, setVersions] = usePersistentState("a-card-rule-versions", RULE_VERSIONS);
const [versions] = useState(RULE_VERSIONS);
const activeCut = configs[category] ?? configs.all ?? BASE_THRESHOLDS;
const editable = canManage && (category === "all" || configs[category] !== null);
const scope = models.filter((model) => model.status !== "下线" && (category === "all" || model.category === category));
@@ -86,13 +84,13 @@ export default function RuleManagementPage() {
const publishVersion = () => {
const version = `V${versions.length + 1}`;
setVersions((current) => [{ version, category: category === "all" ? "全部大类" : MODEL_CATEGORIES.find((item) => item.id === category)?.name ?? category, author: "管理员 王芳", createdAt: "2026-08-31 10:30", current: true, note: "手动发布(Mock" }, ...current.map((item) => ({ ...item, current: false }))]);
toast.success(`已模拟发布规则 ${version}`);
void version;
toast.info("规则发布接口尚未接入");
};
return (
<section className="h-full overflow-auto bg-bg p-6">
<div className="mx-auto flex max-w-screen-2xl flex-col gap-6 pb-8">
<div className="flex w-full flex-col gap-6 pb-8">
<OperationsPageHeader title="监控结果等级规则" description={canManage ? "维护规则矩阵、模型大类切点、版本留痕与阈值影响测算。" : "模型团队只读查看当前规则矩阵与版本记录。"} actions={canManage ? <Button onClick={publishVersion}><Save /></Button> : undefined} />
<Card>
@@ -113,7 +111,7 @@ export default function RuleManagementPage() {
<div className="grid grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)] gap-6">
<Card>
<CardHeader><CardTitle></CardTitle><CardDescription></CardDescription></CardHeader>
<CardContent className="space-y-3">{versions.map((version) => <div className="flex items-center gap-3 rounded-xl border border-border p-4" key={version.version}><b className="text-sm text-foreground">{version.version}</b><span className="rounded-full bg-muted px-2.5 py-1 text-xs">{version.category}</span><span className="min-w-0 flex-1"><small className="block truncate text-xs text-muted-foreground">{version.note}</small><small className="text-3xs text-muted-foreground">{version.author} · {version.createdAt}</small></span>{version.current ? <span className="rounded-full bg-success-soft px-2.5 py-1 text-xs text-success-strong"></span> : canManage ? <Button variant="outline" size="xs" onClick={() => { setVersions((current) => current.map((item) => ({ ...item, current: item.version === version.version }))); toast.success(`已模拟回滚至 ${version.version}`); }}></Button> : <span className="rounded-full bg-muted px-2.5 py-1 text-xs text-muted-foreground"></span>}</div>)}</CardContent>
<CardContent className="space-y-3">{versions.length ? versions.map((version) => <div className="flex items-center gap-3 rounded-xl border border-border p-4" key={version.version}><b className="text-sm text-foreground">{version.version}</b><span className="rounded-full bg-muted px-2.5 py-1 text-xs">{version.category}</span><span className="min-w-0 flex-1"><small className="block truncate text-xs text-muted-foreground">{version.note}</small><small className="text-3xs text-muted-foreground">{version.author} · {version.createdAt}</small></span>{version.current ? <span className="rounded-full bg-success-soft px-2.5 py-1 text-xs text-success-strong"></span> : canManage ? <Button variant="outline" size="xs" onClick={() => toast.info("规则回滚接口尚未接入")}></Button> : <span className="rounded-full bg-muted px-2.5 py-1 text-xs text-muted-foreground"></span>}</div>) : <div className="py-10 text-center text-sm text-muted-foreground"></div>}</CardContent>
</Card>
{canManage && <Card>