chore: update
This commit is contained in:
@@ -9,11 +9,8 @@ import {
|
||||
HStack,
|
||||
Icon,
|
||||
Link,
|
||||
Progress,
|
||||
SimpleGrid,
|
||||
Spacer,
|
||||
Spinner,
|
||||
Stack,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
@@ -25,23 +22,17 @@ import {
|
||||
useToast,
|
||||
} from '@chakra-ui/react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { api, ApiError, type ComponentState, type Demo, type DemoDetails } from '../../lib/api'
|
||||
import { podStatusColor, podStatusLabel, statusColor, statusLabel, timeRemaining } from '../../lib/format'
|
||||
import { api, ApiError, type Demo, type DemoDetails } from '../../lib/api'
|
||||
import { statusColor, statusLabel, timeRemaining } from '../../lib/format'
|
||||
import { ConfirmDialog } from '../../components/ConfirmDialog'
|
||||
import { CreateDemoModal } from '../../components/CreateDemoModal'
|
||||
import { ChevronIcon, PodStatusPanel } from '../../components/PodStatusPanel'
|
||||
|
||||
// Un provisioning en cours => on rafraîchit régulièrement.
|
||||
const POLL_MS = 5000
|
||||
// Rafraîchissement de l'état des pods pendant que le détail d'une ligne est déplié.
|
||||
const DETAILS_POLL_MS = 5000
|
||||
|
||||
const COMPONENTS: { key: keyof DemoDetails['state']; label: string }[] = [
|
||||
{ key: 'api', label: 'Backend' },
|
||||
{ key: 'web', label: 'Frontend' },
|
||||
{ key: 'db', label: 'PostgreSQL' },
|
||||
{ key: 'dbm', label: 'Redis' },
|
||||
]
|
||||
|
||||
export function Demos() {
|
||||
const toast = useToast()
|
||||
const navigate = useNavigate()
|
||||
@@ -59,7 +50,10 @@ export function Demos() {
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
const res = await api.listDemos()
|
||||
setDemos(res.items ?? [])
|
||||
// Les démos passées en abonnement payant vivent désormais dans le
|
||||
// dashboard Premium (n'expirent plus, stockage persistant) — ne plus
|
||||
// les mélanger avec les démos d'essai ici.
|
||||
setDemos((res.items ?? []).filter((d) => d.type_abonnement !== 'premium'))
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError && err.status === 401) navigate('/login')
|
||||
else toast({ status: 'error', title: 'Chargement des démos impossible' })
|
||||
@@ -167,7 +161,7 @@ export function Demos() {
|
||||
{loading ? (
|
||||
<Spinner />
|
||||
) : demos.length === 0 ? (
|
||||
<Text color="gray.500">Aucune démo active. Lancez-en une depuis un lead ou ci-dessus.</Text>
|
||||
<Text color="gray.500">Aucune démo active. Lancez-en une avec le bouton ci-dessus.</Text>
|
||||
) : (
|
||||
<TableContainer borderWidth="1px" borderRadius="lg">
|
||||
<Table>
|
||||
@@ -302,93 +296,3 @@ export function Demos() {
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// PodStatusPanel : vue d'ensemble des 4 composants d'une démo (statut + CPU/mémoire live).
|
||||
function PodStatusPanel({ state }: { state: DemoDetails['state'] }) {
|
||||
const allRunning = COMPONENTS.every((c) => state[c.key].phase === 'Running')
|
||||
const downCount = COMPONENTS.filter((c) => state[c.key].phase !== 'Running').length
|
||||
|
||||
return (
|
||||
<Stack spacing={3}>
|
||||
<HStack spacing={2}>
|
||||
<Box
|
||||
w="8px"
|
||||
h="8px"
|
||||
borderRadius="full"
|
||||
bg={allRunning ? 'green.400' : 'red.400'}
|
||||
flexShrink={0}
|
||||
/>
|
||||
<Text fontSize="sm" fontWeight="medium">
|
||||
{allRunning
|
||||
? 'Tous les services sont opérationnels'
|
||||
: `${downCount} service${downCount > 1 ? 's' : ''} indisponible${downCount > 1 ? 's' : ''}`}
|
||||
</Text>
|
||||
</HStack>
|
||||
<SimpleGrid columns={{ base: 1, sm: 2, lg: 4 }} spacing={3}>
|
||||
{COMPONENTS.map((c) => (
|
||||
<ComponentCard key={c.key} title={c.label} cs={state[c.key]} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
function ComponentCard({ title, cs }: { title: string; cs: ComponentState }) {
|
||||
const cpuPct = cs.cpu_limit_milli > 0 ? Math.min(100, Math.round((cs.cpu_milli / cs.cpu_limit_milli) * 100)) : 0
|
||||
const memPct = cs.memory_limit_mi > 0 ? Math.min(100, Math.round((cs.memory_mi / cs.memory_limit_mi) * 100)) : 0
|
||||
|
||||
return (
|
||||
<Box p={3} borderWidth="1px" borderRadius="lg" bg="bg-surface">
|
||||
<HStack justify="space-between" mb={3}>
|
||||
<Text fontSize="sm" fontWeight="semibold">
|
||||
{title}
|
||||
</Text>
|
||||
<HStack spacing={1.5}>
|
||||
<Box w="7px" h="7px" borderRadius="full" bg={`${podStatusColor(cs.phase)}.400`} flexShrink={0} />
|
||||
<Badge colorScheme={podStatusColor(cs.phase)} fontSize="10px">
|
||||
{podStatusLabel(cs.phase)}
|
||||
</Badge>
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
<Stack spacing={2}>
|
||||
<Box>
|
||||
<Flex justify="space-between" fontSize="xs" color="gray.500" mb={1}>
|
||||
<Text>CPU</Text>
|
||||
<Text fontFamily="mono">
|
||||
{cs.cpu_milli}m / {cs.cpu_limit_milli}m
|
||||
</Text>
|
||||
</Flex>
|
||||
<Progress
|
||||
value={cpuPct}
|
||||
size="xs"
|
||||
borderRadius="full"
|
||||
colorScheme={cpuPct > 85 ? 'red' : cpuPct > 60 ? 'orange' : 'primary'}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex justify="space-between" fontSize="xs" color="gray.500" mb={1}>
|
||||
<Text>Mémoire</Text>
|
||||
<Text fontFamily="mono">
|
||||
{cs.memory_mi}Mi / {cs.memory_limit_mi}Mi
|
||||
</Text>
|
||||
</Flex>
|
||||
<Progress
|
||||
value={memPct}
|
||||
size="xs"
|
||||
borderRadius="full"
|
||||
colorScheme={memPct > 85 ? 'red' : memPct > 60 ? 'orange' : 'primary'}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function ChevronIcon(props: React.ComponentProps<typeof Icon>) {
|
||||
return (
|
||||
<Icon viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={3} {...props}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 18l6-6-6-6" />
|
||||
</Icon>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user