export interface ActivityEvent { id: number; created_at: string; message: string } export interface OperationStep { label: string; state: 'pending' | 'current' | 'done' | 'failed' } interface BetaServer { stage: string; deployed_at: string | null; restarted_at: string | null; healthy_at: string | null; match_seen_at: string | null; } export function betaFlow(beta: { state: string; wait_for_health: number; wait_for_match: number; testPlan?: boolean }, servers: BetaServer[]): OperationStep[] { const stages = ['deploying', 'awaiting-empty', 'restarting']; const labels = ['Stage file changes', 'Wait for empty servers', 'Apply files and restart']; if (beta.wait_for_health) { stages.push('awaiting-health'); labels.push('Verify health'); } if (beta.wait_for_match) { stages.push('awaiting-match'); labels.push('Observe a completed match'); } if (beta.testPlan) { stages.push('observing'); labels.push('Evaluate test requirements'); } stages.push('ready'); labels.push('Review and promote'); const positions = servers.map(s => { if (s.stage !== 'failed') return Math.max(0, stages.indexOf(s.stage)); if (!s.deployed_at) return 0; if (!s.restarted_at) return 2; if (beta.wait_for_health && !s.healthy_at) return stages.indexOf('awaiting-health'); if (beta.wait_for_match && !s.match_seen_at) return stages.indexOf('awaiting-match'); return stages.length - 1; }); const current = beta.state === 'promoted' ? stages.length : beta.state === 'ready-promote' ? stages.length - 1 : positions.length ? Math.min(...positions) : 0; return labels.map((label, i) => ({ label, state: i < current ? 'done' : i === current ? beta.state === 'rolled-back' ? 'pending' : servers.some((s, j) => s.stage === 'failed' && positions[j] === i) ? 'failed' : 'current' : 'pending' })); } export function campaignFlow(servers: { server?: string; restart_state: string; restarted_at: string | null; files_changed: number }[], finished: boolean, files: { server: string; snapshot_pending: number; apply_state: string }[] = []): OperationStep[] { const active = servers.filter(s => !['skipped', 'cancelled'].includes(s.restart_state)); const positions = active.map(s => { if (s.restart_state === 'done') return 5; if (s.restarted_at) return 4; const changes = files.filter(f => f.server === s.server && f.apply_state !== 'cancelled'); if (changes.some(f => f.apply_state === 'applying')) return changes.some(f => f.snapshot_pending) ? 1 : 2; if (changes.length && changes.every(f => f.apply_state === 'applied')) return 3; return 0; }); const current = positions.length ? Math.min(...positions) : finished ? 5 : 0; return ['Wait for empty servers and restart capacity', 'Capture rollback snapshots', 'Apply file changes', 'Restart servers', 'Verify health'].map((label, i) => ({ label, state: i < current ? 'done' : i !== current ? 'pending' : active.some((s, j) => s.restart_state === 'failed' && positions[j] === i) ? 'failed' : active.some(s => ['manual', 'held'].includes(s.restart_state)) ? 'pending' : 'current' })); }