Files
Neo-ZQYY/apps/admin-web/src/pages/OpsPanel.tsx
Neo 6f8f12314f feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:03:48 +08:00

159 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 运维控制面板页面
*
* 功能:
* - 服务器系统资源概况CPU / 内存 / 磁盘)
* - 各环境服务状态 + 启停重启按钮
* - 各环境 Git 状态 + pull / 同步依赖按钮
* - 各环境 .env 配置查看(敏感值脱敏)
*
* CHANGE 2026-07-25 | admin-web-restructure 8.1
* 拆分为 SystemResourceSection / ServiceStatusSection / GitStatusSection 三个子组件,
* 本页面改为组合子组件,功能不变。
*/
import React, { useEffect, useState, useCallback } from "react";
import { Modal, message, Spin, Typography } from "antd";
import { DesktopOutlined } from "@ant-design/icons";
import type { SystemInfo, ServiceStatus, GitInfo } from "../api/opsPanel";
import {
fetchSystemInfo,
fetchServicesStatus,
fetchGitInfo,
startService,
stopService,
restartService,
gitPull,
syncDeps,
} from "../api/opsPanel";
import {
SystemResourceSection,
ServiceStatusSection,
GitStatusSection,
} from "../components/ops";
const { Title } = Typography;
/* ------------------------------------------------------------------ */
/* 组件 */
/* ------------------------------------------------------------------ */
const OpsPanel: React.FC = () => {
const [system, setSystem] = useState<SystemInfo | null>(null);
const [services, setServices] = useState<ServiceStatus[]>([]);
const [gitInfos, setGitInfos] = useState<GitInfo[]>([]);
const [loading, setLoading] = useState(true);
const [actionLoading, setActionLoading] = useState<Record<string, boolean>>({});
// ---- 数据加载 ----
const loadAll = useCallback(async () => {
try {
const [sys, svc, git] = await Promise.all([
fetchSystemInfo(),
fetchServicesStatus(),
fetchGitInfo(),
]);
setSystem(sys);
setServices(svc);
setGitInfos(git);
} catch {
message.error("加载运维数据失败");
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
loadAll();
const timer = setInterval(loadAll, 15_000);
return () => clearInterval(timer);
}, [loadAll]);
// ---- 操作处理 ----
const withAction = async (key: string, fn: () => Promise<void>) => {
setActionLoading((prev) => ({ ...prev, [key]: true }));
try {
await fn();
} finally {
setActionLoading((prev) => ({ ...prev, [key]: false }));
}
};
const handleStart = (env: string) =>
withAction(`start-${env}`, async () => {
const r = await startService(env);
r.success ? message.success(r.message) : message.warning(r.message);
await loadAll();
});
const handleStop = (env: string) =>
withAction(`stop-${env}`, async () => {
const r = await stopService(env);
r.success ? message.success(r.message) : message.warning(r.message);
await loadAll();
});
const handleRestart = (env: string) =>
withAction(`restart-${env}`, async () => {
const r = await restartService(env);
r.success ? message.success(r.message) : message.warning(r.message);
await loadAll();
});
const handlePull = (env: string) =>
withAction(`pull-${env}`, async () => {
const r = await gitPull(env);
if (r.success) {
message.success("拉取成功");
Modal.info({ title: `Git Pull - ${env}`, content: <pre style={{ maxHeight: 300, overflow: "auto", fontSize: 12 }}>{r.output}</pre>, width: 600 });
} else {
message.error("拉取失败");
Modal.error({ title: `Git Pull 失败 - ${env}`, content: <pre style={{ maxHeight: 300, overflow: "auto", fontSize: 12 }}>{r.output}</pre>, width: 600 });
}
await loadAll();
});
const handleSyncDeps = (env: string) =>
withAction(`sync-${env}`, async () => {
const r = await syncDeps(env);
r.success ? message.success("依赖同步完成") : message.error(r.message);
});
// ---- 渲染 ----
if (loading) {
return <Spin size="large" style={{ display: "flex", justifyContent: "center", marginTop: 120 }} />;
}
return (
<div>
<Title level={4} style={{ marginBottom: 16 }}>
<DesktopOutlined style={{ marginRight: 8 }} />
</Title>
{system && <SystemResourceSection system={system} />}
<ServiceStatusSection
services={services}
actionLoading={actionLoading}
onStart={handleStart}
onStop={handleStop}
onRestart={handleRestart}
/>
<GitStatusSection
gitInfos={gitInfos}
services={services}
actionLoading={actionLoading}
onPull={handlePull}
onSyncDeps={handleSyncDeps}
/>
</div>
);
};
export default OpsPanel;