-
This commit is contained in:
@ -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>
|
||||
);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user