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 {
id: string
username: string
lead_id?: string
status: DemoStatus
namespace: string
@@ -87,6 +88,20 @@ export interface Contact {
created_at: string
}
export interface DemoState {
APIState: string
WebState: string
DBState: string
DBMemoryState: string
}
export interface DemoDetails {
username: string
namespace: string
state: DemoState
}
// --- Endpoints ---
export const api = {
@@ -130,4 +145,6 @@ export const api = {
sendMessage: (username: string, telegram: string, sujet: string, message: string) =>
request<{ success: string }>('POST', '/send/message', { username, telegram, sujet, message }),
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,
HStack,
Link,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
SimpleGrid,
Spacer,
Spinner,
Stat,
StatLabel,
StatNumber,
Table,
TableContainer,
Tbody,
@@ -19,7 +30,7 @@ import {
useToast,
} from '@chakra-ui/react'
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 { ConfirmDialog } from '../../components/ConfirmDialog'
@@ -34,6 +45,11 @@ export function Demos() {
const [busy, setBusy] = useState<string | 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 () => {
try {
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'
return (
@@ -127,14 +164,24 @@ export function Demos() {
</Thead>
<Tbody>
{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>
<Badge colorScheme={statusColor(d.status)}>{statusLabel(d.status)}</Badge>
</Td>
<Td>
{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}
</Link>
) : (
@@ -148,7 +195,10 @@ export function Demos() {
size="sm"
variant="outline"
isDisabled={!isAlive(d.status) || busy === d.id}
onClick={() => extend(d)}
onClick={(e) => {
e.stopPropagation()
extend(d)
}}
>
+30 j
</Button>
@@ -157,7 +207,10 @@ export function Demos() {
colorScheme="red"
variant="outline"
isDisabled={!isAlive(d.status)}
onClick={() => setToDelete(d)}
onClick={(e) => {
e.stopPropagation()
setToDelete(d)
}}
>
Détruire
</Button>
@@ -185,6 +238,50 @@ export function Demos() {
et toutes ses données seront supprimées définitivement. Les ressources du pool seront
libérées. Cette action est irréversible.
</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>
</>
)
}