first commit

This commit is contained in:
Mathieu BOURBON
2026-04-18 16:24:44 +02:00
commit fbb6138c28
72 changed files with 3509 additions and 0 deletions

213
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,213 @@
// IPAM — Schéma Prisma
// ---------------------------------------------------------------------
// Le `provider` est injecté via la variable d'environnement DATABASE_PROVIDER
// (SQLite par défaut, PostgreSQL en option). Les deux sont pris en charge.
// Lors d'un changement de provider, régénérer le client : `pnpm db:generate`
// et lancer une nouvelle migration : `pnpm db:migrate`.
// ---------------------------------------------------------------------
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = env("DATABASE_PROVIDER")
url = env("DATABASE_URL")
}
// =====================================================================
// Réseaux (subnets / VLAN)
// =====================================================================
model Network {
id String @id @default(cuid())
name String
cidr String @unique // ex: "192.168.1.0/24"
description String?
vlanId Int?
gateway String?
dnsServers String? // CSV — compat SQLite (pas de array natif)
color String? // Couleur d'affichage UI
hosts Host[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([cidr])
}
// =====================================================================
// Hôtes (une IP = un hôte)
// =====================================================================
model Host {
id String @id @default(cuid())
ipAddress String @unique // IPv4/IPv6
hostname String?
macAddress String?
description String?
vendor String? // Issue du MAC (OUI) ou d'une fingerprint
osGuess String? // OS détecté / renseigné
status HostStatus @default(UNKNOWN)
// Origine de la fiche : saisie manuelle ou découverte
source HostSource @default(MANUAL)
// Relations
networkId String?
network Network? @relation(fields: [networkId], references: [id], onDelete: SetNull)
ports Port[]
applications HostApplication[]
scanResults ScanResult[]
lastSeenAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([ipAddress])
@@index([networkId])
@@index([status])
}
enum HostStatus {
UP
DOWN
UNKNOWN
}
enum HostSource {
MANUAL
DISCOVERED
IMPORTED
}
// =====================================================================
// Ports ouverts sur un hôte
// =====================================================================
model Port {
id String @id @default(cuid())
number Int
protocol Protocol @default(TCP)
serviceName String? // ex: "http", "ssh", renseigné ou deviné
banner String? // Banner applicatif collecté
state PortState @default(OPEN)
hostId String
host Host @relation(fields: [hostId], references: [id], onDelete: Cascade)
// Lien éventuel vers une application métier déclarée
applicationId String?
application Application? @relation(fields: [applicationId], references: [id], onDelete: SetNull)
lastCheckedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([hostId, number, protocol])
@@index([hostId])
@@index([number])
}
enum Protocol {
TCP
UDP
}
enum PortState {
OPEN
CLOSED
FILTERED
UNKNOWN
}
// =====================================================================
// Applications (Jellyfin, Home Assistant, Nextcloud, etc.)
// =====================================================================
model Application {
id String @id @default(cuid())
name String @unique
description String?
category String? // ex: "media", "monitoring", "storage"
icon String? // URL ou clé d'icône
url String? // URL principale d'accès
hosts HostApplication[]
ports Port[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// Table de jointure Hôte <-> Application (N:N)
model HostApplication {
hostId String
applicationId String
host Host @relation(fields: [hostId], references: [id], onDelete: Cascade)
application Application @relation(fields: [applicationId], references: [id], onDelete: Cascade)
notes String?
createdAt DateTime @default(now())
@@id([hostId, applicationId])
}
// =====================================================================
// Découverte réseau — historique des scans
// =====================================================================
model Scan {
id String @id @default(cuid())
type ScanType
status ScanStatus @default(PENDING)
target String // CIDR, IP, ou hôte
params String? // JSON (Zod validé côté app)
startedAt DateTime?
endedAt DateTime?
hostsFound Int @default(0)
portsFound Int @default(0)
errorMessage String?
results ScanResult[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([status])
@@index([type])
}
enum ScanType {
PING
PORT
ARP
MDNS
FULL
}
enum ScanStatus {
PENDING
RUNNING
COMPLETED
FAILED
CANCELLED
}
// Résultat individuel d'un scan (rattaché à un hôte quand possible)
model ScanResult {
id String @id @default(cuid())
scanId String
scan Scan @relation(fields: [scanId], references: [id], onDelete: Cascade)
ipAddress String
hostId String?
host Host? @relation(fields: [hostId], references: [id], onDelete: SetNull)
data String // JSON (ports trouvés, services mDNS, MAC, etc.)
createdAt DateTime @default(now())
@@index([scanId])
@@index([ipAddress])
}

62
prisma/seed.ts Normal file
View File

@@ -0,0 +1,62 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
// Réseau d'exemple
const network = await prisma.network.upsert({
where: { cidr: '192.168.1.0/24' },
update: {},
create: {
name: 'Homelab LAN',
cidr: '192.168.1.0/24',
description: 'Réseau principal du homelab',
gateway: '192.168.1.1',
dnsServers: '1.1.1.1,9.9.9.9',
color: '#3b82f6',
},
});
// Applications usuelles en homelab
const apps = [
{ name: 'Jellyfin', category: 'media', description: 'Serveur multimédia' },
{ name: 'Home Assistant', category: 'automation', description: 'Domotique' },
{ name: 'Nextcloud', category: 'storage', description: 'Cloud personnel' },
{ name: 'Pi-hole', category: 'network', description: 'Bloqueur DNS' },
{ name: 'Portainer', category: 'management', description: 'Gestion Docker' },
{ name: 'Grafana', category: 'monitoring', description: 'Dashboards' },
];
for (const app of apps) {
await prisma.application.upsert({
where: { name: app.name },
update: {},
create: app,
});
}
// Hôte d'exemple
await prisma.host.upsert({
where: { ipAddress: '192.168.1.1' },
update: {},
create: {
ipAddress: '192.168.1.1',
hostname: 'router',
description: 'Passerelle / routeur du homelab',
status: 'UP',
source: 'MANUAL',
networkId: network.id,
},
});
console.log('✅ Seed terminé');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});