fix(prisma): remplace les enums par des String (compat SQLite)

SQLite ne supporte pas les enums Prisma. Les 6 enums (HostStatus,
HostSource, Protocol, PortState, ScanType, ScanStatus) sont
convertis en champs String avec valeurs par défaut littérales et
documentation inline des valeurs autorisées. La validation reste
assurée par Zod côté app.

Adapte aussi les mappings statusVariant des pages hosts/scans
en Record<string, BadgeVariant> avec fallback 'default'.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 14:09:57 +02:00
parent 6c020e7ee3
commit dc8dbc3cd7
3 changed files with 31 additions and 51 deletions

View File

@@ -49,10 +49,11 @@ model Host {
vendor String? // Issue du MAC (OUI) ou d'une fingerprint vendor String? // Issue du MAC (OUI) ou d'une fingerprint
osGuess String? // OS détecté / renseigné osGuess String? // OS détecté / renseigné
status HostStatus @default(UNKNOWN) // Valeurs : "UP" | "DOWN" | "UNKNOWN" (validation Zod côté app)
status String @default("UNKNOWN")
// Origine de la fiche : saisie manuelle ou découverte // Origine de la fiche : "MANUAL" | "DISCOVERED" | "IMPORTED"
source HostSource @default(MANUAL) source String @default("MANUAL")
// Relations // Relations
networkId String? networkId String?
@@ -71,17 +72,10 @@ model Host {
@@index([status]) @@index([status])
} }
enum HostStatus { // NOTE : les enums Prisma ne sont pas supportés par SQLite. On stocke
UP // donc tous les statuts/types en `String` et on valide les valeurs
DOWN // possibles côté app via Zod. Constantes documentées au-dessus de
UNKNOWN // chaque champ.
}
enum HostSource {
MANUAL
DISCOVERED
IMPORTED
}
// ===================================================================== // =====================================================================
// Ports ouverts sur un hôte // Ports ouverts sur un hôte
@@ -89,10 +83,12 @@ enum HostSource {
model Port { model Port {
id String @id @default(cuid()) id String @id @default(cuid())
number Int number Int
protocol Protocol @default(TCP) // Valeurs : "TCP" | "UDP"
protocol String @default("TCP")
serviceName String? // ex: "http", "ssh", renseigné ou deviné serviceName String? // ex: "http", "ssh", renseigné ou deviné
banner String? // Banner applicatif collecté banner String? // Banner applicatif collecté
state PortState @default(OPEN) // Valeurs : "OPEN" | "CLOSED" | "FILTERED" | "UNKNOWN"
state String @default("OPEN")
hostId String hostId String
host Host @relation(fields: [hostId], references: [id], onDelete: Cascade) host Host @relation(fields: [hostId], references: [id], onDelete: Cascade)
@@ -110,17 +106,8 @@ model Port {
@@index([number]) @@index([number])
} }
enum Protocol { // Protocol : "TCP" | "UDP" — voir Port.protocol
TCP // PortState : "OPEN" | "CLOSED" | "FILTERED" | "UNKNOWN" — voir Port.state
UDP
}
enum PortState {
OPEN
CLOSED
FILTERED
UNKNOWN
}
// ===================================================================== // =====================================================================
// Applications (Jellyfin, Home Assistant, Nextcloud, etc.) // Applications (Jellyfin, Home Assistant, Nextcloud, etc.)
@@ -159,9 +146,11 @@ model HostApplication {
// Découverte réseau — historique des scans // Découverte réseau — historique des scans
// ===================================================================== // =====================================================================
model Scan { model Scan {
id String @id @default(cuid()) id String @id @default(cuid())
type ScanType // Valeurs : "PING" | "PORT" | "ARP" | "MDNS" | "FULL"
status ScanStatus @default(PENDING) type String
// Valeurs : "PENDING" | "RUNNING" | "COMPLETED" | "FAILED" | "CANCELLED"
status String @default("PENDING")
target String // CIDR, IP, ou hôte target String // CIDR, IP, ou hôte
params String? // JSON (Zod validé côté app) params String? // JSON (Zod validé côté app)
startedAt DateTime? startedAt DateTime?
@@ -180,21 +169,8 @@ model Scan {
@@index([type]) @@index([type])
} }
enum ScanType { // ScanType : "PING" | "PORT" | "ARP" | "MDNS" | "FULL" — voir Scan.type
PING // ScanStatus : "PENDING" | "RUNNING" | "COMPLETED" | "FAILED" | "CANCELLED" — voir Scan.status
PORT
ARP
MDNS
FULL
}
enum ScanStatus {
PENDING
RUNNING
COMPLETED
FAILED
CANCELLED
}
// Résultat individuel d'un scan (rattaché à un hôte quand possible) // Résultat individuel d'un scan (rattaché à un hôte quand possible)
model ScanResult { model ScanResult {

View File

@@ -30,11 +30,13 @@ async function getHosts() {
} }
} }
const statusVariant = { type BadgeVariant = 'default' | 'success' | 'warning' | 'destructive' | 'info' | 'outline';
const statusVariant: Record<string, BadgeVariant> = {
UP: 'success', UP: 'success',
DOWN: 'destructive', DOWN: 'destructive',
UNKNOWN: 'default', UNKNOWN: 'default',
} as const; };
export default async function HostsPage() { export default async function HostsPage() {
const hosts = await getHosts(); const hosts = await getHosts();
@@ -82,7 +84,7 @@ export default async function HostsPage() {
{h.network?.name ?? '—'} {h.network?.name ?? '—'}
</TableCell> </TableCell>
<TableCell> <TableCell>
<Badge variant={statusVariant[h.status]}>{h.status}</Badge> <Badge variant={statusVariant[h.status] ?? 'default'}>{h.status}</Badge>
</TableCell> </TableCell>
<TableCell className="text-muted-foreground"> <TableCell className="text-muted-foreground">
{h.ports.length > 0 ? ( {h.ports.length > 0 ? (

View File

@@ -26,13 +26,15 @@ async function getScans() {
} }
} }
const statusVariant = { type BadgeVariant = 'default' | 'success' | 'warning' | 'destructive' | 'info' | 'outline';
const statusVariant: Record<string, BadgeVariant> = {
COMPLETED: 'success', COMPLETED: 'success',
RUNNING: 'info', RUNNING: 'info',
PENDING: 'default', PENDING: 'default',
FAILED: 'destructive', FAILED: 'destructive',
CANCELLED: 'warning', CANCELLED: 'warning',
} as const; };
function formatDuration(start: Date | null, end: Date | null) { function formatDuration(start: Date | null, end: Date | null) {
if (!start || !end) return '—'; if (!start || !end) return '—';
@@ -83,7 +85,7 @@ export default async function ScansPage() {
</TableCell> </TableCell>
<TableCell className="font-mono text-sm">{s.target}</TableCell> <TableCell className="font-mono text-sm">{s.target}</TableCell>
<TableCell> <TableCell>
<Badge variant={statusVariant[s.status]}>{s.status}</Badge> <Badge variant={statusVariant[s.status] ?? 'default'}>{s.status}</Badge>
</TableCell> </TableCell>
<TableCell className="tabular-nums">{s.hostsFound}</TableCell> <TableCell className="tabular-nums">{s.hostsFound}</TableCell>
<TableCell className="tabular-nums">{s.portsFound}</TableCell> <TableCell className="tabular-nums">{s.portsFound}</TableCell>