@@ -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 }),
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user