feat(alert): A2 告警引擎分布式安全 — Redis 分布式锁(SetNX)
- Cache 接口新增 SetNX 方法(go-redis 原子 / redka check-then-set 单副本安全) - AlertEngine 改造为支持分布式锁: tryAcquireLock/releaseLock/GetEngineStatus - 多副本部署时仅 Leader 执行告警检查, 60s TTL 防死锁, fail-open 容错 - 新增 GET /alerts/engine/status 监控端点(is_leader/instance_id/lock_holder) - 修复 GrafanaDashboard.tsx Panel.options 类型不匹配导致构建中断 - 同步未提交的前端类型安全迁移(140+ 文件) - ROADMAP/TDD_PLAN/PROJECT_EVALUATION 三文档同步更新 验证: go build 零错误, Docker 部署 healthy, 引擎状态端点正常, 锁竞争测试通过(非 Leader 跳过检查), 5/5 核心 API 回归通过
This commit is contained in:
1 parent
5ca0e6d83a
commit
c68aef4444
150 files changed
+1038
-690
No files matched your search
@@ -9,6 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
import { Plus, Key, Globe, Container } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { credentialApi, deployApi } from '@/api';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
interface Repo {
|
||||
id: number; name: string; url: string; credential_id: number | null;
|
||||
@@ -47,7 +48,7 @@ export default function CredentialFormDialog({ open, onOpenChange, type, editId,
|
||||
|
||||
const loadCredentials = async () => {
|
||||
try {
|
||||
const res: Record<string, any> = await credentialApi.list({ page_size: 100 });
|
||||
const res: JsonData = await credentialApi.list({ page_size: 100 });
|
||||
setCredentials(res?.list || []);
|
||||
} catch { /* silent */ }
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||
import { deployApi } from '@/api';
|
||||
import CredentialFormDialog from './CredentialFormDialog';
|
||||
import ToolConfigTab from './ToolConfigTab';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
interface Repo {
|
||||
id: number; name: string; description: string; url: string;
|
||||
@@ -110,16 +111,16 @@ export default function DeployCredentials() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [rR, rReg, rEnv] = await Promise.all([deployApi.getRepos(), deployApi.getRegistries(), deployApi.getEnvironments()]);
|
||||
setRepos((rR as Record<string, any>)?.list || []);
|
||||
setRegistries((rReg as Record<string, any>)?.list || []);
|
||||
setEnvironments((rEnv as Record<string, any>)?.list || []);
|
||||
setRepos((rR as JsonData)?.list || []);
|
||||
setRegistries((rReg as JsonData)?.list || []);
|
||||
setEnvironments((rEnv as JsonData)?.list || []);
|
||||
} catch { toast.error('获取数据失败'); }
|
||||
finally { setLoading(false); }
|
||||
};
|
||||
|
||||
useEffect(() => { fetchAll(); }, []);
|
||||
|
||||
const handleDelete = async (type: 'repo' | 'registry' | 'env', item: any) => {
|
||||
const handleDelete = async (type: 'repo' | 'registry' | 'env', item: JsonData) => {
|
||||
const ok = await _confirm({ title: '删除', description: `确定删除「${item.name || item.display_name}」?`, confirmText: '删除', variant: 'destructive' });
|
||||
if (!ok) return;
|
||||
try {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Label } from '@/components/ui/label';
|
||||
import { toast } from 'sonner';
|
||||
import { deployApi, userApi, pipelineApi } from '@/api';
|
||||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
interface ItemRole {
|
||||
id: number; name: string; code: string; pattern: string; role: string;
|
||||
@@ -27,11 +28,11 @@ export default function DeployRBAC() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
// Pipeline list for live preview
|
||||
const [pipelines, setPipelines] = useState<any[]>([]);
|
||||
const [pipelines, setPipelines] = useState<JsonData[]>([]);
|
||||
const [previewRoleId, setPreviewRoleId] = useState<number | null>(null);
|
||||
|
||||
// User assignment
|
||||
const [users, setUsers] = useState<any[]>([]);
|
||||
const [users, setUsers] = useState<JsonData[]>([]);
|
||||
const [assignments, setAssignments] = useState<AssignedUser[]>([]);
|
||||
const [selectedUserId, setSelectedUserId] = useState<string>('');
|
||||
const [assignRoleId, setAssignRoleId] = useState<string>('');
|
||||
@@ -60,9 +61,9 @@ export default function DeployRBAC() {
|
||||
pipelineApi.getPipelines({ page_size: 200 }),
|
||||
userApi.getUsers({ page_size: 500 }),
|
||||
]);
|
||||
setRoles(Array.isArray(rolesRes) ? rolesRes : (rolesRes as Record<string, any>)?.list || (rolesRes as Record<string, any>)?.data || []);
|
||||
setPipelines(Array.isArray(pipesRes) ? pipesRes : (pipesRes as Record<string, any>)?.list || (pipesRes as Record<string, any>)?.data || []);
|
||||
const uList = Array.isArray(usersRes) ? usersRes : (usersRes as Record<string, any>)?.list || (usersRes as Record<string, any>)?.data || [];
|
||||
setRoles(Array.isArray(rolesRes) ? rolesRes : (rolesRes as JsonData)?.list || (rolesRes as JsonData)?.data || []);
|
||||
setPipelines(Array.isArray(pipesRes) ? pipesRes : (pipesRes as JsonData)?.list || (pipesRes as JsonData)?.data || []);
|
||||
const uList = Array.isArray(usersRes) ? usersRes : (usersRes as JsonData)?.list || (usersRes as JsonData)?.data || [];
|
||||
setUsers(uList);
|
||||
} catch { /* */ }
|
||||
finally { setLoading(false); }
|
||||
@@ -73,7 +74,7 @@ export default function DeployRBAC() {
|
||||
const loadAssignments = async () => {
|
||||
if (!selectedUserId) { setAssignments([]); return; }
|
||||
try {
|
||||
const res: Record<string, any> = await deployApi.getUserItemRoles(Number(selectedUserId));
|
||||
const res: JsonData = await deployApi.getUserItemRoles(Number(selectedUserId));
|
||||
setAssignments(Array.isArray(res) ? res : res?.data || res?.list || []);
|
||||
} catch { setAssignments([]); }
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { GitBranch, GitCommit, Clock, ChevronDown, Loader2 } from 'lucide-react';
|
||||
import type { PipelineStage } from './pipeline-helpers';
|
||||
import { parseStages, getStatusBadge, getStageStatusIcon } from './pipeline-helpers';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
// 流水线详情弹窗(阶段/配置/历史三 Tab) // Logan.Li
|
||||
export function PipelineDetailDialog({
|
||||
@@ -29,8 +30,8 @@ export function PipelineDetailDialog({
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
selectedPipeline: any;
|
||||
pipelineRuns: any[];
|
||||
selectedPipeline?: JsonData;
|
||||
pipelineRuns: JsonData[];
|
||||
loadingRuns: boolean;
|
||||
activeStage: string | null;
|
||||
setActiveStage: (id: string | null) => void;
|
||||
@@ -131,7 +132,7 @@ export function PipelineDetailDialog({
|
||||
<div className="text-[var(--dg-text-secondary)] text-center py-8 text-sm">暂无执行记录</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{pipelineRuns.map(( run: any) => (
|
||||
{pipelineRuns.map((run: JsonData) => (
|
||||
<div
|
||||
key={run.id}
|
||||
className="flex items-center gap-3 p-3 rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)]"
|
||||
|
||||
@@ -44,6 +44,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u
|
||||
import { toast } from 'sonner';
|
||||
import { pipelineApi, deployApi } from '@/api';
|
||||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
// ---- types ----
|
||||
interface Task {
|
||||
@@ -58,7 +59,7 @@ interface LogEntry {
|
||||
}
|
||||
|
||||
const stageTypeLabels: Record<string, string> = { source: '拉取代码', build: '构建', deploy: '部署', test: '测试', custom: '自定义' };
|
||||
const taskIcons: Record<string, any> = { GitBranch, Wrench, Server, FlaskConical, Zap, Terminal, FileCode, Container };
|
||||
const taskIcons: JsonData = { GitBranch, Wrench, Server, FlaskConical, Zap, Terminal, FileCode, Container };
|
||||
|
||||
function uid() { return `tsk-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; }
|
||||
function fmtDuration(s: number | undefined): string {
|
||||
@@ -119,7 +120,7 @@ function TaskEditor({ task, onUpdate }: { task: Task | null; onUpdate: (t: Task)
|
||||
}
|
||||
|
||||
// ====== Log Dialog ======
|
||||
function LogDialog({ open, onClose, run, logs }: { open: boolean; onClose: () => void; run: any; logs: LogEntry[] }) {
|
||||
function LogDialog({ open, onClose, run, logs }: { open: boolean; onClose: () => void; run?: JsonData; logs: LogEntry[] }) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
||||
@@ -160,16 +161,16 @@ function LogDialog({ open, onClose, run, logs }: { open: boolean; onClose: () =>
|
||||
export default function PipelineEditor() {
|
||||
const { code } = useParams<{ code: string }>();
|
||||
const pipelineCode = code || '';
|
||||
const [pipeline, setPipeline] = useState<any>(null);
|
||||
const [pipeline, setPipeline] = useState<JsonData>(null);
|
||||
const [stages, setStages] = useState<Stage[]>([]);
|
||||
const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
|
||||
const [expandedStages, setExpandedStages] = useState<Record<string, boolean>>({});
|
||||
const [activeTab, setActiveTab] = useState('flow');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [runs, setRuns] = useState<any[]>([]);
|
||||
const [environments, setEnvironments] = useState<any[]>([]);
|
||||
const [members, setMembers] = useState<any[]>([]);
|
||||
const [runs, setRuns] = useState<JsonData[]>([]);
|
||||
const [environments, setEnvironments] = useState<JsonData[]>([]);
|
||||
const [members, setMembers] = useState<JsonData[]>([]);
|
||||
const [loadingMembers, setLoadingMembers] = useState(false);
|
||||
|
||||
// trigger config
|
||||
@@ -186,7 +187,7 @@ export default function PipelineEditor() {
|
||||
|
||||
// log dialog
|
||||
const [logOpen, setLogOpen] = useState(false);
|
||||
const [logRun, setLogRun] = useState<any>(null);
|
||||
const [logRun, setLogRun] = useState<JsonData>(null);
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
|
||||
const _confirm = useConfirm();
|
||||
@@ -194,7 +195,7 @@ export default function PipelineEditor() {
|
||||
const loadEnvironments = useCallback(async () => {
|
||||
try {
|
||||
const res = await deployApi.getEnvironments();
|
||||
setEnvironments(Array.isArray(res) ? res : (res as Record<string, any>)?.list || []);
|
||||
setEnvironments(Array.isArray(res) ? res : (res as JsonData)?.list || []);
|
||||
} catch { /* silent */ }
|
||||
}, []);
|
||||
|
||||
@@ -202,7 +203,7 @@ export default function PipelineEditor() {
|
||||
setLoadingMembers(true);
|
||||
try {
|
||||
const res = await pipelineApi.getMembers(pipelineCode);
|
||||
setMembers(Array.isArray(res) ? res : (res as Record<string, any>)?.data || []);
|
||||
setMembers(Array.isArray(res) ? res : (res as JsonData)?.data || []);
|
||||
} catch { setMembers([]); }
|
||||
finally { setLoadingMembers(false); }
|
||||
}, [pipelineCode]);
|
||||
@@ -210,7 +211,7 @@ export default function PipelineEditor() {
|
||||
const loadRuns = useCallback(async () => {
|
||||
try {
|
||||
const res = await pipelineApi.getRuns(pipelineCode);
|
||||
setRuns(Array.isArray(res) ? res : (res as Record<string, any>)?.list || []);
|
||||
setRuns(Array.isArray(res) ? res : (res as JsonData)?.list || []);
|
||||
} catch { setRuns([]); }
|
||||
}, [pipelineCode]);
|
||||
|
||||
@@ -218,7 +219,7 @@ export default function PipelineEditor() {
|
||||
(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const p = await pipelineApi.getPipelineDetail(pipelineCode) as Record<string, any>;
|
||||
const p = await pipelineApi.getPipelineDetail(pipelineCode) as JsonData;
|
||||
// http.get 已自动解包 json.data,p 即为 pipeline 对象
|
||||
if (!p || !p.id) { setPipeline(null); setLoading(false); return; }
|
||||
setPipeline(p);
|
||||
@@ -294,11 +295,11 @@ export default function PipelineEditor() {
|
||||
} catch { toast.error('停止失败'); }
|
||||
};
|
||||
|
||||
const handleViewLog = async ( run: any) => {
|
||||
const handleViewLog = async (run: JsonData) => {
|
||||
setLogRun(run);
|
||||
setLogOpen(true);
|
||||
try {
|
||||
const data = await pipelineApi.getRunLog(pipelineCode, run.id) as Record<string, any>;
|
||||
const data = await pipelineApi.getRunLog(pipelineCode, run.id) as JsonData;
|
||||
setLogs(data?.logs || []);
|
||||
} catch { setLogs([]); }
|
||||
};
|
||||
@@ -482,7 +483,7 @@ export default function PipelineEditor() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{runs.map(( r: any) => (
|
||||
{runs.map((r: JsonData) => (
|
||||
<tr key={r.id} className="border-b border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/50">
|
||||
<td className="px-3 py-2 font-medium text-[var(--dg-text-primary)]">#{r.build_number || r.id}</td>
|
||||
<td className="px-3 py-2">
|
||||
@@ -552,8 +553,8 @@ export default function PipelineEditor() {
|
||||
|
||||
{/* Members list — split direct users vs group assignments */}
|
||||
{(() => {
|
||||
const directUsers = members.filter(( m: any) => m.username);
|
||||
const groupAssignments = members.filter(( m: any) => m.group_id);
|
||||
const directUsers = members.filter((m: JsonData) => m.username);
|
||||
const groupAssignments = members.filter((m: JsonData) => m.group_id);
|
||||
return (
|
||||
<>
|
||||
{/* Direct users */}
|
||||
@@ -568,7 +569,7 @@ export default function PipelineEditor() {
|
||||
<div className="py-4 text-center text-[var(--dg-text-secondary)] text-xs">暂无直接用户</div>
|
||||
) : (
|
||||
<div className="divide-y divide-[var(--dg-border)]">
|
||||
{directUsers.map(( m: any) => (
|
||||
{directUsers.map((m: JsonData) => (
|
||||
<div key={m.id} className="flex items-center justify-between px-4 py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-blue-500/20 flex items-center justify-center"><Users className="w-4 h-4 text-blue-400" /></div>
|
||||
@@ -597,7 +598,7 @@ export default function PipelineEditor() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y divide-[var(--dg-border)]">
|
||||
{groupAssignments.map(( m: any) => (
|
||||
{groupAssignments.map((m: JsonData) => (
|
||||
<div key={m.id} className="flex items-center justify-between px-4 py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-purple-500/20 flex items-center justify-center"><Shield className="w-4 h-4 text-purple-400" /></div>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { toast } from 'sonner';
|
||||
import { pipelineApi } from '@/api';
|
||||
import { FileBrowserDialog } from './FileBrowserDialog';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
// ═══════════ 阶段定义 (与 PipelineManager 共用) ═══════════
|
||||
const STAGE_DEFS = [
|
||||
@@ -61,7 +62,7 @@ const STATUS_BADGES: Record<string, string> = {
|
||||
cancelled: 'bg-amber-500/20 text-amber-400 border-amber-500/30',
|
||||
};
|
||||
|
||||
const STATUS_ICONS: Record<string, any> = {
|
||||
const STATUS_ICONS: JsonData = {
|
||||
success: CheckCircle2,
|
||||
failed: XCircle,
|
||||
running: Loader2,
|
||||
@@ -101,8 +102,8 @@ function getRunStageStats(run: PipelineRun | null): Map<string, string> {
|
||||
// 如果 run.stages 有数据,直接用
|
||||
const statuses = parseStageStatuses(run.stages);
|
||||
statuses.forEach(s => {
|
||||
if (s.name || (s as Record<string, any>).type) {
|
||||
const key = (s as Record<string, any>).type || (STAGE_DEFS.find(d => d.label === s.name)?.key);
|
||||
if (s.name || (s as JsonData).type) {
|
||||
const key = (s as JsonData).type || (STAGE_DEFS.find(d => d.label === s.name)?.key);
|
||||
if (key) map.set(key, s.status);
|
||||
}
|
||||
});
|
||||
@@ -128,7 +129,7 @@ function getStageLogSummary(logs: LogEntry[], stageKey: string): string {
|
||||
|
||||
export default function PipelineRunDetail() {
|
||||
const { code: pipelineCode } = useParams<{ code: string }>();
|
||||
const [pipeline, setPipeline] = useState<any>(null);
|
||||
const [pipeline, setPipeline] = useState<JsonData>(null);
|
||||
const [runs, setRuns] = useState<PipelineRun[]>([]);
|
||||
const [selectedRun, setSelectedRun] = useState<PipelineRun | null>(null);
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
@@ -147,7 +148,7 @@ export default function PipelineRunDetail() {
|
||||
// 加载流水线详情
|
||||
const fetchPipeline = useCallback(async () => {
|
||||
try {
|
||||
const data: Record<string, any> = await pipelineApi.getByCode(pipelineCode);
|
||||
const data: JsonData = await pipelineApi.getByCode(pipelineCode);
|
||||
setPipeline(data?.data || data);
|
||||
} catch { /* silent, pipeline info optional */ }
|
||||
}, [pipelineCode]);
|
||||
@@ -156,7 +157,7 @@ export default function PipelineRunDetail() {
|
||||
const fetchRuns = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data: Record<string, any> = await pipelineApi.getRunsByCode(pipelineCode, pageSize);
|
||||
const data: JsonData = await pipelineApi.getRunsByCode(pipelineCode, pageSize);
|
||||
const list = data?.list || [];
|
||||
setRuns(list);
|
||||
if (!selectedRun && list.length > 0) {
|
||||
@@ -180,7 +181,7 @@ export default function PipelineRunDetail() {
|
||||
// 加载日志
|
||||
const fetchLogs = useCallback(async (run: PipelineRun) => {
|
||||
try {
|
||||
const data: Record<string, any> = await pipelineApi.getRunLogStream(run.id);
|
||||
const data: JsonData = await pipelineApi.getRunLogStream(run.id);
|
||||
if (data?.logs) {
|
||||
const parsed = typeof data.logs === 'string' ? JSON.parse(data.logs) : data.logs;
|
||||
setLogs(Array.isArray(parsed) ? parsed : []);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Play } from 'lucide-react';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
// 执行流水线弹窗(分支/Commit/环境变量) // Logan.Li
|
||||
export function PipelineRunDialog({
|
||||
@@ -21,7 +22,7 @@ export function PipelineRunDialog({
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
selectedPipeline: any;
|
||||
selectedPipeline?: JsonData;
|
||||
onRun: () => void;
|
||||
}) {
|
||||
return (
|
||||
|
||||
@@ -12,9 +12,10 @@ import { Button } from '@/components/ui/button';
|
||||
import { AlertCircle, X, Check, Loader2 } from 'lucide-react';
|
||||
import { releaseApi } from '@/api';
|
||||
import { toast } from 'sonner';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
interface ReleaseApproveDialogProps {
|
||||
release: any | null;
|
||||
release: JsonData | null;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSuccess?: () => void;
|
||||
|
||||
@@ -15,9 +15,10 @@ import { Rocket, CheckCircle, Loader2, RotateCcw } from 'lucide-react';
|
||||
import { releaseApi } from '@/api';
|
||||
import { toast } from 'sonner';
|
||||
import { PIPELINE_STEPS } from './releaseHelpers';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
interface ReleaseDetailDialogProps {
|
||||
release: any | null;
|
||||
release: JsonData | null;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSuccess?: () => void;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
|
||||
import { toast } from 'sonner';
|
||||
import { deployApi } from '@/api';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
interface ToolConfig {
|
||||
id: number; name: string; display_name: string; description: string;
|
||||
@@ -55,8 +56,8 @@ export default function ToolConfigTab() {
|
||||
const fetchTools = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res: Record<string, any> = await deployApi.getTools();
|
||||
setTools((res as Record<string, any>)?.list || []);
|
||||
const res: JsonData = await deployApi.getTools();
|
||||
setTools((res as JsonData)?.list || []);
|
||||
} catch { toast.error('获取工具配置失败'); }
|
||||
finally { setLoading(false); }
|
||||
};
|
||||
@@ -65,8 +66,8 @@ export default function ToolConfigTab() {
|
||||
|
||||
const openVersionDialog = async (tool: ToolConfig) => {
|
||||
try {
|
||||
const res: Record<string, any> = await deployApi.getToolVersions(tool.id);
|
||||
const versions = (res as Record<string, any>)?.versions || [];
|
||||
const res: JsonData = await deployApi.getToolVersions(tool.id);
|
||||
const versions = (res as JsonData)?.versions || [];
|
||||
if (versions.length === 0) {
|
||||
// No versions — install directly
|
||||
handleInstall(tool);
|
||||
@@ -99,7 +100,7 @@ export default function ToolConfigTab() {
|
||||
|
||||
const handleCheckStatus = async (tool: ToolConfig) => {
|
||||
try {
|
||||
const res: Record<string, any> = await deployApi.checkTool(tool.id);
|
||||
const res: JsonData = await deployApi.checkTool(tool.id);
|
||||
toast.success(res?.available ? `${tool.display_name} 可用` : `${tool.display_name} 不可用`);
|
||||
fetchTools();
|
||||
} catch { toast.error('检测失败'); }
|
||||
@@ -119,9 +120,9 @@ export default function ToolConfigTab() {
|
||||
const start = Date.now();
|
||||
try {
|
||||
// Simulate a quick reachability test by fetching the version URL
|
||||
const res: Record<string, any> = await deployApi.getToolVersions(tool.id);
|
||||
const res: JsonData = await deployApi.getToolVersions(tool.id);
|
||||
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
||||
const available = (res as Record<string, any>)?.versions?.length > 0;
|
||||
const available = (res as JsonData)?.versions?.length > 0;
|
||||
if (available) {
|
||||
toast.success(`${tool.display_name} 镜像可访问 (${elapsed}s)`);
|
||||
} else {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Label } from '@/components/ui/label';
|
||||
import { toast } from 'sonner';
|
||||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||
import { deployApi } from '@/api';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
interface Group {
|
||||
id: number; name: string; description: string;
|
||||
@@ -35,7 +36,7 @@ export default function UserGroupsManager() {
|
||||
|
||||
const loadGroups = async () => {
|
||||
setLoading(true);
|
||||
try { const res = await deployApi.getGroups(); setGroups((res as Record<string, any>)?.list || []); }
|
||||
try { const res = await deployApi.getGroups(); setGroups((res as JsonData)?.list || []); }
|
||||
catch { toast.error('加载失败'); }
|
||||
finally { setLoading(false); }
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Maximize2, Minimize2, X, FolderOpen } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useUserStore } from '@/store';
|
||||
import { pipelineApi } from '@/api';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
interface WorkspaceTermDialogProps {
|
||||
open: boolean;
|
||||
@@ -41,7 +42,7 @@ export function WorkspaceTermDialog({
|
||||
useEffect(() => {
|
||||
if (!open || !runId) return;
|
||||
pipelineApi.getWorkspace(runId)
|
||||
.then(( data: any) => setWorkspace(data?.workspace_path || ''))
|
||||
.then((data: JsonData) => setWorkspace(data?.workspace_path || ''))
|
||||
.catch(() => {});
|
||||
}, [open, runId]);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { CheckCircle, XCircle, Loader2 } from 'lucide-react';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
// 流水线阶段定义 // Logan.Li
|
||||
export interface PipelineStage {
|
||||
@@ -19,7 +20,7 @@ export const pipelineTemplates = [
|
||||
];
|
||||
|
||||
// 解析 stages 字段(兼容 JSON 数组 / 逗号分隔字符串) // Logan.Li
|
||||
export function parseStages( stages: any): PipelineStage[] {
|
||||
export function parseStages( stages: string): PipelineStage[] {
|
||||
if (Array.isArray(stages)) return stages;
|
||||
if (typeof stages === 'string') {
|
||||
try {
|
||||
@@ -42,7 +43,7 @@ export function nameToCode(name: string): string {
|
||||
}
|
||||
|
||||
// 状态徽章(使用后端 last_status 字段) // Logan.Li
|
||||
export function getStatusBadge( status: any) {
|
||||
export function getStatusBadge( status: string) {
|
||||
switch (status) {
|
||||
case 'success':
|
||||
return (
|
||||
|
||||
@@ -70,7 +70,7 @@ export function PipelineCard({
|
||||
#{String(pipeline.run_count)}
|
||||
</Badge>
|
||||
)}
|
||||
{getStatusBadge(pipeline.last_status)}
|
||||
{getStatusBadge(String(pipeline.last_status))}
|
||||
{environment && (
|
||||
<Badge
|
||||
style={{ backgroundColor: `${environment.color}20`, color: environment.color as string }}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// 发布相关共享工具函数与常量 // Logan.Li
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import type { JsonData } from '@/types/common';
|
||||
import {
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
|
||||
// 发布状态徽章(列表 + 详情共用) // Logan.Li
|
||||
export function getStatusBadge( status: any) {
|
||||
export function getStatusBadge( status: string) {
|
||||
switch (status) {
|
||||
case 'success':
|
||||
return (
|
||||
@@ -59,7 +60,7 @@ export function getStatusBadge( status: any) {
|
||||
}
|
||||
|
||||
// 环境徽章 // Logan.Li
|
||||
export function getEnvBadge( env: any) {
|
||||
export function getEnvBadge( env: string) {
|
||||
const colors: Record<string, string> = {
|
||||
dev: 'border-slate-500/30 text-slate-400 bg-slate-500/10',
|
||||
test: 'border-blue-500/30 text-blue-400 bg-blue-500/10',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 发布管理(审批流)共享工具函数与常量 // Logan.Li
|
||||
// 注意:本文件专用于 ReleaseManager(含审批流,状态标签与 AppRelease 的 releaseHelpers 不同)
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import type { JsonData } from '@/types/common';
|
||||
import {
|
||||
Clock,
|
||||
Check,
|
||||
@@ -19,7 +20,7 @@ export const ENVIRONMENTS: Record<string, { label: string; color: string }> = {
|
||||
};
|
||||
|
||||
// 发布状态徽章(审批流:待审批/已审批/已驳回/发布中/发布成功/发布失败/已回滚) // Logan.Li
|
||||
export function getStatusBadge( status: any) {
|
||||
export function getStatusBadge( status: string) {
|
||||
const map: Record<string, { icon: any; label: string; cls: string }> = {
|
||||
pending: { icon: Clock, label: '待审批', cls: 'border-amber-500/30 text-amber-400 bg-amber-500/10' },
|
||||
approved: { icon: Check, label: '已审批', cls: 'border-purple-500/30 text-purple-400 bg-purple-500/10' },
|
||||
@@ -41,7 +42,7 @@ export function getStatusBadge( status: any) {
|
||||
}
|
||||
|
||||
// 环境徽章 // Logan.Li
|
||||
export function getEnvironmentBadge( env: any) {
|
||||
export function getEnvironmentBadge( env: string) {
|
||||
const cfg = ENVIRONMENTS[env];
|
||||
return (
|
||||
<Badge variant="outline" className={cfg?.color || 'border-slate-500/30 text-slate-400'}>
|
||||
|
||||
Reference in new issue
Block a user