import React, { useState, useEffect } from 'react';
import {
ChevronRight,
CheckCircle,
Circle,
AlertCircle,
Clock,
Play,
Pause,
Settings,
Code,
Server,
Shield,
Database,
Globe,
Zap,
FileText,
Users,
GitBranch,
Package,
Monitor,
ChevronDown
} from 'lucide-react';
const DevOpsPipelineSystem = () => {
const [expandedStep, setExpandedStep] = useState(null);
const [completedSteps, setCompletedSteps] = useState(new Set());
const [stepStatuses, setStepStatuses] = useState({});
const [isProcessing, setIsProcessing] = useState(false);
const [currentExecutingStep, setCurrentExecutingStep] = useState(null);
// 主流程定义
const mainPipeline = [
{
id: 'preparation',
title: '准备阶段',
icon: FileText,
color: '#3B82F6',
description: '项目初始化和环境准备',
subSteps: [
{ id: 'code-review', title: '代码审查', icon: Code, description: '代码质量检查和同行评审', details: '执行静态代码分析,检查代码规范,确保代码质量符合团队标准。' },
{ id: 'dependency-check', title: '依赖检查', icon: Package, description: '检查项目依赖和版本兼容性', details: '扫描项目依赖,检查版本冲突,确保所有依赖项都是最新且兼容的版本。' },
{ id: 'env-config', title: '环境配置', icon: Settings, description: '配置部署环境参数', details: '设置环境变量、配置文件、数据库连接等部署所需的各项参数。' }
]
},
{
id: 'build',
title: '构建阶段',
icon: Package,
color: '#10B981',
description: '代码编译和打包',
subSteps: [
{ id: 'compile', title: '代码编译', icon: Code, description: '编译源代码并生成可执行文件', details: '使用构建工具编译源代码,处理TypeScript、Sass等预处理文件。' },
{ id: 'unit-test', title: '单元测试', icon: CheckCircle, description: '执行单元测试确保代码质量', details: '运行所有单元测试用例,确保代码覆盖率达到标准,验证功能正确性。' },
{ id: 'package-build', title: '打包构建', icon: Package, description: '打包应用程序和资源文件', details: '将编译后的代码和资源文件打包成可部署的格式,优化文件大小。' }
]
},
{
id: 'security',
title: '安全检测',
icon: Shield,
color: '#F59E0B',
description: '安全漏洞扫描和检测',
subSteps: [
{ id: 'vulnerability-scan', title: '漏洞扫描', icon: Shield, description: '扫描代码中的安全漏洞', details: '使用安全扫描工具检测SQL注入、XSS等常见安全漏洞。' },
{ id: 'dependency-security', title: '依赖安全检查', icon: AlertCircle, description: '检查第三方依赖的安全性', details: '扫描第三方依赖包,检查是否存在已知的安全漏洞和风险。' },
{ id: 'compliance-check', title: '合规性检查', icon: FileText, description: '确保符合安全合规要求', details: '验证应用程序是否符合GDPR、SOX等法规要求和公司安全政策。' }
]
},
{
id: 'deployment',
title: '部署阶段',
icon: Server,
color: '#8B5CF6',
description: '应用程序部署和配置',
subSteps: [
{ id: 'staging-deploy', title: '预发布部署', icon: Server, description: '部署到预发布环境进行测试', details: '将应用部署到预发布环境,进行功能验证和性能测试。' },
{ id: 'integration-test', title: '集成测试', icon: Zap, description: '执行集成测试验证功能', details: '在预发布环境中执行端到端测试,验证各系统间的集成。' },
{ id: 'production-deploy', title: '生产部署', icon: Globe, description: '部署到生产环境', details: '使用蓝绿部署或滚动更新方式,将应用安全地部署到生产环境。' }
]
},
{
id: 'monitoring',
title: '监控验证',
icon: Monitor,
color: '#EF4444',
description: '系统监控和健康检查',
subSteps: [
{ id: 'health-check', title: '健康检查', icon: Monitor, description: '检查应用程序运行状态', details: '验证应用服务、数据库连接、外部API等关键组件的健康状态。' },
{ id: 'performance-test', title: '性能测试', icon: Zap, description: '验证系统性能指标', details: '测试应用的响应时间、吞吐量、资源使用率等性能指标。' },
{ id: 'user-acceptance', title: '用户验收', icon: Users, description: '用户验收测试', details: '邀请关键用户进行验收测试,确保功能满足业务需求。' }
]
}
];
const executeStep = async (stepId) => {
setIsProcessing(true);
setCurrentExecutingStep(stepId);
setStepStatuses(prev => ({ ...prev, [stepId]: 'running' }));
// 模拟处理时间
await new Promise(resolve => setTimeout(resolve, 3000));
setStepStatuses(prev => ({ ...prev, [stepId]: 'completed' }));
setCompletedSteps(prev => new Set([...prev, stepId]));
setIsProcessing(false);
setCurrentExecutingStep(null);
};
const getStepStatus = (stepId) => {
if (stepStatuses[stepId] === 'running') return 'running';
if (completedSteps.has(stepId)) return 'completed';
return 'pending';
};
const getMainStepStatus = (step) => {
const completedSubSteps = step.subSteps.filter(subStep => completedSteps.has(subStep.id)).length;
if (completedSubSteps === step.subSteps.length) return 'completed';
if (completedSubSteps > 0) return 'running';
return 'pending';
};
const StepIndicator = ({ status, icon: Icon, color, size = 20 }) => {
const statusStyles = {
completed: {
background: '#10B981',
border: '2px solid #10B981',
color: 'white'
},
running: {
background: color,
border: 2px solid ${color}
,
color: 'white',
animation: 'pulse 2s infinite'
},
pending: {
background: 'rgba(255, 255, 255, 0.1)',
border: '2px solid rgba(255, 255, 255, 0.3)',
color: 'rgba(255, 255, 255, 0.6)'
}
};
return (
<div
className="step-indicator"
style={statusStyles[status]}
>
{status === 'completed' ? <CheckCircle size={size} /> : <Icon size={size} />}
</div>
);
};
const toggleExpanded = (stepId) => {
setExpandedStep(expandedStep === stepId ? null : stepId);
};
return (
DevOps 发布系统
智能化产品发布流水线
<div className="system-status">
<div className="status-item">
<span className="label">总体进度</span>
<span className="value">
{Math.round((completedSteps.size / mainPipeline.reduce((acc, step) => acc + step.subSteps.length, 0)) * 100)}%
</span>
</div>
<div className="status-item">
<span className="label">已完成步骤</span>
<span className="value">
{completedSteps.size} / {mainPipeline.reduce((acc, step) => acc + step.subSteps.length, 0)}
</span>
</div>
</div>
</div>
</div>
<div className="system-content">
<div className="pipeline-container">
<div className="pipeline-header">
<h2>产品发布流水线</h2>
<p>点击各阶段卡片展开详细步骤,引导您完成完整的产品上线流程</p>
</div>
{/* 主流程总览 */}
<div className="main-pipeline">
{mainPipeline.map((step, index) => (
<React.Fragment key={step.id}>
<div
className={`pipeline-step ${expandedStep === step.id ? 'expanded' : ''} ${getMainStepStatus(step)}`}
onClick={() => toggleExpanded(step.id)}
>
<StepIndicator
status={getMainStepStatus(step)}
icon={step.icon}
color={step.color}
size={24}
/>
<div className="step-content">
<h3>{step.title}</h3>
<p>{step.description}</p>
<div className="step-progress">
<div className="progress-bar">
<div
className="progress-fill"
style={{
width: `${(step.subSteps.filter(subStep => completedSteps.has(subStep.id)).length / step.subSteps.length) * 100}%`,
background: step.color
}}
/>
</div>
<span className="progress-text">
{step.subSteps.filter(subStep => completedSteps.has(subStep.id)).length} / {step.subSteps.length}
</span>
</div>
</div>
<div className="expand-indicator">
<ChevronDown
size={20}
className={`expand-arrow ${expandedStep === step.id ? 'rotated' : ''}`}
/>
</div>
{expandedStep === step.id && (
<div className="active-border" style={{ background: step.color }} />
)}
</div>
{index < mainPipeline.length - 1 && (
<div className="pipeline-connector">
<ChevronRight size={24} />
</div>
)}
</React.Fragment>
))}
</div>
{/* 详细步骤展开区域 */}
{expandedStep && (
<div className="detailed-steps-section">
<div className="section-header">
<h3>{mainPipeline.find(s => s.id === expandedStep)?.title} - 详细步骤</h3>
<p>按顺序执行以下步骤完成 {mainPipeline.find(s => s.id === expandedStep)?.title}</p>
</div>
<div className="substeps-grid">
{mainPipeline.find(s => s.id === expandedStep)?.subSteps.map((subStep, index) => (
<div
key={subStep.id}
className={`substep-card ${getStepStatus(subStep.id)} ${currentExecutingStep === subStep.id ? 'executing' : ''}`}
>
<div className="card-header">
<StepIndicator
status={getStepStatus(subStep.id)}
icon={subStep.icon}
color={mainPipeline.find(s => s.id === expandedStep)?.color}
size={20}
/>
<div className="step-number">步骤 {index + 1}</div>
</div>
<div className="card-content">
<h4>{subStep.title}</h4>
<p className="description">{subStep.description}</p>
<p className="details">{subStep.details}</p>
</div>
<div className="card-actions">
{getStepStatus(subStep.id) === 'pending' && (
<button
className="execute-btn"
onClick={() => executeStep(subStep.id)}
disabled={isProcessing}
style={{ background: mainPipeline.find(s => s.id === expandedStep)?.color }}
>
{currentExecutingStep === subStep.id ? (
<>
<Clock size={16} />
执行中...
</>
) : (
<>
<Play size={16} />
开始执行
</>
)}
</button>
)}
{getStepStatus(subStep.id) === 'completed' && (
<div className="completed-badge">
<CheckCircle size={16} />
已完成
</div>
)}
{getStepStatus(subStep.id) === 'running' && (
<div className="running-badge">
<Clock size={16} />
执行中
</div>
)}
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
<style jsx>{`
.devops-system {
min-height: 100vh;
background: linear-gradient(135deg, #1e1b4b 0%, #312e81 50%, #1e3a8a 100%);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: white;
}
.system-header {
background: rgba(30, 27, 75, 0.8);
backdrop-filter: blur(20px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding: 24px 40px;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1400px;
margin: 0 auto;
}
.logo {
display: flex;
align-items: center;
gap: 16px;
}
.logo h1 {
margin: 0;
font-size: 28px;
font-weight: 700;
background: linear-gradient(135deg, #60a5fa, #a78bfa);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.logo p {
margin: 0;
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
}
.system-status {
display: flex;
gap: 32px;
}
.status-item {
text-align: right;
}
.status-item .label {
display: block;
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 4px;
}
.status-item .value {
display: block;
font-size: 18px;
font-weight: 600;
color: white;
}
.system-content {
padding: 40px;
max-width: 1400px;
margin: 0 auto;
}
.pipeline-container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 40px;
}
.pipeline-header {
text-align: center;
margin-bottom: 40px;
}
.pipeline-header h2 {
font-size: 32px;
margin: 0 0 12px 0;
background: linear-gradient(135deg, #60a5fa, #a78bfa);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.pipeline-header p {
color: rgba(255, 255, 255, 0.7);
font-size: 16px;
margin: 0;
}
.main-pipeline {
display: flex;
align-items: center;
gap: 24px;
margin-bottom: 40px;
overflow-x: auto;
padding: 20px 0;
}
.pipeline-step {
background: rgba(255, 255, 255, 0.08);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 24px;
min-width: 280px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
display: flex;
flex-direction: column;
gap: 16px;
}
.pipeline-step:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);
background: rgba(255, 255, 255, 0.12);
}
.pipeline-step.expanded {
border-color: rgba(96, 165, 250, 0.6);
box-shadow: 0 8px 16px rgba(96, 165, 250, 0.3);
}
.pipeline-step.completed {
border-color: rgba(16, 185, 129, 0.6);
}
.pipeline-step.running {
border-color: rgba(251, 191, 36, 0.6);
animation: glow 2s ease-in-out infinite alternate;
}
@keyframes glow {
from { box-shadow: 0 0 10px rgba(251, 191, 36, 0.3); }
to { box-shadow: 0 0 20px rgba(251, 191, 36, 0.6); }
}
.active-border {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
border-radius: 16px 16px 0 0;
}
.step-indicator {
width: 56px;
height: 56px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
align-self: flex-start;
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.8; }
}
.step-content {
flex: 1;
}
.step-content h3 {
margin: 0 0 8px 0;
font-size: 20px;
font-weight: 600;
}
.step-content p {
margin: 0 0 16px 0;
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
line-height: 1.5;
}
.step-progress {
display: flex;
align-items: center;
gap: 12px;
}
.progress-bar {
flex: 1;
height: 6px;
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
transition: width 0.3s ease;
border-radius: 3px;
}
.progress-text {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
min-width: 40px;
}
.expand-indicator {
align-self: center;
}
.expand-arrow {
transition: transform 0.3s ease;
color: rgba(255, 255, 255, 0.6);
}
.expand-arrow.rotated {
transform: rotate(180deg);
}
.pipeline-connector {
color: rgba(255, 255, 255, 0.4);
flex-shrink: 0;
}
.detailed-steps-section {
border-top: 1px solid rgba(255, 255, 255, 0.1);
padding-top: 40px;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.section-header {
margin-bottom: 32px;
}
.section-header h3 {
margin: 0 0 8px 0;
font-size: 24px;
font-weight: 700;
color: white;
}
.section-header p {
margin: 0;
color: rgba(255, 255, 255, 0.7);
font-size: 16px;
}
.substeps-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 24px;
}
.substep-card {
background: rgba(255, 255, 255, 0.08);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 24px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.substep-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
.substep-card.completed {
border-color: rgba(16, 185, 129, 0.6);
background: rgba(16, 185, 129, 0.1);
}
.substep-card.running {
border-color: rgba(59, 130, 246, 0.6);
background: rgba(59, 130, 246, 0.1);
}
.substep-card.executing {
animation: executeGlow 2s ease-in-out infinite;
}
@keyframes executeGlow {
0%, 100% { box-shadow: 0 0 10px rgba(59, 130, 246, 0.3); }
50% { box-shadow: 0 0 20px rgba(59, 130, 246, 0.6); }
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.card-header .step-indicator {
width: 48px;
height: 48px;
}
.step-number {
background: rgba(255, 255, 255, 0.1);
padding: 6px 12px;
border-radius: 8px;
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
}
.card-content h4 {
margin: 0 0 12px 0;
font-size: 18px;
font-weight: 600;
color: white;
}
.card-content .description {
margin: 0 0 12px 0;
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
line-height: 1.5;
}
.card-content .details {
margin: 0 0 20px 0;
color: rgba(255, 255, 255, 0.6);
font-size: 13px;
line-height: 1.6;
}
.card-actions {
margin-top: auto;
}
.execute-btn {
display: flex;
align-items: center;
gap: 8px;
background: #3B82F6;
border: none;
border-radius: 12px;
padding: 12px 20px;
color: white;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
width: 100%;
justify-content: center;
}
.execute-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(59, 130, 246, 0.3);
}
.execute-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.completed-badge {
display: flex;
align-items: center;
gap: 8px;
background: rgba(16, 185, 129, 0.2);
color: #10B981;
padding: 12px 20px;
border-radius: 12px;
justify-content: center;
font-weight: 500;
}
.running-badge {
display: flex;
align-items: center;
gap: 8px;
background: rgba(59, 130, 246, 0.2);
color: #3B82F6;
padding: 12px 20px;
border-radius: 12px;
justify-content: center;
font-weight: 500;
animation: pulse 2s infinite;
}
@media (max-width: 768px) {
.system-header {
padding: 20px;
}
.header-content {
flex-direction: column;
gap: 20px;
text-align: center;
}
.system-status {
justify-content: center;
}
.system-content {
padding: 20px;
}
.pipeline-container {
padding: 24px;
}
.main-pipeline {
flex-direction: column;
align-items: stretch;
}
.pipeline-step {
min-width: auto;
}
.pipeline-connector {
transform: rotate(90deg);
margin: 12px 0;
}
.substeps-grid {
flex-direction: column;
align-items: stretch;
}
.substep-card {
min-width: auto;
}
.substep-connector {
transform: rotate(90deg);
margin: 12px 0;
align-self: center;
}
}
`}</style>
</div>
);
};
export default DevOpsPipelineSystem;