feat: 工具配置增强、站点拨测与日志中心多项改进

- 工具配置:新增卸载 API/UI、Go 工具链、流水线构建自动安装依赖
- 修复 ListTools VerifyTool 递归栈溢出导致获取工具配置失败
- Node.js Alpine 安装改用 unofficial-builds musl 预编译包与 index.json 解析 LTS
- 站点拨测:详情页路由、告警自动恢复、并发拨测与默认可观测频率
- 日志中心:RBAC、查询构建器、ES/Loki 增强与审计 request/response 展示
- 发布配置:构建节点、流水线远程执行、Deploy RBAC 导航拆分
This commit is contained in:
Logan committed 2026-07-02 07:47:33 +08:00
1 parent cde7bbe540
commit 7e1c8f583c
84 files changed
+7729 -1507

No files matched your search

@@ -42,7 +42,7 @@ import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { toast } from 'sonner';
import { pipelineApi, deployApi } from '@/api';
import { pipelineApi, deployApi, buildNodeApi, type BuildNode } from '@/api';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { PageShell } from '@/components/layout/PageShell';
import { PageEmpty } from '@/components/layout/PageEmpty';
@@ -182,6 +182,8 @@ export default function PipelineEditor() {
const [webhookToken, setWebhookToken] = useState('');
const [variables, setVariables] = useState<[string, string][]>([]);
const [environmentId, setEnvironmentId] = useState<number | string>('');
const [buildNodes, setBuildNodes] = useState<BuildNode[]>([]);
const [buildNodeId, setBuildNodeId] = useState('');
// member add dialog
const [newMemberUser, setNewMemberUser] = useState('');
@@ -230,6 +232,7 @@ export default function PipelineEditor() {
setCronExpr(p.cron_expr || '');
setWebhookToken(p.webhook_token || '');
setEnvironmentId(p.environment_id || '');
setBuildNodeId(p.build_node_id ? String(p.build_node_id) : '');
try {
const parsed = p.variables ? JSON.parse(p.variables) : {};
setVariables(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
@@ -249,6 +252,10 @@ export default function PipelineEditor() {
loadEnvironments();
loadMembers();
loadRuns();
buildNodeApi.list().then((res) => {
const data = res as { list?: BuildNode[] };
setBuildNodes(Array.isArray(data?.list) ? data.list.filter((n) => n.enabled) : []);
}).catch(() => { /* silent */ });
}, [pipelineCode, loadEnvironments, loadMembers, loadRuns]);
const selectedTask = stages.flatMap(s => s.tasks).find(t => t.id === selectedTaskId) || null;
@@ -273,6 +280,7 @@ export default function PipelineEditor() {
webhook_token: webhookToken,
environment_id: environmentId ? Number(environmentId) : null,
variables: JSON.stringify(Object.fromEntries(variables.filter(([k]) => k.trim()))),
build_node_id: buildNodeId ? Number(buildNodeId) : null,
});
toast.success('已保存');
} catch { toast.error('保存失败'); }
@@ -386,7 +394,20 @@ export default function PipelineEditor() {
))}
</SelectContent>
</Select>
<span className="text-xs text-[var(--dg-text-secondary)] opacity-70">选择后流水线执行记录会关联到对应环境</span>
<Server className="w-4 h-4 text-[var(--dg-text-secondary)] ml-2" />
<span className="text-sm text-[var(--dg-text-secondary)]">构建节点:</span>
<Select value={buildNodeId || '__auto'} onValueChange={v => setBuildNodeId(v === '__auto' ? '' : v)}>
<SelectTrigger className="w-48 h-8 text-sm"><SelectValue placeholder="自动调度" /></SelectTrigger>
<SelectContent>
<SelectItem value="__auto">自动调度(Master 优先)</SelectItem>
{buildNodes.map(n => (
<SelectItem key={n.id} value={String(n.id)}>
{n.name} ({n.running_count || 0}/{n.max_executors})
</SelectItem>
))}
</SelectContent>
</Select>
<span className="text-xs text-[var(--dg-text-secondary)] opacity-70">Master 满载时自动调度到空闲远程节点</span>
</CardContent>
</Card>
@@ -491,6 +512,7 @@ export default function PipelineEditor() {
<th className="px-3 py-2 text-left text-[var(--dg-text-secondary)]">状态</th>
<th className="px-3 py-2 text-left text-[var(--dg-text-secondary)]">触发方式</th>
<th className="px-3 py-2 text-left text-[var(--dg-text-secondary)]">触发人</th>
<th className="px-3 py-2 text-left text-[var(--dg-text-secondary)]">节点</th>
<th className="px-3 py-2 text-left text-[var(--dg-text-secondary)]">分支</th>
<th className="px-3 py-2 text-left text-[var(--dg-text-secondary)]">耗时</th>
<th className="px-3 py-2 text-left text-[var(--dg-text-secondary)]">时间</th>
@@ -516,6 +538,7 @@ export default function PipelineEditor() {
{!r.trigger_mode && (r.triggered_by || '手动')}
</td>
<td className="px-3 py-2 text-[var(--dg-text-secondary)] text-xs">{r.triggered_by || 'admin'}</td>
<td className="px-3 py-2 text-[var(--dg-text-secondary)] text-xs">{r.build_node_name || 'Master'}</td>
<td className="px-3 py-2 text-[var(--dg-text-secondary)] font-mono text-xs">{r.branch || '-'}</td>
<td className="px-3 py-2 text-[var(--dg-text-secondary)]">{fmtDuration(r.duration)}</td>
<td className="px-3 py-2 text-[var(--dg-text-secondary)] text-xs">{r.created_at ? new Date(r.created_at).toLocaleString('zh-CN') : '-'}</td>