This commit is contained in:
2025-08-31 12:24:50 -04:00
parent 6ec69103dd
commit 66b114f374
8 changed files with 321 additions and 81 deletions

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import {
Modal,
Button,
@ -39,6 +39,40 @@ export const ExecutionModal: React.FC<ExecutionModalProps> = ({
const [execution, setExecution] = useState<FunctionExecution | null>(null);
const [logs, setLogs] = useState<string[]>([]);
const [loadingLogs, setLoadingLogs] = useState(false);
const [autoRefreshLogs, setAutoRefreshLogs] = useState(false);
const pollIntervalRef = useRef<NodeJS.Timeout | null>(null);
const logsPollIntervalRef = useRef<NodeJS.Timeout | null>(null);
const stopLogsAutoRefresh = () => {
if (logsPollIntervalRef.current) {
clearInterval(logsPollIntervalRef.current);
logsPollIntervalRef.current = null;
}
setAutoRefreshLogs(false);
};
// Cleanup intervals on unmount or when modal closes
useEffect(() => {
if (!opened) {
// Stop auto-refresh when modal closes
stopLogsAutoRefresh();
if (pollIntervalRef.current) {
clearTimeout(pollIntervalRef.current);
}
}
}, [opened]);
// Cleanup intervals on unmount
useEffect(() => {
return () => {
if (pollIntervalRef.current) {
clearTimeout(pollIntervalRef.current);
}
if (logsPollIntervalRef.current) {
clearInterval(logsPollIntervalRef.current);
}
};
}, []);
if (!func) return null;
@ -69,8 +103,13 @@ export const ExecutionModal: React.FC<ExecutionModalProps> = ({
setResult(response.data);
if (async) {
// Poll for execution status
// Poll for execution status and start auto-refreshing logs
pollExecution(response.data.execution_id);
} else {
// For synchronous executions, load logs immediately
if (response.data.execution_id) {
loadLogs(response.data.execution_id);
}
}
notifications.show({
@ -91,19 +130,24 @@ export const ExecutionModal: React.FC<ExecutionModalProps> = ({
};
const pollExecution = async (executionId: string) => {
// Start auto-refreshing logs immediately for async executions
startLogsAutoRefresh(executionId);
const poll = async () => {
try {
const response = await executionApi.getById(executionId);
setExecution(response.data);
if (response.data.status === 'running' || response.data.status === 'pending') {
setTimeout(poll, 2000); // Poll every 2 seconds
pollIntervalRef.current = setTimeout(poll, 2000); // Poll every 2 seconds
} else {
// Execution completed, get logs
// Execution completed, stop auto-refresh and load final logs
stopLogsAutoRefresh();
loadLogs(executionId);
}
} catch (error) {
console.error('Error polling execution:', error);
stopLogsAutoRefresh();
}
};
@ -122,6 +166,28 @@ export const ExecutionModal: React.FC<ExecutionModalProps> = ({
}
};
const startLogsAutoRefresh = (executionId: string) => {
// Clear any existing interval
if (logsPollIntervalRef.current) {
clearInterval(logsPollIntervalRef.current);
}
setAutoRefreshLogs(true);
// Load logs immediately
loadLogs(executionId);
// Set up auto-refresh every 2 seconds
logsPollIntervalRef.current = setInterval(async () => {
try {
const response = await executionApi.getLogs(executionId);
setLogs(response.data.logs || []);
} catch (error) {
console.error('Error auto-refreshing logs:', error);
}
}, 2000);
};
const handleCancel = async () => {
if (result && async) {
try {
@ -285,35 +351,60 @@ export const ExecutionModal: React.FC<ExecutionModalProps> = ({
)}
{/* Logs */}
{async && (
<div style={{ marginTop: '1rem' }}>
<Group justify="space-between" mb="xs">
<div style={{ marginTop: '1rem' }}>
<Group justify="space-between" mb="xs">
<Group gap="xs">
<Text size="sm" fw={500}>Logs:</Text>
{autoRefreshLogs && (
<Badge size="xs" color="blue" variant="light">
Auto-refreshing
</Badge>
)}
</Group>
<Group gap="xs">
{result.execution_id && (
<Button
size="xs"
variant={autoRefreshLogs ? "filled" : "light"}
color={autoRefreshLogs ? "red" : "blue"}
leftSection={<IconRefresh size={12} />}
onClick={() => {
if (autoRefreshLogs) {
stopLogsAutoRefresh();
} else {
startLogsAutoRefresh(result.execution_id);
}
}}
>
{autoRefreshLogs ? 'Stop Auto-refresh' : 'Auto-refresh'}
</Button>
)}
<Button
size="xs"
variant="light"
leftSection={<IconRefresh size={12} />}
onClick={() => result.execution_id && loadLogs(result.execution_id)}
loading={loadingLogs}
disabled={autoRefreshLogs}
>
Refresh
Manual Refresh
</Button>
</Group>
<Paper bg="gray.9" p="sm" mah={200} style={{ overflow: 'auto' }}>
{loadingLogs ? (
<Group justify="center">
<Loader size="sm" />
</Group>
) : logs.length > 0 ? (
<Text size="xs" c="white" component="pre">
{logs.join('\n')}
</Text>
) : (
<Text size="xs" c="gray.5">No logs available</Text>
)}
</Paper>
</div>
)}
</Group>
<Paper bg="gray.9" p="sm" mah={200} style={{ overflow: 'auto' }}>
{loadingLogs ? (
<Group justify="center">
<Loader size="sm" />
</Group>
) : (logs.length > 0 || (execution?.logs && execution.logs.length > 0)) ? (
<Text size="xs" c="white" component="pre">
{(execution?.logs || logs).join('\n')}
</Text>
) : (
<Text size="xs" c="gray.5">No logs available</Text>
)}
</Paper>
</div>
</Paper>
</>
)}

View File

@ -35,6 +35,7 @@ export interface FunctionExecution {
error?: string;
duration?: number;
memory_used?: number;
logs?: string[];
container_id?: string;
executor_id: string;
created_at: string;