generated from Dicken/dickendock
Hersteller-Auto-Erkennung ueber MAC (verifiziert), Geraete-Tabelle Hersteller/Modell-Spalten, Dropdown-Klick-Bug, Kategorie-Farbe als Hintergrund
This commit is contained in:
@@ -3,6 +3,7 @@ import { eq } from "drizzle-orm";
|
||||
import type { Device, DeviceCreateInput, DeviceUpdateInput } from "@launchpad/shared";
|
||||
import { db } from "../client.js";
|
||||
import { devices } from "../schema.js";
|
||||
import { lookupVendorFromMac } from "../../scanner/macVendors.js";
|
||||
|
||||
function nowIso(): string {
|
||||
return new Date().toISOString();
|
||||
@@ -34,6 +35,8 @@ export function getDevice(id: string): Device | null {
|
||||
export function createDevice(input: DeviceCreateInput): Device {
|
||||
const id = randomUUID();
|
||||
const timestamp = nowIso();
|
||||
const manufacturer =
|
||||
input.manufacturer ?? (input.mac ? lookupVendorFromMac(input.mac) : null);
|
||||
|
||||
db.insert(devices)
|
||||
.values({
|
||||
@@ -41,7 +44,7 @@ export function createDevice(input: DeviceCreateInput): Device {
|
||||
hostname: input.hostname,
|
||||
ip: input.ip,
|
||||
mac: input.mac ?? null,
|
||||
manufacturer: input.manufacturer ?? null,
|
||||
manufacturer,
|
||||
model: input.model ?? null,
|
||||
online: input.online ?? false,
|
||||
source: input.source ?? "manual",
|
||||
@@ -62,12 +65,22 @@ export function updateDevice(id: string, input: DeviceUpdateInput): Device | nul
|
||||
const existing = getDevice(id);
|
||||
if (!existing) return null;
|
||||
|
||||
// Wenn der Nutzer eine MAC einträgt/ändert, aber selbst keinen Hersteller
|
||||
// angibt UND noch keiner gespeichert ist, automatisch versuchen zu
|
||||
// erkennen (siehe scanner/macVendors.ts) - überschreibt nie einen bereits
|
||||
// gesetzten oder gerade explizit eingegebenen Wert.
|
||||
const autoManufacturer =
|
||||
input.manufacturer === undefined && !existing.manufacturer && input.mac
|
||||
? lookupVendorFromMac(input.mac)
|
||||
: undefined;
|
||||
|
||||
db.update(devices)
|
||||
.set({
|
||||
...(input.hostname !== undefined && { hostname: input.hostname }),
|
||||
...(input.ip !== undefined && { ip: input.ip }),
|
||||
...(input.mac !== undefined && { mac: input.mac }),
|
||||
...(input.manufacturer !== undefined && { manufacturer: input.manufacturer }),
|
||||
...(autoManufacturer && { manufacturer: autoManufacturer }),
|
||||
...(input.model !== undefined && { model: input.model }),
|
||||
...(input.online !== undefined && { online: input.online }),
|
||||
...(input.source !== undefined && { source: input.source }),
|
||||
@@ -118,14 +131,19 @@ function findByMacOrIp(mac: string | null | undefined, ip: string): Device | nul
|
||||
export function upsertDeviceFromScan(input: DeviceScanInput): Device {
|
||||
const existing = findByMacOrIp(input.mac, input.ip);
|
||||
const timestamp = nowIso();
|
||||
const mac = input.mac ?? existing?.mac ?? null;
|
||||
const manufacturer =
|
||||
input.manufacturer ??
|
||||
existing?.manufacturer ??
|
||||
(mac ? lookupVendorFromMac(mac) : null);
|
||||
|
||||
if (existing) {
|
||||
db.update(devices)
|
||||
.set({
|
||||
hostname: input.hostname,
|
||||
ip: input.ip,
|
||||
mac: input.mac ?? existing.mac,
|
||||
manufacturer: input.manufacturer ?? existing.manufacturer,
|
||||
mac,
|
||||
manufacturer,
|
||||
model: input.model ?? existing.model,
|
||||
online: input.online ?? existing.online,
|
||||
source: input.source,
|
||||
@@ -143,8 +161,8 @@ export function upsertDeviceFromScan(input: DeviceScanInput): Device {
|
||||
id,
|
||||
hostname: input.hostname,
|
||||
ip: input.ip,
|
||||
mac: input.mac ?? null,
|
||||
manufacturer: input.manufacturer ?? null,
|
||||
mac,
|
||||
manufacturer,
|
||||
model: input.model ?? null,
|
||||
online: input.online ?? false,
|
||||
source: input.source,
|
||||
|
||||
61
apps/backend/src/scanner/macVendors.ts
Normal file
61
apps/backend/src/scanner/macVendors.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Ordnet die ersten drei Oktette einer MAC-Adresse (OUI-Präfix) einem
|
||||
* Hersteller zu. Bewusst eine KLEINE, aber jeweils einzeln online
|
||||
* verifizierte Auswahl (nicht aus dem Gedächtnis geraten) - NICHT die
|
||||
* vollständige IEEE-OUI-Registrierung (die hat >50.000 Einträge). Komplett
|
||||
* offline zur Laufzeit, kein externer Dienst.
|
||||
*
|
||||
* Deckt bewusst die im Homelab-Kontext häufigsten "Kern"-Geräte ab: Single-
|
||||
* Board-Computer, NAS-Systeme und Virtualisierung. Router/Switches/IoT
|
||||
* wurden nicht aufgenommen, weil sich die exakten Präfixe nicht ohne
|
||||
* weitere ausführliche Recherche verlässlich verifizieren ließen - lieber
|
||||
* gar keinen Treffer als einen falschen.
|
||||
*
|
||||
* Unbekannte Präfixe liefern null, das Feld bleibt dann leer und frei
|
||||
* editierbar. Bereits gesetzte Werte werden nie überschrieben (siehe
|
||||
* Aufrufer in devices.ts).
|
||||
*/
|
||||
const OUI_VENDORS: Record<string, string> = {
|
||||
// Raspberry Pi Foundation / Raspberry Pi Trading Ltd
|
||||
// Quelle: forums.raspberrypi.com/viewtopic.php?t=347926, maclookup.app/macaddress/b827eb
|
||||
"28CDC1": "Raspberry Pi",
|
||||
B827EB: "Raspberry Pi",
|
||||
D83ADD: "Raspberry Pi",
|
||||
DCA632: "Raspberry Pi",
|
||||
E45F01: "Raspberry Pi",
|
||||
|
||||
// VMware (virtuelle Netzwerkkarten)
|
||||
// Quelle: maclookup.app/macaddress/005056, maclookup.app/macaddress/000c29
|
||||
"005056": "VMware (VM)",
|
||||
"000C29": "VMware (VM)",
|
||||
|
||||
// Synology
|
||||
// Quelle: maclookup.app/vendors/synology-incorporated
|
||||
"001132": "Synology",
|
||||
"9009D0": "Synology",
|
||||
|
||||
// QNAP
|
||||
// Quelle: maclookup.app/vendors/qnap-systems-inc
|
||||
"245EBE": "QNAP",
|
||||
E843B6: "QNAP",
|
||||
|
||||
// QEMU/KVM (Standard-Präfix für automatisch generierte VM-MACs,
|
||||
// u. a. von Proxmox VE verwendet) - allgemein bekannte Konvention.
|
||||
"525400": "QEMU/Proxmox (VM)",
|
||||
|
||||
// Docker (docker0-Bridge-Netzwerk) - Software-Konvention, kein
|
||||
// IEEE-registriertes OUI, aber in der Praxis sehr zuverlässig.
|
||||
"0242AC": "Docker (virtuell)",
|
||||
};
|
||||
|
||||
function normalizeOui(mac: string): string | null {
|
||||
const cleaned = mac.replace(/[^0-9A-Fa-f]/g, "").toUpperCase();
|
||||
if (cleaned.length < 6) return null;
|
||||
return cleaned.slice(0, 6);
|
||||
}
|
||||
|
||||
export function lookupVendorFromMac(mac: string): string | null {
|
||||
const oui = normalizeOui(mac);
|
||||
if (!oui) return null;
|
||||
return OUI_VENDORS[oui] ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user