feat: 去 mock 化 —— 全模块接入真实数据 (参考 jianmu/opsx-jenkins/aiops)

后端(35+ 新路由):
- AES-256-GCM 字段加密(enc:v1:),密钥自动生成 data/encryption.key
- 新模型:Host/K8sCluster/Credential/BuildNode/DeployTool/WebhookEndpoint/
  JenkinsServer/SystemSetting;Pipeline 扩展触发器/变量/缓存列,
  PipelineRun 扩展 buildNumber/externalUrl
- 资源 CRUD + 真实测试端点:主机/集群/构建节点 TCP 拨号、
  工具真实执行版本检测(保守安装策略)、Webhook HMAC-SHA256 签名发送、
  入站流水线触发 /api/hooks/pipeline/:id
- Jenkins 完整集成(移植 opsx-jenkins 生产客户端):crumb/BasicAuth/
  buildWithParameters/Location 队列/progressiveText 增量控制台,
  服务器 CRUD+测试、Job 树拉取(folder 压平)、导入为流水线、
  jenkins 类型流水线真实执行回传日志
- 流水线列表附带 lastRun、运行重试、summary 统计、系统设置单行表
- 运行结束触发出站 Webhook 通知;GitOps 同步状态真实推进

前端(14 页全部去 mock):
- 流水线列表/发布记录/批量发布/环境/凭证/构建节点/工具/Webhook/
  系统设置/Jenkins 导入/GitOps(资源树+差异) 全部接入真实 API
- 编辑器 4 Tab 持久化(基本信息/触发设置含真实 Webhook URL/变量缓存/发布历史)
- TopBar 真实流水线切换 + 仅保存/保存并运行
- Sidebar 徽标真实统计;删除未使用组件与 mock 数据文件
This commit is contained in:
Logan.Li committed 2026-10-27 03:15:20 +08:00
1 parent 04d1342b27
commit 26341c3e8a
37 files changed
+5702 -4683

No files matched your search

+47 -30
View File
@@ -1,5 +1,6 @@
import { useState, useCallback } from 'react';
import { Toaster } from 'sonner';
import { useQuery } from '@tanstack/react-query';
import Sidebar from '@/components/Sidebar';
import TopBar from '@/components/TopBar';
import PipelineFlowEditor from '@/components/flow/PipelineFlowEditor';
@@ -19,7 +20,7 @@ import WebhookConfig from '@/pages/WebhookConfig';
import GitOpsDeploy from '@/pages/GitOpsDeploy';
import PluginManager from '@/pages/PluginManager';
import SystemSettings from '@/pages/SystemSettings';
import { pipelineScenarios } from '@/data/pipelineData';
import { apiClient } from '@/lib/api';
export type ModuleType = 'pipelines' | 'gitops' | 'batch-release' | 'release-records' | 'environments' | 'credentials' | 'build-nodes' | 'tools' | 'jenkins-import' | 'webhooks' | 'plugins' | 'settings';
export type EditorTab = '基本信息' | '流程配置' | '触发设置' | '变量和缓存' | '发布';
@@ -31,19 +32,27 @@ export default function App() {
// Pipeline list/editor view toggle
const [pipelineView, setPipelineView] = useState<'list' | 'editor'>('list');
// Current editing pipeline ID for flow editor
const [currentPipelineId, setCurrentPipelineId] = useState<number>(1);
// Pipeline editor state (only used when in pipeline editor mode)
const [activeScenario, setActiveScenario] = useState<'go-backend' | 'vue-frontend'>('go-backend');
const [activeTab, setActiveTab] = useState<EditorTab>('流程配置');
const [isSimulating, setIsSimulating] = useState(false);
const [executionStatus, setExecutionStatus] = useState<'idle' | 'running' | 'success' | 'failed'>('idle');
const [saveTick, setSaveTick] = useState(0);
const [runTick, setRunTick] = useState(0);
const scenario = pipelineScenarios[activeScenario];
const { data: pipelines = [] } = useQuery({
queryKey: ['pipelines'],
queryFn: apiClient.listPipelines,
});
const handleScenarioChange = (key: 'go-backend' | 'vue-frontend') => {
setActiveScenario(key);
};
// 当前流水线最近一次运行状态(TopBar 徽章)
const { data: currentRuns = [] } = useQuery({
queryKey: ['runs', { pipelineId: currentPipelineId }],
queryFn: () => apiClient.listRuns({ pipelineId: currentPipelineId }),
refetchInterval: (query) =>
query.state.data?.[0]?.status === 'running' || query.state.data?.[0]?.status === 'pending' ? 2000 : false,
});
const currentPipeline = pipelines.find((p: any) => p.id === currentPipelineId);
const latestStatus = currentRuns[0]?.status as string | undefined;
const isPipelineRunning = latestStatus === 'running' || latestStatus === 'pending';
const handleSelectPipeline = useCallback((id: string) => {
setCurrentPipelineId(Number(id) || 1);
@@ -51,13 +60,19 @@ export default function App() {
setActiveTab('流程配置');
}, []);
const handleSimulateRun = useCallback(() => {
setIsSimulating(true);
setExecutionStatus('running');
setTimeout(() => {
setIsSimulating(false);
setExecutionStatus('success');
}, 3000);
const handlePipelineSwitch = useCallback((id: number) => {
setCurrentPipelineId(id);
setActiveTab('流程配置');
}, []);
const handleOnlySave = useCallback(() => {
if (activeTab !== '流程配置') setActiveTab('流程配置');
setSaveTick((t) => t + 1);
}, [activeTab]);
const handleSaveAndRun = useCallback(() => {
setActiveTab('流程配置');
setRunTick((t) => t + 1);
}, []);
// Render content based on active module
@@ -71,40 +86,42 @@ export default function App() {
return (
<div className="h-full flex flex-col">
<TopBar
pipelineName={scenario.name}
pipelineName={currentPipeline?.name || '...'}
pipelines={pipelines.map((p: any) => ({ id: p.id, name: p.name }))}
currentPipelineId={currentPipelineId}
onPipelineChange={handlePipelineSwitch}
activeTab={activeTab}
onTabChange={setActiveTab}
onSimulateRun={handleSimulateRun}
isSimulating={isSimulating}
scenarioKey={activeScenario}
onScenarioChange={handleScenarioChange}
executionStatus={executionStatus}
onOnlySave={handleOnlySave}
onSaveAndRun={handleSaveAndRun}
isRunning={isPipelineRunning}
executionStatus={(latestStatus as 'running' | 'success' | 'failed' | 'cancelled') || 'idle'}
onBack={() => setPipelineView('list')}
/>
<div className="flex-1 flex overflow-hidden">
{activeTab === '流程配置' && (
<div className="flex-1 overflow-hidden">
<PipelineFlowEditor pipelineId={currentPipelineId} />
<PipelineFlowEditor pipelineId={currentPipelineId} saveSignal={saveTick} runSignal={runTick} />
</div>
)}
{activeTab === '基本信息' && (
<div className="flex-1 overflow-y-auto">
<BasicInfoPanel />
<BasicInfoPanel pipelineId={currentPipelineId} />
</div>
)}
{activeTab === '触发设置' && (
<div className="flex-1 overflow-y-auto">
<TriggerPanel />
<TriggerPanel pipelineId={currentPipelineId} />
</div>
)}
{activeTab === '变量和缓存' && (
<div className="flex-1 overflow-y-auto">
<VariablesPanel />
<VariablesPanel pipelineId={currentPipelineId} />
</div>
)}
{activeTab === '发布' && (
<div className="flex-1 overflow-hidden">
<ReleasePanel />
<div className="flex-1 overflow-y-auto">
<ReleasePanel pipelineId={currentPipelineId} />
</div>
)}
</div>
@@ -121,7 +138,7 @@ export default function App() {
case 'jenkins-import':
return <JenkinsImport />;
case 'batch-release':
return <BatchRelease />;
return <BatchRelease onViewRecords={() => setActiveModule('release-records')} />;
case 'release-records':
return <ReleaseRecords />;
case 'webhooks':