feat: FlowStack 发布系统首个可运行版本 (gin + React)

- Go gin 后端:流水线/节点/连线/运行/日志/GitOps/插件 REST API,
  SQLite(GORM) 自动建表 + seed,模拟执行引擎 + SSE 实时日志推送,
  前端产物 go:embed 嵌入单二进制,一个端口同时服务 API 与页面
- React 19 前端:画布式流水线编辑器(React Flow + Tekton 任务库)、
  GitOps 部署、批量发布、发布记录等 14 个模块页面,
  tRPC 全部替换为 REST(TanStack Query)
- 首次启动生成随机密码管理员账号(日志 + data/initial-admin-password.txt)
- run.sh 一键构建启动(默认端口 9527,支持 start/stop/restart/build/dev)
This commit is contained in:
Logan.Li committed 2026-10-27 02:44:10 +08:00
commit 04d1342b27
115 files changed
+23268

No files matched your search

+272
View File
@@ -0,0 +1,272 @@
import { useState } from 'react';
import { Bell, Info, TestTube } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { Separator } from '@/components/ui/separator';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { toast } from 'sonner';
export default function BasicInfoPanel() {
const [pipelineName, setPipelineName] = useState('gateway-pre-serverless');
const [description, setDescription] = useState('');
// Feishu notification
const [feishuEnabled, setFeishuEnabled] = useState(false);
const [feishuWebhook, setFeishuWebhook] = useState('');
const [feishuTiming, setFeishuTiming] = useState('all');
// DingTalk notification
const [dingtalkEnabled, setDingtalkEnabled] = useState(false);
const [dingtalkWebhook, setDingtalkWebhook] = useState('');
const [dingtalkTiming, setDingtalkTiming] = useState('all');
const handleTestConnection = (type: 'feishu' | 'dingtalk') => {
toast.success(`${type === 'feishu' ? '飞书' : '钉钉'} Webhook 连接测试成功`, {
description: '通知服务可正常访问',
});
};
const timingOptions = [
{ value: 'all', label: '全部事件' },
{ value: 'success', label: '仅成功' },
{ value: 'failure', label: '仅失败' },
];
return (
<div className="tab-content-fade space-y-6 w-full px-6 py-6">
{/* Basic Information */}
<div
className="rounded-lg border p-6 shadow-sm"
style={{
background: 'var(--panel-bg)',
borderColor: 'var(--border-color)',
}}
>
<div className="flex items-center gap-2 mb-5">
<Info className="w-5 h-5" style={{ color: 'var(--accent-blue)' }} />
<h3
className="text-base font-semibold"
style={{ color: 'var(--text-primary)' }}
>
基本信息
</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
<div>
<Label className="text-sm font-medium mb-1.5 block" style={{ color: 'var(--text-secondary)' }}>
流水线名称
</Label>
<Input
value={pipelineName}
onChange={(e) => setPipelineName(e.target.value)}
placeholder="输入流水线名称"
className="h-9"
/>
</div>
<div>
<Label className="text-sm font-medium mb-1.5 block" style={{ color: 'var(--text-secondary)' }}>
描述
</Label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="输入流水线描述(可选)"
rows={1}
className="w-full px-3 py-2 text-sm rounded-md border outline-none focus:ring-1 focus:ring-[var(--accent-blue)] focus:border-transparent transition-all resize-none h-9"
style={{
background: 'var(--canvas-bg)',
borderColor: 'var(--border-color)',
color: 'var(--text-primary)',
}}
/>
</div>
<div>
<Label className="text-xs mb-1 block" style={{ color: 'var(--text-secondary)', opacity: 0.7 }}>
创建人
</Label>
<span className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>
admin
</span>
</div>
<div>
<Label className="text-xs mb-1 block" style={{ color: 'var(--text-secondary)', opacity: 0.7 }}>
创建时间
</Label>
<span className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>
2024-06-20
</span>
</div>
<div>
<Label className="text-xs mb-1 block" style={{ color: 'var(--text-secondary)', opacity: 0.7 }}>
最近执行
</Label>
<span className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>
未执行
</span>
</div>
</div>
</div>
{/* Notification Settings */}
<div
className="rounded-lg border p-6 shadow-sm"
style={{
background: 'var(--panel-bg)',
borderColor: 'var(--border-color)',
}}
>
<div className="flex items-center gap-2 mb-5">
<Bell className="w-5 h-5" style={{ color: 'var(--accent-blue)' }} />
<h3
className="text-base font-semibold"
style={{ color: 'var(--text-primary)' }}
>
通知设置
</h3>
</div>
<div className="space-y-6">
{/* Feishu */}
<div>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<span className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>
飞书通知
</span>
<Tooltip>
<TooltipTrigger asChild>
<Info className="w-3.5 h-3.5 cursor-help" style={{ color: 'var(--text-secondary)', opacity: 0.5 }} />
</TooltipTrigger>
<TooltipContent>
<p>通过飞书群机器人发送流水线执行通知</p>
</TooltipContent>
</Tooltip>
</div>
<Switch checked={feishuEnabled} onCheckedChange={setFeishuEnabled} />
</div>
{feishuEnabled && (
<div className="tab-content-fade pl-0 space-y-3">
<div>
<Label className="text-xs mb-1.5 block" style={{ color: 'var(--text-secondary)' }}>
Webhook 地址
</Label>
<Input
value={feishuWebhook}
onChange={(e) => setFeishuWebhook(e.target.value)}
placeholder="https://open.feishu.cn/open-apis/bot/v2/hook/..."
className="h-9"
/>
</div>
<div>
<Label className="text-xs mb-2 block" style={{ color: 'var(--text-secondary)' }}>
通知时机
</Label>
<RadioGroup
value={feishuTiming}
onValueChange={setFeishuTiming}
className="flex items-center gap-4"
>
{timingOptions.map((opt) => (
<div key={opt.value} className="flex items-center space-x-2">
<RadioGroupItem value={opt.value} id={`feishu-${opt.value}`} />
<Label htmlFor={`feishu-${opt.value}`} className="text-sm cursor-pointer">
{opt.label}
</Label>
</div>
))}
</RadioGroup>
</div>
<div className="pt-1">
<Button
variant="outline"
size="sm"
onClick={() => handleTestConnection('feishu')}
className="h-8"
>
<TestTube className="w-3.5 h-3.5 mr-1.5" />
测试连接
</Button>
</div>
</div>
)}
</div>
<Separator style={{ backgroundColor: 'var(--border-color)' }} />
{/* DingTalk */}
<div>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<span className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>
钉钉通知
</span>
<Tooltip>
<TooltipTrigger asChild>
<Info className="w-3.5 h-3.5 cursor-help" style={{ color: 'var(--text-secondary)', opacity: 0.5 }} />
</TooltipTrigger>
<TooltipContent>
<p>通过钉钉群机器人发送流水线执行通知</p>
</TooltipContent>
</Tooltip>
</div>
<Switch checked={dingtalkEnabled} onCheckedChange={setDingtalkEnabled} />
</div>
{dingtalkEnabled && (
<div className="tab-content-fade pl-0 space-y-3">
<div>
<Label className="text-xs mb-1.5 block" style={{ color: 'var(--text-secondary)' }}>
Webhook 地址
</Label>
<Input
value={dingtalkWebhook}
onChange={(e) => setDingtalkWebhook(e.target.value)}
placeholder="https://oapi.dingtalk.com/robot/send?access_token=..."
className="h-9"
/>
</div>
<div>
<Label className="text-xs mb-2 block" style={{ color: 'var(--text-secondary)' }}>
通知时机
</Label>
<RadioGroup
value={dingtalkTiming}
onValueChange={setDingtalkTiming}
className="flex items-center gap-4"
>
{timingOptions.map((opt) => (
<div key={opt.value} className="flex items-center space-x-2">
<RadioGroupItem value={opt.value} id={`dingtalk-${opt.value}`} />
<Label htmlFor={`dingtalk-${opt.value}`} className="text-sm cursor-pointer">
{opt.label}
</Label>
</div>
))}
</RadioGroup>
</div>
<div className="pt-1">
<Button
variant="outline"
size="sm"
onClick={() => handleTestConnection('dingtalk')}
className="h-8"
>
<TestTube className="w-3.5 h-3.5 mr-1.5" />
测试连接
</Button>
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
}