chore: update
ci-web / test (push) Successful in 14m40s

This commit is contained in:
Xor290
2026-08-03 11:23:42 +02:00
parent 2592ea1c6f
commit e2802aeb59
5 changed files with 440 additions and 241 deletions
+146 -120
View File
@@ -12,6 +12,7 @@ import {
Link,
Spacer,
Spinner,
Stack,
Table,
TableContainer,
Tbody,
@@ -29,6 +30,7 @@ import { statusColor, statusLabel, timeRemaining } from '../../lib/format'
import { ConfirmDialog } from '../../components/ConfirmDialog'
import { CreateDemoModal } from '../../components/CreateDemoModal'
import { EditDomainModal } from '../../components/EditDomainModal'
import { DemoCard } from '../../components/DemoCard'
import { ChevronIcon, PodStatusPanel } from '../../components/PodStatusPanel'
// Un provisioning en cours => on rafraîchit régulièrement.
@@ -144,6 +146,34 @@ export function Demos() {
const isAlive = (s: Demo['status']) => s !== 'expired' && s !== 'failed'
const renderActions = (d: Demo) => (
<>
<Button
size="sm"
variant="outline"
isDisabled={!isAlive(d.status) || busy === d.id}
onClick={(e) => {
e.stopPropagation()
extend(d)
}}
>
+30 j
</Button>
<Button
size="sm"
colorScheme="red"
variant="outline"
isDisabled={!isAlive(d.status)}
onClick={(e) => {
e.stopPropagation()
setToDelete(d)
}}
>
Détruire
</Button>
</>
)
return (
<>
<Flex mb={6} align="center" gap={4} wrap="wrap">
@@ -167,131 +197,127 @@ export function Demos() {
) : demos.length === 0 ? (
<Text color="gray.500">Aucune démo active. Lancez-en une avec le bouton ci-dessus.</Text>
) : (
<TableContainer borderWidth="1px" borderRadius="lg">
<Table>
<Thead>
<Tr>
<Th>Namespace</Th>
<Th>Client</Th>
<Th>Statut</Th>
<Th>URL</Th>
<Th>Expire dans</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{demos.map((d) => {
const isOpen = expandedId === d.id
return (
<Fragment key={d.id}>
<Tr
cursor="pointer"
bg={isOpen ? 'chakra-subtle-bg' : undefined}
_hover={{ bg: 'chakra-subtle-bg' }}
onClick={() => toggleRow(d)}
>
<Td fontFamily="mono">
<HStack spacing={2}>
<Icon
as={ChevronIcon}
boxSize={3}
color="gray.400"
transform={isOpen ? 'rotate(90deg)' : undefined}
transition="transform 0.15s"
/>
<Text>{d.namespace}</Text>
</HStack>
</Td>
<Td>
{d.username ? (
<Text>{d.username}</Text>
) : (
<Text color="gray.400"></Text>
)}
</Td>
<Td>
<Badge colorScheme={statusColor(d.status)}>{statusLabel(d.status)}</Badge>
</Td>
<Td>
<HStack spacing={1}>
{d.status === 'ready' ? (
<Link
href={d.url}
color="primary.500"
isExternal
onClick={(e) => e.stopPropagation()}
>
{d.url}
</Link>
<>
{/* Desktop/tablette : tableau. */}
<TableContainer borderWidth="1px" borderRadius="lg" display={{ base: 'none', md: 'block' }}>
<Table>
<Thead>
<Tr>
<Th>Namespace</Th>
<Th>Client</Th>
<Th>Statut</Th>
<Th>URL</Th>
<Th>Expire dans</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{demos.map((d) => {
const isOpen = expandedId === d.id
return (
<Fragment key={d.id}>
<Tr
cursor="pointer"
bg={isOpen ? 'chakra-subtle-bg' : undefined}
_hover={{ bg: 'chakra-subtle-bg' }}
onClick={() => toggleRow(d)}
>
<Td fontFamily="mono">
<HStack spacing={2}>
<Icon
as={ChevronIcon}
boxSize={3}
color="gray.400"
transform={isOpen ? 'rotate(90deg)' : undefined}
transition="transform 0.15s"
/>
<Text>{d.namespace}</Text>
</HStack>
</Td>
<Td>
{d.username ? (
<Text>{d.username}</Text>
) : (
<Text color="gray.400"></Text>
)}
<IconButton
aria-label="Modifier le domaine"
icon={<EditIcon />}
size="xs"
variant="ghost"
onClick={(e) => {
e.stopPropagation()
setEditingDomain(d)
}}
/>
</HStack>
</Td>
<Td>{isAlive(d.status) ? timeRemaining(d.expires_at) : '—'}</Td>
<Td textAlign="right">
<HStack justify="flex-end">
<Button
size="sm"
variant="outline"
isDisabled={!isAlive(d.status) || busy === d.id}
onClick={(e) => {
e.stopPropagation()
extend(d)
}}
>
+30 j
</Button>
<Button
size="sm"
colorScheme="red"
variant="outline"
isDisabled={!isAlive(d.status)}
onClick={(e) => {
e.stopPropagation()
setToDelete(d)
}}
>
Détruire
</Button>
</HStack>
</Td>
</Tr>
<Tr>
<Td p={0} border={isOpen ? undefined : 'none'} colSpan={6}>
<Collapse in={isOpen} unmountOnExit animateOpacity>
<Box p={4} bg="chakra-subtle-bg" borderTopWidth="1px">
{detailsLoading && !details ? (
<Flex justify="center" py={4}>
<Spinner size="sm" />
</Flex>
) : details ? (
<PodStatusPanel state={details.state} />
</Td>
<Td>
<Badge colorScheme={statusColor(d.status)}>{statusLabel(d.status)}</Badge>
</Td>
<Td>
<HStack spacing={1}>
{d.status === 'ready' ? (
<Link
href={d.url}
color="primary.500"
isExternal
onClick={(e) => e.stopPropagation()}
>
{d.url}
</Link>
) : (
<Text color="gray.500" fontSize="sm">
Aucune donnée.
</Text>
<Text color="gray.400"></Text>
)}
</Box>
</Collapse>
</Td>
</Tr>
</Fragment>
)
})}
</Tbody>
</Table>
</TableContainer>
<IconButton
aria-label="Modifier le domaine"
icon={<EditIcon />}
size="xs"
variant="ghost"
onClick={(e) => {
e.stopPropagation()
setEditingDomain(d)
}}
/>
</HStack>
</Td>
<Td>{isAlive(d.status) ? timeRemaining(d.expires_at) : '—'}</Td>
<Td textAlign="right">
<HStack justify="flex-end">{renderActions(d)}</HStack>
</Td>
</Tr>
<Tr>
<Td p={0} border={isOpen ? undefined : 'none'} colSpan={6}>
<Collapse in={isOpen} unmountOnExit animateOpacity>
<Box p={4} bg="chakra-subtle-bg" borderTopWidth="1px">
{detailsLoading && !details ? (
<Flex justify="center" py={4}>
<Spinner size="sm" />
</Flex>
) : details ? (
<PodStatusPanel state={details.state} />
) : (
<Text color="gray.500" fontSize="sm">
Aucune donnée.
</Text>
)}
</Box>
</Collapse>
</Td>
</Tr>
</Fragment>
)
})}
</Tbody>
</Table>
</TableContainer>
{/* Mobile : cartes (pas de scroll horizontal). */}
<Stack spacing={3} display={{ base: 'flex', md: 'none' }}>
{demos.map((d) => (
<DemoCard
key={d.id}
demo={d}
isOpen={expandedId === d.id}
onToggle={() => toggleRow(d)}
detailsLoading={detailsLoading}
details={expandedId === d.id ? details : null}
onEditDomain={() => setEditingDomain(d)}
expiresLabel={isAlive(d.status) ? timeRemaining(d.expires_at) : '—'}
actions={renderActions(d)}
/>
))}
</Stack>
</>
)}
<ConfirmDialog