This commit is contained in:
2025-09-01 13:10:35 -04:00
parent d4f4747fde
commit aa524d8ac7
30 changed files with 731 additions and 1691 deletions

View File

@ -1,31 +1,16 @@
import React, { useEffect, useState } from 'react';
import {
Table,
Button,
Stack,
Title,
Group,
ActionIcon,
import {
DataTable,
TableColumn,
Badge,
Card,
Group,
Text,
Loader,
Alert,
Tooltip,
Menu,
} from '@mantine/core';
Stack
} from '@skybridge/web-components';
import {
IconPlayerPlay,
IconSettings,
IconTrash,
IconRocket,
IconCode,
IconDots,
IconPlus,
IconRefresh,
IconExclamationCircle,
} from '@tabler/icons-react';
import { notifications } from '@mantine/notifications';
import { functionApi } from '../services/apiService';
import { FunctionDefinition } from '../types';
@ -48,12 +33,10 @@ export const FunctionList: React.FC<FunctionListProps> = ({
try {
setLoading(true);
setError(null);
const response = await functionApi.list();
// Ensure we have a valid array
const functionsArray = response.data?.functions || [];
setFunctions(functionsArray);
} catch (err) {
console.error('Failed to load functions:', err);
const data = await functionApi.listFunctions();
setFunctions(data);
} catch (error) {
console.error('Failed to load functions:', error);
setError('Failed to load functions');
} finally {
setLoading(false);
@ -65,229 +48,78 @@ export const FunctionList: React.FC<FunctionListProps> = ({
}, []);
const handleDelete = async (func: FunctionDefinition) => {
if (!confirm(`Are you sure you want to delete function "${func.name}"?`)) {
return;
}
await functionApi.deleteFunction(func.id);
loadFunctions();
};
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 getStatusColor = (status: string) => {
switch (status) {
case 'active': return 'green';
case 'inactive': return 'gray';
case 'error': return 'red';
case 'building': return 'yellow';
default: return 'blue';
}
};
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 && functions.length === 0) {
return (
<Stack gap="lg">
<Group justify="space-between">
<Title order={2}>Functions</Title>
<Group>
<Button
leftSection={<IconRefresh size={16} />}
onClick={loadFunctions}
loading={loading}
>
Refresh
</Button>
<Button
leftSection={<IconPlus size={16} />}
onClick={onCreateFunction}
>
Create Function
</Button>
</Group>
const columns: TableColumn[] = [
{
key: 'name',
label: 'Function Name',
sortable: true,
render: (value, func: FunctionDefinition) => (
<Group gap="xs">
<IconCode size={16} />
<Text fw={500}>{value}</Text>
</Group>
)
},
{
key: 'runtime',
label: 'Runtime',
render: (value) => (
<Badge variant="light" size="sm">{value}</Badge>
)
},
{
key: 'status',
label: 'Status',
render: (value) => (
<Badge color={getStatusColor(value)} size="sm">{value}</Badge>
)
},
{
key: 'created_at',
label: 'Created',
render: (value) => new Date(value).toLocaleDateString()
},
];
<Stack align="center" justify="center" h={200}>
<Loader size="lg" />
<Text>Loading functions...</Text>
</Stack>
</Stack>
);
}
const customActions = [
{
key: 'execute',
label: 'Execute',
icon: <IconPlayerPlay size={14} />,
onClick: (func: FunctionDefinition) => onExecuteFunction(func),
},
];
return (
<Stack gap="lg">
<Group justify="space-between">
<Title order={2}>Functions</Title>
<Group>
<Button
leftSection={<IconRefresh size={16} />}
onClick={loadFunctions}
loading={loading}
>
Refresh
</Button>
<Button
leftSection={<IconPlus size={16} />}
onClick={onCreateFunction}
>
Create Function
</Button>
</Group>
</Group>
{error && (
<Alert color="red" title="Error">
{error}
<Button variant="light" size="xs" mt="sm" onClick={loadFunctions}>
Retry
</Button>
</Alert>
)}
{functions.length === 0 ? (
<Card shadow="sm" radius="md" withBorder p="xl">
<Stack align="center" gap="md">
<IconCode size={48} color="gray" />
<div style={{ textAlign: 'center' }}>
<Text fw={500} mb="xs">
No functions found
</Text>
<Text size="sm" c="dimmed">
Create your first serverless function to get started
</Text>
</div>
<Button
leftSection={<IconPlus size={16} />}
onClick={onCreateFunction}
>
Create Function
</Button>
</Stack>
</Card>
) : (
<Card shadow="sm" radius="md" withBorder>
<Table>
<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.description || ''}</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.memoryLimit || 'N/A'} MB</Text>
</Table.Td>
<Table.Td>
<Text size="sm">{func.timeout || 'N/A'}</Text>
</Table.Td>
<Table.Td>
<Text size="sm">N/A</Text>
</Table.Td>
<Table.Td>
<Text size="sm">
{func.createdAt ? new Date(func.createdAt).toLocaleDateString() : 'N/A'}
</Text>
</Table.Td>
<Table.Td>
<Group gap="xs">
<Tooltip label="Execute Function">
<ActionIcon
variant="subtle"
color="green"
size="sm"
onClick={() => onExecuteFunction(func)}
>
<IconPlayerPlay size={16} />
</ActionIcon>
</Tooltip>
<Menu position="bottom-end">
<Menu.Target>
<ActionIcon variant="subtle" 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>
</Card>
)}
<Stack gap="md">
<DataTable
data={functions}
columns={columns}
loading={loading}
error={error}
title="Functions"
searchable
onAdd={onCreateFunction}
onEdit={onEditFunction}
onDelete={handleDelete}
onRefresh={loadFunctions}
customActions={customActions}
emptyMessage="No functions found"
/>
</Stack>
);
};
};