feat: real time progress update for runs

This commit is contained in:
amhsirak
2026-01-02 11:42:32 +05:30
parent 0e3d430f41
commit 8a32c0b2d2
2 changed files with 119 additions and 4 deletions

View File

@@ -12,6 +12,45 @@ import { GenericModal } from "../ui/GenericModal";
import { getUserById } from "../../api/auth";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
import { io, Socket } from "socket.io-client";
import { remoteBrowserApiUrl } from "../../apiConfig";
const socketCache = new Map<string, Socket>();
const progressCallbacks = new Map<string, Set<(data: any) => void>>();
function getOrCreateSocket(browserId: string): Socket {
if (socketCache.has(browserId)) {
return socketCache.get(browserId)!;
}
const socket = io(`${remoteBrowserApiUrl}/${browserId}`, {
transports: ["websocket"],
rejectUnauthorized: false
});
socket.on('workflowProgress', (data: any) => {
const callbacks = progressCallbacks.get(browserId);
if (callbacks) {
callbacks.forEach(cb => cb(data));
}
});
socketCache.set(browserId, socket);
return socket;
}
function cleanupSocketIfUnused(browserId: string) {
const callbacks = progressCallbacks.get(browserId);
if (!callbacks || callbacks.size === 0) {
const socket = socketCache.get(browserId);
if (socket) {
socket.disconnect();
socketCache.delete(browserId);
progressCallbacks.delete(browserId);
}
}
}
interface RunTypeChipProps {
runByUserId?: string;
@@ -54,6 +93,53 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, onToggleExpanded, cu
const logEndRef = useRef<HTMLDivElement | null>(null);
const [workflowProgress, setWorkflowProgress] = useState<{
current: number;
total: number;
percentage: number;
} | null>(null);
// Subscribe to progress updates using module-level socket cache
useEffect(() => {
if (!row.browserId) return;
// Get or create socket (from module cache)
getOrCreateSocket(row.browserId);
// Register callback
if (!progressCallbacks.has(row.browserId)) {
progressCallbacks.set(row.browserId, new Set());
}
const callback = (data: any) => {
setWorkflowProgress(data);
};
progressCallbacks.get(row.browserId)!.add(callback);
// Cleanup: remove callback and cleanup socket if no callbacks remain
return () => {
const callbacks = progressCallbacks.get(row.browserId);
if (callbacks) {
callbacks.delete(callback);
// Cleanup socket if this was the last callback
cleanupSocketIfUnused(row.browserId);
}
};
}, [row.browserId]);
// Clear progress UI when run completes and trigger socket cleanup
useEffect(() => {
if (row.status !== 'running' && row.status !== 'queued') {
setWorkflowProgress(null);
// Attempt to cleanup socket when run completes
// (will only cleanup if no other callbacks exist)
if (row.browserId) {
cleanupSocketIfUnused(row.browserId);
}
}
}, [row.status, row.browserId]);
const handleAbort = () => {
abortRunHandler(row.runId, row.name, row.browserId);
}
@@ -185,7 +271,8 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, onToggleExpanded, cu
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
<Collapse in={isOpen} timeout="auto" unmountOnExit>
<RunContent row={row} abortRunHandler={handleAbort} currentLog={currentLog}
logEndRef={logEndRef} interpretationInProgress={runningRecordingName === row.name} />
logEndRef={logEndRef} interpretationInProgress={runningRecordingName === row.name}
workflowProgress={workflowProgress} />
</Collapse>
</TableCell>
</TableRow>