feat: add details demo
ci-web / test (push) Has been cancelled

This commit is contained in:
Nuxgrid
2026-07-28 14:19:57 +02:00
parent 356604fba4
commit 65ca24b6eb
2 changed files with 119 additions and 5 deletions
+17
View File
@@ -63,6 +63,7 @@ export type DemoStatus =
export interface Demo { export interface Demo {
id: string id: string
username: string
lead_id?: string lead_id?: string
status: DemoStatus status: DemoStatus
namespace: string namespace: string
@@ -87,6 +88,20 @@ export interface Contact {
created_at: string created_at: string
} }
export interface DemoState {
APIState: string
WebState: string
DBState: string
DBMemoryState: string
}
export interface DemoDetails {
username: string
namespace: string
state: DemoState
}
// --- Endpoints --- // --- Endpoints ---
export const api = { export const api = {
@@ -130,4 +145,6 @@ export const api = {
sendMessage: (username: string, telegram: string, sujet: string, message: string) => sendMessage: (username: string, telegram: string, sujet: string, message: string) =>
request<{ success: string }>('POST', '/send/message', { username, telegram, sujet, message }), request<{ success: string }>('POST', '/send/message', { username, telegram, sujet, message }),
getMessage: () => request<{ messages: Contact[] }>('GET', '/messages'), getMessage: () => request<{ messages: Contact[] }>('GET', '/messages'),
getDemoDetails: (username: string) =>
request<DemoDetails>('POST', '/demos/details', { username }),
} }
+102 -5
View File
@@ -6,8 +6,19 @@ import {
Heading, Heading,
HStack, HStack,
Link, Link,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
SimpleGrid,
Spacer, Spacer,
Spinner, Spinner,
Stat,
StatLabel,
StatNumber,
Table, Table,
TableContainer, TableContainer,
Tbody, Tbody,
@@ -19,7 +30,7 @@ import {
useToast, useToast,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { api, ApiError, type Demo } from '../../lib/api' import { api, ApiError, type Demo, type DemoDetails } from '../../lib/api'
import { statusColor, statusLabel, timeRemaining } from '../../lib/format' import { statusColor, statusLabel, timeRemaining } from '../../lib/format'
import { ConfirmDialog } from '../../components/ConfirmDialog' import { ConfirmDialog } from '../../components/ConfirmDialog'
@@ -34,6 +45,11 @@ export function Demos() {
const [busy, setBusy] = useState<string | null>(null) const [busy, setBusy] = useState<string | null>(null)
const [toDelete, setToDelete] = useState<Demo | null>(null) const [toDelete, setToDelete] = useState<Demo | null>(null)
// --- Détails d'une démo (modale) ---
const [detailsFor, setDetailsFor] = useState<Demo | null>(null)
const [details, setDetails] = useState<DemoDetails | null>(null)
const [detailsLoading, setDetailsLoading] = useState(false)
const load = useCallback(async () => { const load = useCallback(async () => {
try { try {
const res = await api.listDemos() const res = await api.listDemos()
@@ -97,6 +113,27 @@ export function Demos() {
} }
} }
const openDetails = async (d: Demo) => {
setDetailsFor(d)
setDetails(null)
setDetailsLoading(true)
try {
const res = await api.getDemoDetails(d.username)
setDetails(res)
} catch (err) {
const msg = err instanceof ApiError ? err.message : 'Erreur'
toast({ status: 'error', title: 'Détails indisponibles', description: msg })
setDetailsFor(null)
} finally {
setDetailsLoading(false)
}
}
const closeDetails = () => {
setDetailsFor(null)
setDetails(null)
}
const isAlive = (s: Demo['status']) => s !== 'expired' && s !== 'failed' const isAlive = (s: Demo['status']) => s !== 'expired' && s !== 'failed'
return ( return (
@@ -127,14 +164,24 @@ export function Demos() {
</Thead> </Thead>
<Tbody> <Tbody>
{demos.map((d) => ( {demos.map((d) => (
<Tr key={d.id}> <Tr
key={d.id}
cursor="pointer"
_hover={{ bg: 'gray.50' }}
onClick={() => openDetails(d)}
>
<Td fontFamily="mono">{d.namespace}</Td> <Td fontFamily="mono">{d.namespace}</Td>
<Td> <Td>
<Badge colorScheme={statusColor(d.status)}>{statusLabel(d.status)}</Badge> <Badge colorScheme={statusColor(d.status)}>{statusLabel(d.status)}</Badge>
</Td> </Td>
<Td> <Td>
{d.status === 'ready' ? ( {d.status === 'ready' ? (
<Link href={d.url} color="primary.500" isExternal> <Link
href={d.url}
color="primary.500"
isExternal
onClick={(e) => e.stopPropagation()}
>
{d.url} {d.url}
</Link> </Link>
) : ( ) : (
@@ -148,7 +195,10 @@ export function Demos() {
size="sm" size="sm"
variant="outline" variant="outline"
isDisabled={!isAlive(d.status) || busy === d.id} isDisabled={!isAlive(d.status) || busy === d.id}
onClick={() => extend(d)} onClick={(e) => {
e.stopPropagation()
extend(d)
}}
> >
+30 j +30 j
</Button> </Button>
@@ -157,7 +207,10 @@ export function Demos() {
colorScheme="red" colorScheme="red"
variant="outline" variant="outline"
isDisabled={!isAlive(d.status)} isDisabled={!isAlive(d.status)}
onClick={() => setToDelete(d)} onClick={(e) => {
e.stopPropagation()
setToDelete(d)
}}
> >
Détruire Détruire
</Button> </Button>
@@ -185,6 +238,50 @@ export function Demos() {
et toutes ses données seront supprimées définitivement. Les ressources du pool seront et toutes ses données seront supprimées définitivement. Les ressources du pool seront
libérées. Cette action est irréversible. libérées. Cette action est irréversible.
</ConfirmDialog> </ConfirmDialog>
<Modal isOpen={!!detailsFor} onClose={closeDetails} size="lg">
<ModalOverlay />
<ModalContent>
<ModalHeader>
Détails de{' '}
<Text as="span" fontFamily="mono">
{detailsFor?.namespace}
</Text>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{detailsLoading ? (
<Flex justify="center" py={8}>
<Spinner />
</Flex>
) : details ? (
<SimpleGrid columns={2} spacing={4}>
<Stat>
<StatLabel>Backend</StatLabel>
<StatNumber fontSize="md">{details.state.APIState || '—'}</StatNumber>
</Stat>
<Stat>
<StatLabel>Frontend</StatLabel>
<StatNumber fontSize="md">{details.state.WebState || '—'}</StatNumber>
</Stat>
<Stat>
<StatLabel>PostgreSQL</StatLabel>
<StatNumber fontSize="md">{details.state.DBState || '—'}</StatNumber>
</Stat>
<Stat>
<StatLabel>Redis</StatLabel>
<StatNumber fontSize="md">{details.state.DBMemoryState || '—'}</StatNumber>
</Stat>
</SimpleGrid>
) : (
<Text color="gray.500">Aucune donnée.</Text>
)}
</ModalBody>
<ModalFooter>
<Button onClick={closeDetails}>Fermer</Button>
</ModalFooter>
</ModalContent>
</Modal>
</> </>
) )
} }