This commit is contained in:
2025-08-31 01:06:02 -04:00
parent d8f1fb3753
commit 7a7ad1e44d
3 changed files with 320 additions and 278 deletions

View File

@ -1,21 +1,21 @@
import React, { useState, useEffect } from 'react';
import {
Card,
Group,
Text,
Badge,
Stack,
Table,
Button,
Stack,
Title,
Modal,
Select,
TextInput,
Pagination,
Container,
Alert,
Loader,
Code,
Group,
ActionIcon,
Modal,
Badge,
Card,
Text,
Loader,
Alert,
Code,
ScrollArea,
Flex,
} from '@mantine/core';
@ -183,19 +183,39 @@ const ExecutionList: React.FC = () => {
if (loading && executions.length === 0) {
return (
<Container size="xl">
<Flex justify="center" align="center" h={200}>
<Stack gap="lg">
<Group justify="space-between">
<Title order={2}>Function Executions</Title>
<Button
leftSection={<IconRefresh size={16} />}
onClick={handleRefresh}
loading={loading}
>
Refresh
</Button>
</Group>
<Stack align="center" justify="center" h={200}>
<Loader size="lg" />
</Flex>
</Container>
<Text>Loading executions...</Text>
</Stack>
</Stack>
);
}
return (
<Container size="xl">
<Stack gap="md">
<Card>
<Group justify="space-between" mb="md">
<Stack gap="lg">
<Group justify="space-between">
<Title order={2}>Function Executions</Title>
<Button
leftSection={<IconRefresh size={16} />}
onClick={handleRefresh}
loading={loading}
>
Refresh
</Button>
</Group>
<Group>
<TextInput
placeholder="Search executions..."
@ -216,24 +236,30 @@ const ExecutionList: React.FC = () => {
style={{ width: 200 }}
/>
</Group>
<Button leftSection={<IconRefresh size={16} />} onClick={handleRefresh} loading={loading}>
Refresh
</Button>
</Group>
{error && (
<Alert color="red" mb="md">
<Alert color="red" title="Error">
{error}
</Alert>
)}
{filteredExecutions.length === 0 ? (
<Text c="dimmed" ta="center" py="xl">
<Card shadow="sm" radius="md" withBorder p="xl">
<Stack align="center" gap="md">
<IconClock size={48} color="gray" />
<div style={{ textAlign: 'center' }}>
<Text fw={500} mb="xs">
No executions found
</Text>
<Text size="sm" c="dimmed">
There are no function executions matching your current filters
</Text>
</div>
</Stack>
</Card>
) : (
<ScrollArea>
<Table striped highlightOnHover>
<Card shadow="sm" radius="md" withBorder>
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>Function</Table.Th>
@ -250,11 +276,11 @@ const ExecutionList: React.FC = () => {
<Table.Td>
<Stack gap={2}>
<Text fw={500}>{getFunctionName(execution.function_id)}</Text>
<Code size="xs">{execution.id.slice(0, 8)}...</Code>
<Code style={{ fontSize: '12px' }}>{execution.id.slice(0, 8)}...</Code>
</Stack>
</Table.Td>
<Table.Td>
<Badge color={getStatusColor(execution.status)} variant="filled">
<Badge color={getStatusColor(execution.status)} variant="light">
{execution.status}
</Badge>
</Table.Td>
@ -300,11 +326,11 @@ const ExecutionList: React.FC = () => {
))}
</Table.Tbody>
</Table>
</ScrollArea>
</Card>
)}
{totalPages > 1 && (
<Group justify="center" mt="md">
<Group justify="center">
<Pagination
value={page}
onChange={setPage}
@ -313,8 +339,6 @@ const ExecutionList: React.FC = () => {
/>
</Group>
)}
</Card>
</Stack>
{/* Logs Modal */}
<Modal
@ -378,7 +402,7 @@ const ExecutionList: React.FC = () => {
</div>
</Stack>
</Modal>
</Container>
</Stack>
);
};

View File

@ -1,18 +1,18 @@
import React, { useEffect, useState } from 'react';
import {
Table,
Badge,
Button,
Group,
Text,
ActionIcon,
Menu,
Paper,
Stack,
Title,
Alert,
Group,
ActionIcon,
Badge,
Card,
Text,
Loader,
Center,
Alert,
Tooltip,
Menu,
} from '@mantine/core';
import {
IconPlayerPlay,
@ -114,40 +114,21 @@ export const FunctionList: React.FC<FunctionListProps> = ({
}
};
if (loading) {
if (loading && functions.length === 0) {
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">
<Stack gap="lg">
<Group justify="space-between">
<Title order={2}>Functions</Title>
<Group>
<Button
leftSection={<IconRefresh size={14} />}
variant="light"
leftSection={<IconRefresh size={16} />}
onClick={loadFunctions}
loading={loading}
>
Refresh
</Button>
<Button
leftSection={<IconPlus size={14} />}
leftSection={<IconPlus size={16} />}
onClick={onCreateFunction}
>
Create Function
@ -155,26 +136,67 @@ export const FunctionList: React.FC<FunctionListProps> = ({
</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>
<Stack align="center" justify="center" h={200}>
<Loader size="lg" />
<Text>Loading functions...</Text>
</Stack>
</Stack>
);
}
return (
<Stack gap="lg">
<Group justify="space-between">
<Title order={2}>Functions</Title>
<Group>
<Button
leftSection={<IconPlus size={14} />}
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>
</Center>
<Button
leftSection={<IconPlus size={16} />}
onClick={onCreateFunction}
>
Create Function
</Button>
</Stack>
</Card>
) : (
<Table striped>
<Card shadow="sm" radius="md" withBorder>
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>Name</Table.Th>
@ -192,7 +214,7 @@ export const FunctionList: React.FC<FunctionListProps> = ({
<Table.Tr key={func.id}>
<Table.Td>
<Text fw={500}>{func.name}</Text>
<Text size="xs" c="dimmed">{func.handler}</Text>
<Text size="xs" c="dimmed">{func.description || ''}</Text>
</Table.Td>
<Table.Td>
<Badge color={getRuntimeColor(func.runtime)} variant="light">
@ -201,33 +223,28 @@ export const FunctionList: React.FC<FunctionListProps> = ({
</Table.Td>
<Table.Td>
<Text size="sm" c="dimmed">
{func.image}
{func.image || ''}
</Text>
</Table.Td>
<Table.Td>
<Text size="sm">{func.memory} MB</Text>
<Text size="sm">{func.memoryLimit || 'N/A'} MB</Text>
</Table.Td>
<Table.Td>
<Text size="sm">{func.timeout}</Text>
<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.owner?.name || 'Unknown'}
{func.owner?.type && (
<Text size="xs" c="dimmed">({func.owner.type})</Text>
)}
</Text>
</Table.Td>
<Table.Td>
<Text size="sm">
{func.created_at ? new Date(func.created_at).toLocaleDateString() : 'N/A'}
{func.createdAt ? new Date(func.createdAt).toLocaleDateString() : 'N/A'}
</Text>
</Table.Td>
<Table.Td>
<Group gap="xs">
<Tooltip label="Execute Function">
<ActionIcon
variant="light"
variant="subtle"
color="green"
size="sm"
onClick={() => onExecuteFunction(func)}
@ -237,7 +254,7 @@ export const FunctionList: React.FC<FunctionListProps> = ({
</Tooltip>
<Menu position="bottom-end">
<Menu.Target>
<ActionIcon variant="light" size="sm">
<ActionIcon variant="subtle" size="sm">
<IconDots size={16} />
</ActionIcon>
</Menu.Target>
@ -269,7 +286,8 @@ export const FunctionList: React.FC<FunctionListProps> = ({
))}
</Table.Tbody>
</Table>
</Card>
)}
</Paper>
</Stack>
);
};

View File

@ -75,16 +75,16 @@ services:
- ./migrations:/app/migrations:ro,Z
restart: unless-stopped
frontend:
build:
context: ./kms-frontend
dockerfile: Dockerfile
container_name: kms-frontend
ports:
- "3000:80"
networks:
- kms-network
restart: unless-stopped
# frontend:
# build:
# context: ./kms-frontend
# dockerfile: Dockerfile
# container_name: kms-frontend
# ports:
# - "3000:80"
# networks:
# - kms-network
# restart: unless-stopped
volumes:
postgres_data: