import { Check, FileText, Loader2, Pause, Play, Square, X } from 'lucide-react'; import React, { useMemo, useState } from 'react'; import { useI18n } from '@/application/i18n/I18nProvider'; import type { ScriptRun } from '@/types/global/netcatty-bridge-script.d.ts'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils.ts'; import { ScriptRunLogDialog } from './ScriptRunLogDialog'; export interface ScriptRunListProps { runs: ScriptRun[]; onStop: (runId: string) => void; onPause: (runId: string) => void; onResume: (runId: string) => void; } function formatDuration(startedAt: number, endedAt?: number) { const ms = (endedAt ?? Date.now()) - startedAt; const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const rest = seconds % 60; return `${String(minutes).padStart(2, '0')}:${String(rest).padStart(2, '0')}`; } function ScriptStatusIcon({ status }: { status: ScriptRun['status'] }) { if (status === 'completed') { return ; } if (status === 'failed') { return ; } return ; } function resolveRunMeta(run: ScriptRun, t: (key: string, params?: Record) => string) { const elapsed = formatDuration(run.startedAt, run.endedAt); const parts: string[] = [t(`scripts.running.status.${run.status}`)]; if ((run.stepIndex ?? 0) > 0 || run.status === 'running' || run.status === 'paused') { parts.push(t('scripts.running.operationsCount', { count: run.stepIndex ?? 0 })); } if (run.status === 'running' || run.status === 'paused') { if (run.progressMode === 'determinate' && run.progressTotal) { parts.push(t('scripts.running.determinateProgress', { label: run.progressLabel || t('scripts.running.progressFallback'), current: run.progressCurrent ?? 0, total: run.progressTotal, })); } else if (run.activityLabel) { parts.push(run.activityLabel); } else if (run.waitingFor) { parts.push(t('scripts.running.waitingFor', { pattern: run.waitingFor })); } } parts.push(t('scripts.running.elapsed', { elapsed })); return parts.join(' ยท '); } function sortRuns(runs: ScriptRun[]): ScriptRun[] { const rank = (status: ScriptRun['status']) => { if (status === 'running') return 0; if (status === 'paused') return 1; return 2; }; return [...runs].sort((a, b) => { const rankDiff = rank(a.status) - rank(b.status); if (rankDiff !== 0) return rankDiff; return b.startedAt - a.startedAt; }); } export const ScriptRunList: React.FC = ({ runs, onStop, onPause, onResume, }) => { const { t } = useI18n(); const [logRunId, setLogRunId] = useState(null); const sortedRuns = useMemo(() => sortRuns(runs), [runs]); const logRun = sortedRuns.find((run) => run.runId === logRunId) ?? null; if (runs.length === 0) { return (
{t('scripts.running.empty')}
); } return ( <>
{sortedRuns.map((run) => { const label = run.scriptLabel || run.scriptId || t('scripts.running.unnamed'); const isActive = run.status === 'running' || run.status === 'paused'; return (
{label}
{resolveRunMeta(run, t)}
{run.error ? (
{run.error}
) : null}
{run.status === 'running' ? ( ) : null} {run.status === 'paused' ? ( ) : null} {isActive ? ( ) : null}
); })}
{ if (!open) setLogRunId(null); }} /> ); };