chore: update
ci-api / test (push) Successful in 21m59s
ci-web / test (push) Successful in 13m44s

This commit is contained in:
Xor290
2026-08-02 21:31:29 +02:00
parent ca14327ea2
commit 2592ea1c6f
15 changed files with 338 additions and 1629 deletions
-1510
View File
File diff suppressed because one or more lines are too long
-13
View File
@@ -1,13 +0,0 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Omnex — Plateforme de gestion de commandes & livraison</title>
<meta name="description" content="Déployez en un clic une démo complète de la plateforme de gestion de commandes et de livraison." />
<script type="module" crossorigin src="/assets/index-BZ4jKugg.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
+92
View File
@@ -0,0 +1,92 @@
import { useEffect, useState } from 'react'
import {
Button,
FormControl,
FormHelperText,
FormLabel,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
useToast,
} from '@chakra-ui/react'
import { api, ApiError, type Demo } from '../lib/api'
interface EditDomainModalProps {
demo: Demo | null
onClose: () => void
onSaved: () => void
}
// Modal d'édition du domaine public d'une démo — partagé entre le dashboard
// Demos et le dashboard Premium.
export function EditDomainModal({ demo, onClose, onSaved }: EditDomainModalProps) {
const toast = useToast()
const [domain, setDomain] = useState('')
const [saving, setSaving] = useState(false)
useEffect(() => {
setDomain(demo?.custom_domain ?? '')
}, [demo])
const handleClose = () => {
if (saving) return
onClose()
}
const handleSave = async () => {
if (!demo) return
setSaving(true)
try {
await api.setDemoDomain(demo.id, domain.trim())
toast({ status: 'success', title: 'Domaine mis à jour' })
onSaved()
onClose()
} catch (err) {
const msg = err instanceof ApiError ? err.message : 'Erreur'
toast({ status: 'error', title: 'Mise à jour impossible', description: msg })
} finally {
setSaving(false)
}
}
return (
<Modal isOpen={!!demo} onClose={handleClose} closeOnOverlayClick={!saving}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Domaine de la plateforme</ModalHeader>
<ModalCloseButton isDisabled={saving} />
<ModalBody>
<FormControl>
<FormLabel fontSize="sm">Domaine personnalisé</FormLabel>
<Input
placeholder="boutique.mon-domaine.com"
value={domain}
onChange={(e) => setDomain(e.target.value)}
isDisabled={saving}
fontFamily="mono"
/>
<FormHelperText>
Laissez vide pour revenir au domaine par défaut ({demo?.namespace}.&lt;domaine
omnex&gt;). Le DNS du domaine choisi doit pointer vers le cluster, et un certificat
TLS valide doit le couvrir le certificat wildcard partagé ne couvre que le domaine
par défaut.
</FormHelperText>
</FormControl>
</ModalBody>
<ModalFooter>
<Button variant="ghost" mr={3} onClick={handleClose} isDisabled={saving}>
Annuler
</Button>
<Button colorScheme="primary" onClick={handleSave} isLoading={saving}>
Enregistrer
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}
+3
View File
@@ -58,6 +58,7 @@ export interface Demo {
status: DemoStatus
namespace: string
url: string
custom_domain?: string
type_abonnement?: string
created_at: string
expires_at: string
@@ -186,6 +187,8 @@ export const api = {
}),
extendDemo: (id: string) => request<Demo>('POST', `/demos/${id}/extend`),
deleteDemo: (id: string) => request<Demo>('DELETE', `/demos/${id}`),
setDemoDomain: (id: string, domain: string) =>
request<Demo>('POST', `/demos/${id}/domain`, { domain }),
listCodes: () => request<{ items: CodeBuySub[] }>('GET', '/codes'),
createCode: (username: string) =>
+34 -12
View File
@@ -8,6 +8,7 @@ import {
Heading,
HStack,
Icon,
IconButton,
Link,
Spacer,
Spinner,
@@ -21,11 +22,13 @@ import {
Tr,
useToast,
} from '@chakra-ui/react'
import { EditIcon } from '@chakra-ui/icons'
import { useNavigate } from 'react-router-dom'
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 { EditDomainModal } from '../../components/EditDomainModal'
import { ChevronIcon, PodStatusPanel } from '../../components/PodStatusPanel'
// Un provisioning en cours => on rafraîchit régulièrement.
@@ -41,6 +44,7 @@ export function Demos() {
const [busy, setBusy] = useState<string | null>(null)
const [toDelete, setToDelete] = useState<Demo | null>(null)
const [createModalOpen, setCreateModalOpen] = useState(false)
const [editingDomain, setEditingDomain] = useState<Demo | null>(null)
// --- Ligne dépliée (état live des pods) ---
const [expandedId, setExpandedId] = useState<string | null>(null)
@@ -209,18 +213,30 @@ export function Demos() {
<Badge colorScheme={statusColor(d.status)}>{statusLabel(d.status)}</Badge>
</Td>
<Td>
{d.status === 'ready' ? (
<Link
href={d.url}
color="primary.500"
isExternal
onClick={(e) => e.stopPropagation()}
>
{d.url}
</Link>
) : (
<Text color="gray.400"></Text>
)}
<HStack spacing={1}>
{d.status === 'ready' ? (
<Link
href={d.url}
color="primary.500"
isExternal
onClick={(e) => e.stopPropagation()}
>
{d.url}
</Link>
) : (
<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">
@@ -293,6 +309,12 @@ 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>
<EditDomainModal
demo={editingDomain}
onClose={() => setEditingDomain(null)}
onSaved={() => void load()}
/>
</>
)
}
+34 -12
View File
@@ -8,6 +8,7 @@ import {
Heading,
HStack,
Icon,
IconButton,
Link,
Spacer,
Spinner,
@@ -21,11 +22,13 @@ import {
Tr,
useToast,
} from '@chakra-ui/react'
import { EditIcon } from '@chakra-ui/icons'
import { useNavigate } from 'react-router-dom'
import { api, ApiError, type Demo, type DemoDetails } from '../../lib/api'
import { statusColor, statusLabel } from '../../lib/format'
import { ChevronIcon, PodStatusPanel } from '../../components/PodStatusPanel'
import { CreateDemoModal } from '../../components/CreateDemoModal'
import { EditDomainModal } from '../../components/EditDomainModal'
// Un provisioning en cours => on rafraîchit régulièrement.
const POLL_MS = 5000
@@ -38,6 +41,7 @@ export function PremiumDemos() {
const [demos, setDemos] = useState<Demo[]>([])
const [loading, setLoading] = useState(true)
const [createModalOpen, setCreateModalOpen] = useState(false)
const [editingDomain, setEditingDomain] = useState<Demo | null>(null)
// --- Ligne dépliée (état live des pods) ---
const [expandedId, setExpandedId] = useState<string | null>(null)
@@ -165,18 +169,30 @@ export function PremiumDemos() {
<Badge colorScheme={statusColor(d.status)}>{statusLabel(d.status)}</Badge>
</Td>
<Td>
{d.status === 'ready' ? (
<Link
href={d.url}
color="primary.500"
isExternal
onClick={(e) => e.stopPropagation()}
>
{d.url}
</Link>
) : (
<Text color="gray.400">—</Text>
)}
<HStack spacing={1}>
{d.status === 'ready' ? (
<Link
href={d.url}
color="primary.500"
isExternal
onClick={(e) => e.stopPropagation()}
>
{d.url}
</Link>
) : (
<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>
</Tr>
<Tr>
@@ -203,6 +219,12 @@ export function PremiumDemos() {
</Table>
</TableContainer>
)}
<EditDomainModal
demo={editingDomain}
onClose={() => setEditingDomain(null)}
onSaved={() => void load()}
/>
</>
)
}
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/App.tsx","./src/main.tsx","./src/setupTests.ts","./src/theme.ts","./src/vite-env.d.ts","./src/components/BackofficeLayout.tsx","./src/components/ColorModeToggle.tsx","./src/components/ConfirmDialog.test.tsx","./src/components/ConfirmDialog.tsx","./src/components/CreateDemoModal.tsx","./src/components/Footer.tsx","./src/components/Header.tsx","./src/components/PasswordInput.tsx","./src/components/PodStatusPanel.tsx","./src/components/PublicLayout.tsx","./src/lib/api.ts","./src/lib/auth.tsx","./src/lib/format.test.ts","./src/lib/format.ts","./src/pages/AdminLogin.tsx","./src/pages/Landing.tsx","./src/pages/Login.tsx","./src/pages/Pricing.tsx","./src/pages/Register.tsx","./src/pages/backoffice/Codes.tsx","./src/pages/backoffice/Demos.tsx","./src/pages/backoffice/PremiumDemos.tsx","./src/pages/backoffice/Profile.tsx","./src/pages/backoffice/Subscription.tsx"],"version":"5.9.3"}
{"root":["./src/App.tsx","./src/main.tsx","./src/setupTests.ts","./src/theme.ts","./src/vite-env.d.ts","./src/components/BackofficeLayout.tsx","./src/components/ColorModeToggle.tsx","./src/components/ConfirmDialog.test.tsx","./src/components/ConfirmDialog.tsx","./src/components/CreateDemoModal.tsx","./src/components/EditDomainModal.tsx","./src/components/Footer.tsx","./src/components/Header.tsx","./src/components/PasswordInput.tsx","./src/components/PodStatusPanel.tsx","./src/components/PublicLayout.tsx","./src/lib/api.ts","./src/lib/auth.tsx","./src/lib/format.test.ts","./src/lib/format.ts","./src/pages/AdminLogin.tsx","./src/pages/Landing.tsx","./src/pages/Login.tsx","./src/pages/Pricing.tsx","./src/pages/Register.tsx","./src/pages/backoffice/Codes.tsx","./src/pages/backoffice/Demos.tsx","./src/pages/backoffice/PremiumDemos.tsx","./src/pages/backoffice/Profile.tsx","./src/pages/backoffice/Subscription.tsx"],"version":"5.9.3"}