This commit is contained in:
2025-08-30 21:17:23 -04:00
parent f72c05bfd8
commit 2778cbc512
46 changed files with 11717 additions and 0 deletions

View File

@ -0,0 +1,271 @@
import React, { useEffect, useState } from 'react';
import {
Table,
Badge,
Button,
Group,
Text,
ActionIcon,
Menu,
Paper,
Title,
Alert,
Loader,
Center,
Tooltip,
} from '@mantine/core';
import {
IconPlay,
IconSettings,
IconTrash,
IconRocket,
IconCode,
IconDots,
IconPlus,
IconRefresh,
IconExclamationCircle,
} from '@tabler/icons-react';
import { notifications } from '@mantine/notifications';
import { functionApi } from '../services/api';
import { FunctionDefinition } from '../types';
interface FunctionListProps {
onCreateFunction: () => void;
onEditFunction: (func: FunctionDefinition) => void;
onExecuteFunction: (func: FunctionDefinition) => void;
}
export const FunctionList: React.FC<FunctionListProps> = ({
onCreateFunction,
onEditFunction,
onExecuteFunction,
}) => {
const [functions, setFunctions] = useState<FunctionDefinition[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadFunctions = async () => {
try {
setLoading(true);
setError(null);
const response = await functionApi.list();
setFunctions(response.data.functions || []);
} catch (err) {
console.error('Failed to load functions:', err);
setError('Failed to load functions');
} finally {
setLoading(false);
}
};
useEffect(() => {
loadFunctions();
}, []);
const handleDelete = async (func: FunctionDefinition) => {
if (!confirm(`Are you sure you want to delete function "${func.name}"?`)) {
return;
}
try {
await functionApi.delete(func.id);
notifications.show({
title: 'Success',
message: `Function "${func.name}" deleted successfully`,
color: 'green',
});
loadFunctions();
} catch (err) {
console.error('Failed to delete function:', err);
notifications.show({
title: 'Error',
message: `Failed to delete function "${func.name}"`,
color: 'red',
});
}
};
const handleDeploy = async (func: FunctionDefinition) => {
try {
await functionApi.deploy(func.id);
notifications.show({
title: 'Success',
message: `Function "${func.name}" deployed successfully`,
color: 'green',
});
} catch (err) {
console.error('Failed to deploy function:', err);
notifications.show({
title: 'Error',
message: `Failed to deploy function "${func.name}"`,
color: 'red',
});
}
};
const getRuntimeColor = (runtime: string) => {
switch (runtime) {
case 'nodejs18': return 'green';
case 'python3.9': return 'blue';
case 'go1.20': return 'cyan';
default: return 'gray';
}
};
if (loading) {
return (
<Center py={60}>
<Loader size="lg" />
</Center>
);
}
if (error) {
return (
<Alert icon={<IconExclamationCircle size={16} />} title="Error" color="red" mb="md">
{error}
<Button variant="light" size="xs" mt="sm" onClick={loadFunctions}>
Retry
</Button>
</Alert>
);
}
return (
<Paper shadow="xs" p="md">
<Group justify="space-between" mb="md">
<Title order={2}>Functions</Title>
<Group>
<Button
leftSection={<IconRefresh size={14} />}
variant="light"
onClick={loadFunctions}
loading={loading}
>
Refresh
</Button>
<Button
leftSection={<IconPlus size={14} />}
onClick={onCreateFunction}
>
Create Function
</Button>
</Group>
</Group>
{functions.length === 0 ? (
<Center py={40}>
<div style={{ textAlign: 'center' }}>
<IconCode size={48} color="gray" />
<Text size="lg" mt="md" c="dimmed">
No functions found
</Text>
<Text size="sm" c="dimmed" mb="md">
Create your first serverless function to get started
</Text>
<Button
leftSection={<IconPlus size={14} />}
onClick={onCreateFunction}
>
Create Function
</Button>
</div>
</Center>
) : (
<Table striped>
<Table.Thead>
<Table.Tr>
<Table.Th>Name</Table.Th>
<Table.Th>Runtime</Table.Th>
<Table.Th>Image</Table.Th>
<Table.Th>Memory</Table.Th>
<Table.Th>Timeout</Table.Th>
<Table.Th>Owner</Table.Th>
<Table.Th>Created</Table.Th>
<Table.Th>Actions</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{functions.map((func) => (
<Table.Tr key={func.id}>
<Table.Td>
<Text fw={500}>{func.name}</Text>
<Text size="xs" c="dimmed">{func.handler}</Text>
</Table.Td>
<Table.Td>
<Badge color={getRuntimeColor(func.runtime)} variant="light">
{func.runtime}
</Badge>
</Table.Td>
<Table.Td>
<Text size="sm" c="dimmed">
{func.image}
</Text>
</Table.Td>
<Table.Td>
<Text size="sm">{func.memory} MB</Text>
</Table.Td>
<Table.Td>
<Text size="sm">{func.timeout}</Text>
</Table.Td>
<Table.Td>
<Text size="sm">
{func.owner.name}
<Text size="xs" c="dimmed">({func.owner.type})</Text>
</Text>
</Table.Td>
<Table.Td>
<Text size="sm">
{new Date(func.created_at).toLocaleDateString()}
</Text>
</Table.Td>
<Table.Td>
<Group gap="xs">
<Tooltip label="Execute Function">
<ActionIcon
variant="light"
color="green"
size="sm"
onClick={() => onExecuteFunction(func)}
>
<IconPlay size={16} />
</ActionIcon>
</Tooltip>
<Menu position="bottom-end">
<Menu.Target>
<ActionIcon variant="light" size="sm">
<IconDots size={16} />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item
leftSection={<IconSettings size={16} />}
onClick={() => onEditFunction(func)}
>
Edit
</Menu.Item>
<Menu.Item
leftSection={<IconRocket size={16} />}
onClick={() => handleDeploy(func)}
>
Deploy
</Menu.Item>
<Menu.Item
leftSection={<IconTrash size={16} />}
color="red"
onClick={() => handleDelete(func)}
>
Delete
</Menu.Item>
</Menu.Dropdown>
</Menu>
</Group>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
)}
</Paper>
);
};