feat(ui): klickbarer Prototyp mit Beispieldaten (#002)
Layout im AdminLTE-Stil (Sidebar, Kopfleiste, Inhaltskopf, Footer), Logo als SVG-Komponente, Farbschema mit Dark-Mode, Dashboard, Kunden- und Host-Ansichten, Rechnungsuebersicht, Jahresbahn und globale Suche (Strg+K). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,727 @@
|
||||
import type { Alert, Host, Kunde, Laufzeit, Report } from "./types";
|
||||
|
||||
/**
|
||||
* Beispieldaten für den Prototyp (#002). Bezugsdatum ist HEUTE, damit Ablauffristen
|
||||
* und Badges realistisch aussehen. In #003 ersetzt die Datenbank diese Datei.
|
||||
*/
|
||||
export const HEUTE = new Date("2026-09-17T08:00:00Z");
|
||||
|
||||
export const kunden: Kunde[] = [
|
||||
{
|
||||
id: "k1",
|
||||
name: "Brandl Elektrotechnik GmbH",
|
||||
kundennummer: "K-1001",
|
||||
ansprechpartner: "Sabine Brandl",
|
||||
email: "it@brandl-elektro.de",
|
||||
telefon: "+49 871 998120",
|
||||
notizen: "Wartungsvertrag bis 12/2027. Rechnungen bitte gesammelt zum Quartalsende.",
|
||||
standorte: [
|
||||
{ id: "s1", kundeId: "k1", name: "Zentrale Landshut", ort: "Landshut" },
|
||||
{ id: "s2", kundeId: "k1", name: "Werk Ergolding", ort: "Ergolding" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "k2",
|
||||
name: "Huber Logistik AG",
|
||||
kundennummer: "K-1002",
|
||||
ansprechpartner: "Markus Huber",
|
||||
email: "edv@huber-logistik.de",
|
||||
telefon: "+49 89 4412300",
|
||||
notizen: null,
|
||||
standorte: [{ id: "s3", kundeId: "k2", name: "Hauptlager München", ort: "München" }],
|
||||
},
|
||||
{
|
||||
id: "k3",
|
||||
name: "Lehner Steuerberatung",
|
||||
kundennummer: "K-1003",
|
||||
ansprechpartner: "Dr. Eva Lehner",
|
||||
email: "kanzlei@lehner-stb.de",
|
||||
telefon: "+49 941 776540",
|
||||
notizen: "Nur Fernwartung nach Voranmeldung.",
|
||||
standorte: [{ id: "s4", kundeId: "k3", name: "Kanzlei Regensburg", ort: "Regensburg" }],
|
||||
},
|
||||
{
|
||||
id: "k4",
|
||||
name: "Systemhaus Nord GmbH",
|
||||
kundennummer: "K-1004",
|
||||
ansprechpartner: "Jens Petersen",
|
||||
email: "service@systemhaus-nord.de",
|
||||
telefon: "+49 431 220190",
|
||||
notizen: null,
|
||||
standorte: [{ id: "s5", kundeId: "k4", name: "Rechenzentrum Kiel", ort: "Kiel" }],
|
||||
},
|
||||
];
|
||||
|
||||
export const hosts: Host[] = [
|
||||
{
|
||||
id: "h1",
|
||||
hostname: "pve-brandl-01",
|
||||
fqdn: "pve-brandl-01.brandl.local",
|
||||
kundeId: "k1",
|
||||
standortId: "s1",
|
||||
produkt: "pve",
|
||||
produktVersion: "pve-manager/8.3.2/dc2b2f4e",
|
||||
kernel: "6.8.12-4-pve",
|
||||
serverId: "A1B2C3D4E5F60718",
|
||||
clusterName: "brandl-cluster",
|
||||
nodeName: "node01",
|
||||
ips: ["192.168.10.11", "10.20.0.11"],
|
||||
sockets: 1,
|
||||
cpuModell: "Intel(R) Xeon(R) E-2388G",
|
||||
uptimeSekunden: 4_233_600,
|
||||
paketUpdates: 3,
|
||||
agentVersion: "0.2.0",
|
||||
letzterReport: "2026-09-17T03:41:00Z",
|
||||
subscription: {
|
||||
key: "pve1c-a6bd21fcb6",
|
||||
status: "active",
|
||||
level: "c",
|
||||
produktname: "Proxmox VE Community Subscription 1 CPU/year",
|
||||
lizenzierteSockets: 1,
|
||||
regdate: "2025-09-24T00:00:00Z",
|
||||
nextduedate: "2026-09-24T00:00:00Z",
|
||||
checktime: "2026-09-17T03:40:00Z",
|
||||
},
|
||||
tags: ["produktiv", "cluster"],
|
||||
notizen: "Hauptvirtualisierung Zentrale.",
|
||||
},
|
||||
{
|
||||
id: "h2",
|
||||
hostname: "pve-brandl-02",
|
||||
fqdn: "pve-brandl-02.brandl.local",
|
||||
kundeId: "k1",
|
||||
standortId: "s2",
|
||||
produkt: "pve",
|
||||
produktVersion: "pve-manager/8.3.2/dc2b2f4e",
|
||||
kernel: "6.8.12-4-pve",
|
||||
serverId: "B2C3D4E5F6071829",
|
||||
clusterName: "brandl-cluster",
|
||||
nodeName: "node02",
|
||||
ips: ["192.168.10.12"],
|
||||
sockets: 2,
|
||||
cpuModell: "Intel(R) Xeon(R) Silver 4310",
|
||||
uptimeSekunden: 1_987_200,
|
||||
paketUpdates: 0,
|
||||
agentVersion: "0.2.0",
|
||||
letzterReport: "2026-09-17T03:52:00Z",
|
||||
subscription: {
|
||||
key: "pve2c-3f91ad77e2",
|
||||
status: "active",
|
||||
level: "c",
|
||||
produktname: "Proxmox VE Community Subscription 2 CPUs/year",
|
||||
lizenzierteSockets: 2,
|
||||
regdate: "2025-10-08T00:00:00Z",
|
||||
nextduedate: "2026-10-08T00:00:00Z",
|
||||
checktime: "2026-09-17T03:51:00Z",
|
||||
},
|
||||
tags: ["produktiv", "cluster"],
|
||||
notizen: null,
|
||||
},
|
||||
{
|
||||
id: "h3",
|
||||
hostname: "pbs-brandl-bak",
|
||||
fqdn: "pbs-brandl-bak.brandl.local",
|
||||
kundeId: "k1",
|
||||
standortId: "s2",
|
||||
produkt: "pbs",
|
||||
produktVersion: "proxmox-backup-server/3.2.7",
|
||||
kernel: "6.8.12-4-pve",
|
||||
serverId: "C3D4E5F607182A3B",
|
||||
clusterName: null,
|
||||
nodeName: null,
|
||||
ips: ["192.168.10.20"],
|
||||
sockets: 1,
|
||||
cpuModell: "AMD Ryzen 7 5700G",
|
||||
uptimeSekunden: 8_553_600,
|
||||
paketUpdates: 7,
|
||||
agentVersion: "0.2.0",
|
||||
letzterReport: "2026-09-17T04:10:00Z",
|
||||
subscription: {
|
||||
key: "pbsc-9d41be5507",
|
||||
status: "active",
|
||||
level: "c",
|
||||
produktname: "Proxmox Backup Server Community Subscription 1 CPU/year",
|
||||
lizenzierteSockets: 1,
|
||||
regdate: "2026-03-02T00:00:00Z",
|
||||
nextduedate: "2027-03-02T00:00:00Z",
|
||||
checktime: "2026-09-17T04:09:00Z",
|
||||
},
|
||||
tags: ["backup"],
|
||||
notizen: null,
|
||||
},
|
||||
{
|
||||
id: "h4",
|
||||
hostname: "pve-huber-01",
|
||||
fqdn: "pve-huber-01.huber.intern",
|
||||
kundeId: "k2",
|
||||
standortId: "s3",
|
||||
produkt: "pve",
|
||||
produktVersion: "pve-manager/8.2.4/faa83925",
|
||||
kernel: "6.8.4-2-pve",
|
||||
serverId: "D4E5F607182A3B4C",
|
||||
clusterName: null,
|
||||
nodeName: null,
|
||||
ips: ["10.8.0.5", "192.168.30.5"],
|
||||
sockets: 2,
|
||||
cpuModell: "Intel(R) Xeon(R) Gold 5318Y",
|
||||
uptimeSekunden: 15_120_000,
|
||||
paketUpdates: 24,
|
||||
agentVersion: "0.2.0",
|
||||
letzterReport: "2026-09-17T02:15:00Z",
|
||||
subscription: {
|
||||
key: "pve1c-77c0ea1b93",
|
||||
status: "active",
|
||||
level: "c",
|
||||
produktname: "Proxmox VE Community Subscription 1 CPU/year",
|
||||
lizenzierteSockets: 1,
|
||||
regdate: "2025-12-01T00:00:00Z",
|
||||
nextduedate: "2026-12-01T00:00:00Z",
|
||||
checktime: "2026-09-17T02:14:00Z",
|
||||
},
|
||||
tags: ["produktiv"],
|
||||
notizen: "Zweiter Sockel im Sommer nachgerüstet.",
|
||||
},
|
||||
{
|
||||
id: "h5",
|
||||
hostname: "pve-huber-02",
|
||||
fqdn: "pve-huber-02.huber.intern",
|
||||
kundeId: "k2",
|
||||
standortId: "s3",
|
||||
produkt: "pve",
|
||||
produktVersion: "pve-manager/8.2.4/faa83925",
|
||||
kernel: "6.8.4-2-pve",
|
||||
serverId: "E5F607182A3B4C5D",
|
||||
clusterName: null,
|
||||
nodeName: null,
|
||||
ips: ["192.168.30.6"],
|
||||
sockets: 1,
|
||||
cpuModell: "Intel(R) Core(TM) i7-13700",
|
||||
uptimeSekunden: 604_800,
|
||||
paketUpdates: 41,
|
||||
agentVersion: "0.2.0",
|
||||
letzterReport: "2026-09-17T02:22:00Z",
|
||||
subscription: {
|
||||
key: null,
|
||||
status: "notfound",
|
||||
level: null,
|
||||
produktname: null,
|
||||
lizenzierteSockets: null,
|
||||
regdate: null,
|
||||
nextduedate: null,
|
||||
checktime: "2026-09-17T02:21:00Z",
|
||||
},
|
||||
tags: ["test"],
|
||||
notizen: "Testumgebung, Lizenzierung noch offen.",
|
||||
},
|
||||
{
|
||||
id: "h6",
|
||||
hostname: "pmg-lehner-mail",
|
||||
fqdn: "mail.lehner-stb.de",
|
||||
kundeId: "k3",
|
||||
standortId: "s4",
|
||||
produkt: "pmg",
|
||||
produktVersion: "pmg/8.1.2",
|
||||
kernel: "6.8.12-2-pve",
|
||||
serverId: "F607182A3B4C5D6E",
|
||||
clusterName: null,
|
||||
nodeName: null,
|
||||
ips: ["81.21.44.18", "192.168.5.10"],
|
||||
sockets: 1,
|
||||
cpuModell: "Intel(R) Xeon(R) E-2124",
|
||||
uptimeSekunden: 20_736_000,
|
||||
paketUpdates: 2,
|
||||
agentVersion: "0.2.0",
|
||||
letzterReport: "2026-09-17T01:30:00Z",
|
||||
subscription: {
|
||||
key: "pmgc-2be0f41a88",
|
||||
status: "active",
|
||||
level: "c",
|
||||
produktname: "Proxmox Mail Gateway Community Subscription 1 CPU/year",
|
||||
lizenzierteSockets: 1,
|
||||
regdate: "2025-11-15T00:00:00Z",
|
||||
nextduedate: "2026-11-15T00:00:00Z",
|
||||
checktime: "2026-09-17T01:29:00Z",
|
||||
},
|
||||
tags: ["mail"],
|
||||
notizen: null,
|
||||
},
|
||||
{
|
||||
id: "h7",
|
||||
hostname: "pve-lehner-01",
|
||||
fqdn: "pve-lehner-01.lehner.local",
|
||||
kundeId: "k3",
|
||||
standortId: "s4",
|
||||
produkt: "pve",
|
||||
produktVersion: "pve-manager/8.1.10/4b06efb5",
|
||||
kernel: "6.5.13-5-pve",
|
||||
serverId: "07182A3B4C5D6E7F",
|
||||
clusterName: null,
|
||||
nodeName: null,
|
||||
ips: ["192.168.5.20"],
|
||||
sockets: 1,
|
||||
cpuModell: "Intel(R) Xeon(R) E-2226G",
|
||||
uptimeSekunden: 3_110_400,
|
||||
paketUpdates: 63,
|
||||
agentVersion: "0.2.0",
|
||||
letzterReport: "2026-09-17T01:45:00Z",
|
||||
subscription: {
|
||||
key: "pve1c-11ffa3c209",
|
||||
status: "expired",
|
||||
level: "c",
|
||||
produktname: "Proxmox VE Community Subscription 1 CPU/year",
|
||||
lizenzierteSockets: 1,
|
||||
regdate: "2024-08-30T00:00:00Z",
|
||||
nextduedate: "2026-08-30T00:00:00Z",
|
||||
checktime: "2026-09-17T01:44:00Z",
|
||||
},
|
||||
tags: ["produktiv"],
|
||||
notizen: "Verlängerung mit Kundin besprochen, Bestellung offen.",
|
||||
},
|
||||
{
|
||||
id: "h8",
|
||||
hostname: "pve-nord-01",
|
||||
fqdn: "pve-nord-01.nord.intern",
|
||||
kundeId: "k4",
|
||||
standortId: "s5",
|
||||
produkt: "pve",
|
||||
produktVersion: "pve-manager/9.0.3/ab12cd34",
|
||||
kernel: "6.14.5-1-pve",
|
||||
serverId: "182A3B4C5D6E7F80",
|
||||
clusterName: "nord-cluster",
|
||||
nodeName: "kiel01",
|
||||
ips: ["172.16.4.11"],
|
||||
sockets: 2,
|
||||
cpuModell: "AMD EPYC 7313P",
|
||||
uptimeSekunden: 11_059_200,
|
||||
paketUpdates: null,
|
||||
agentVersion: "0.1.0",
|
||||
letzterReport: "2026-09-14T03:12:00Z",
|
||||
subscription: {
|
||||
key: "pve2c-85d37ae041",
|
||||
status: "active",
|
||||
level: "c",
|
||||
produktname: "Proxmox VE Community Subscription 2 CPUs/year",
|
||||
lizenzierteSockets: 2,
|
||||
regdate: "2026-01-20T00:00:00Z",
|
||||
nextduedate: "2027-01-20T00:00:00Z",
|
||||
checktime: "2026-09-14T03:11:00Z",
|
||||
},
|
||||
tags: ["produktiv", "cluster"],
|
||||
notizen: null,
|
||||
},
|
||||
{
|
||||
id: "h9",
|
||||
hostname: "pve-demo-neu",
|
||||
fqdn: null,
|
||||
kundeId: null,
|
||||
standortId: null,
|
||||
produkt: "pve",
|
||||
produktVersion: "pve-manager/9.0.3/ab12cd34",
|
||||
kernel: "6.14.5-1-pve",
|
||||
serverId: "2A3B4C5D6E7F8091",
|
||||
clusterName: null,
|
||||
nodeName: null,
|
||||
ips: ["203.0.113.44"],
|
||||
sockets: 1,
|
||||
cpuModell: "Intel(R) Core(TM) i5-14500",
|
||||
uptimeSekunden: 86_400,
|
||||
paketUpdates: 12,
|
||||
agentVersion: "0.2.0",
|
||||
letzterReport: "2026-09-17T06:05:00Z",
|
||||
subscription: {
|
||||
key: "pve1c-5c72db90aa",
|
||||
status: "active",
|
||||
level: "c",
|
||||
produktname: "Proxmox VE Community Subscription 1 CPU/year",
|
||||
lizenzierteSockets: 1,
|
||||
regdate: "2026-06-05T00:00:00Z",
|
||||
nextduedate: "2027-06-05T00:00:00Z",
|
||||
checktime: "2026-09-17T06:04:00Z",
|
||||
},
|
||||
tags: [],
|
||||
notizen: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const laufzeiten: Laufzeit[] = [
|
||||
{
|
||||
id: "l1",
|
||||
hostId: "h1",
|
||||
key: "pve1c-a6bd21fcb6",
|
||||
start: "2025-09-24",
|
||||
ende: "2026-09-23",
|
||||
vorschlag: false,
|
||||
einkauf: {
|
||||
nummer: "PX-2025-88213",
|
||||
datum: "2025-09-22",
|
||||
faelligAm: "2025-10-06",
|
||||
nettoBetrag: 110,
|
||||
ustSatz: 19,
|
||||
rabatt: 0,
|
||||
zahlungsart: "Überweisung",
|
||||
bezahlt: true,
|
||||
bezahltAm: "2025-09-30",
|
||||
pdfDateiname: "proxmox-PX-2025-88213.pdf",
|
||||
},
|
||||
verkauf: {
|
||||
erstellt: true,
|
||||
nummer: "RE-2025-0412",
|
||||
datum: "2025-10-01",
|
||||
nettoBetrag: 165,
|
||||
ustSatz: 19,
|
||||
bezahlt: true,
|
||||
bezahltAm: "2025-10-14",
|
||||
pdfDateiname: "RE-2025-0412.pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "l2",
|
||||
hostId: "h1",
|
||||
key: "pve1c-a6bd21fcb6",
|
||||
start: "2026-09-24",
|
||||
ende: "2027-09-23",
|
||||
vorschlag: false,
|
||||
einkauf: {
|
||||
nummer: "PX-2026-14790",
|
||||
datum: "2026-09-10",
|
||||
faelligAm: "2026-09-24",
|
||||
nettoBetrag: 115,
|
||||
ustSatz: 19,
|
||||
rabatt: 0,
|
||||
zahlungsart: "Überweisung",
|
||||
bezahlt: false,
|
||||
bezahltAm: null,
|
||||
pdfDateiname: "proxmox-PX-2026-14790.pdf",
|
||||
},
|
||||
verkauf: null,
|
||||
},
|
||||
{
|
||||
id: "l3",
|
||||
hostId: "h2",
|
||||
key: "pve2c-3f91ad77e2",
|
||||
start: "2025-10-08",
|
||||
ende: "2026-10-07",
|
||||
vorschlag: false,
|
||||
einkauf: {
|
||||
nummer: "PX-2025-88213",
|
||||
datum: "2025-09-22",
|
||||
faelligAm: "2025-10-06",
|
||||
nettoBetrag: 220,
|
||||
ustSatz: 19,
|
||||
rabatt: 0,
|
||||
zahlungsart: "Überweisung",
|
||||
bezahlt: true,
|
||||
bezahltAm: "2025-09-30",
|
||||
pdfDateiname: "proxmox-PX-2025-88213.pdf",
|
||||
},
|
||||
verkauf: {
|
||||
erstellt: true,
|
||||
nummer: "RE-2025-0412",
|
||||
datum: "2025-10-01",
|
||||
nettoBetrag: 320,
|
||||
ustSatz: 19,
|
||||
bezahlt: true,
|
||||
bezahltAm: "2025-10-14",
|
||||
pdfDateiname: "RE-2025-0412.pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "l4",
|
||||
hostId: "h2",
|
||||
key: "pve2c-3f91ad77e2",
|
||||
start: "2026-10-08",
|
||||
ende: "2027-10-07",
|
||||
vorschlag: true,
|
||||
einkauf: null,
|
||||
verkauf: null,
|
||||
},
|
||||
{
|
||||
id: "l5",
|
||||
hostId: "h3",
|
||||
key: "pbsc-9d41be5507",
|
||||
start: "2026-03-02",
|
||||
ende: "2027-03-01",
|
||||
vorschlag: false,
|
||||
einkauf: {
|
||||
nummer: "PX-2026-09114",
|
||||
datum: "2026-02-26",
|
||||
faelligAm: "2026-03-12",
|
||||
nettoBetrag: 95,
|
||||
ustSatz: 19,
|
||||
rabatt: 0,
|
||||
zahlungsart: "Kreditkarte",
|
||||
bezahlt: true,
|
||||
bezahltAm: "2026-02-26",
|
||||
pdfDateiname: "proxmox-PX-2026-09114.pdf",
|
||||
},
|
||||
verkauf: {
|
||||
erstellt: true,
|
||||
nummer: "RE-2026-0188",
|
||||
datum: "2026-03-03",
|
||||
nettoBetrag: 140,
|
||||
ustSatz: 19,
|
||||
bezahlt: true,
|
||||
bezahltAm: "2026-03-20",
|
||||
pdfDateiname: "RE-2026-0188.pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "l6",
|
||||
hostId: "h4",
|
||||
key: "pve1c-77c0ea1b93",
|
||||
start: "2025-12-01",
|
||||
ende: "2026-11-30",
|
||||
vorschlag: false,
|
||||
einkauf: {
|
||||
nummer: "PX-2025-91002",
|
||||
datum: "2025-11-27",
|
||||
faelligAm: "2025-12-11",
|
||||
nettoBetrag: 110,
|
||||
ustSatz: 19,
|
||||
rabatt: 0,
|
||||
zahlungsart: "Überweisung",
|
||||
bezahlt: true,
|
||||
bezahltAm: "2025-12-02",
|
||||
pdfDateiname: null,
|
||||
},
|
||||
verkauf: {
|
||||
erstellt: true,
|
||||
nummer: "RE-2025-0501",
|
||||
datum: "2025-12-03",
|
||||
nettoBetrag: 170,
|
||||
ustSatz: 19,
|
||||
bezahlt: false,
|
||||
bezahltAm: null,
|
||||
pdfDateiname: "RE-2025-0501.pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "l7",
|
||||
hostId: "h6",
|
||||
key: "pmgc-2be0f41a88",
|
||||
start: "2025-11-15",
|
||||
ende: "2026-11-14",
|
||||
vorschlag: false,
|
||||
einkauf: {
|
||||
nummer: "PX-2025-90455",
|
||||
datum: "2025-11-11",
|
||||
faelligAm: "2025-11-25",
|
||||
nettoBetrag: 90,
|
||||
ustSatz: 19,
|
||||
rabatt: 5,
|
||||
zahlungsart: "Überweisung",
|
||||
bezahlt: true,
|
||||
bezahltAm: "2025-11-18",
|
||||
pdfDateiname: "proxmox-PX-2025-90455.pdf",
|
||||
},
|
||||
verkauf: {
|
||||
erstellt: true,
|
||||
nummer: "RE-2025-0489",
|
||||
datum: "2025-11-20",
|
||||
nettoBetrag: 135,
|
||||
ustSatz: 19,
|
||||
bezahlt: true,
|
||||
bezahltAm: "2025-12-01",
|
||||
pdfDateiname: "RE-2025-0489.pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "l8",
|
||||
hostId: "h7",
|
||||
key: "pve1c-11ffa3c209",
|
||||
start: "2025-08-30",
|
||||
ende: "2026-08-29",
|
||||
vorschlag: false,
|
||||
einkauf: {
|
||||
nummer: "PX-2025-85110",
|
||||
datum: "2025-08-26",
|
||||
faelligAm: "2025-09-09",
|
||||
nettoBetrag: 110,
|
||||
ustSatz: 19,
|
||||
rabatt: 0,
|
||||
zahlungsart: "Überweisung",
|
||||
bezahlt: true,
|
||||
bezahltAm: "2025-08-28",
|
||||
pdfDateiname: "proxmox-PX-2025-85110.pdf",
|
||||
},
|
||||
verkauf: {
|
||||
erstellt: true,
|
||||
nummer: "RE-2025-0377",
|
||||
datum: "2025-09-01",
|
||||
nettoBetrag: 165,
|
||||
ustSatz: 19,
|
||||
bezahlt: true,
|
||||
bezahltAm: "2025-09-15",
|
||||
pdfDateiname: "RE-2025-0377.pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "l9",
|
||||
hostId: "h8",
|
||||
key: "pve2c-85d37ae041",
|
||||
start: "2026-01-20",
|
||||
ende: "2027-01-19",
|
||||
vorschlag: false,
|
||||
einkauf: {
|
||||
nummer: "PX-2026-03998",
|
||||
datum: "2026-01-16",
|
||||
faelligAm: "2026-01-30",
|
||||
nettoBetrag: 220,
|
||||
ustSatz: 19,
|
||||
rabatt: 0,
|
||||
zahlungsart: "Überweisung",
|
||||
bezahlt: true,
|
||||
bezahltAm: "2026-01-19",
|
||||
pdfDateiname: "proxmox-PX-2026-03998.pdf",
|
||||
},
|
||||
verkauf: {
|
||||
erstellt: false,
|
||||
nummer: null,
|
||||
datum: null,
|
||||
nettoBetrag: null,
|
||||
ustSatz: 19,
|
||||
bezahlt: false,
|
||||
bezahltAm: null,
|
||||
pdfDateiname: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "l10",
|
||||
hostId: "h9",
|
||||
key: "pve1c-5c72db90aa",
|
||||
start: "2026-06-05",
|
||||
ende: "2027-06-04",
|
||||
vorschlag: false,
|
||||
einkauf: {
|
||||
nummer: "PX-2026-11233",
|
||||
datum: "2026-06-02",
|
||||
faelligAm: "2026-06-16",
|
||||
nettoBetrag: 115,
|
||||
ustSatz: 19,
|
||||
rabatt: 0,
|
||||
zahlungsart: "Überweisung",
|
||||
bezahlt: true,
|
||||
bezahltAm: "2026-06-04",
|
||||
pdfDateiname: null,
|
||||
},
|
||||
verkauf: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const alerts: Alert[] = [
|
||||
{
|
||||
id: "a1",
|
||||
typ: "ablauf-7",
|
||||
titel: "Subscription läuft in 7 Tagen ab",
|
||||
beschreibung: "pve-brandl-01 · Brandl Elektrotechnik GmbH · 24.09.2026",
|
||||
schwere: "gefahr",
|
||||
hostId: "h1",
|
||||
kundeId: "k1",
|
||||
laufzeitId: "l2",
|
||||
seit: "2026-09-17T06:00:00Z",
|
||||
erledigt: false,
|
||||
},
|
||||
{
|
||||
id: "a2",
|
||||
typ: "status",
|
||||
titel: "Subscription abgelaufen",
|
||||
beschreibung: "pve-lehner-01 · Lehner Steuerberatung · Status expired seit 30.08.2026",
|
||||
schwere: "gefahr",
|
||||
hostId: "h7",
|
||||
kundeId: "k3",
|
||||
laufzeitId: null,
|
||||
seit: "2026-08-30T04:00:00Z",
|
||||
erledigt: false,
|
||||
},
|
||||
{
|
||||
id: "a3",
|
||||
typ: "sockets",
|
||||
titel: "Lizenzierte Sockets weichen ab",
|
||||
beschreibung: "pve-huber-01 · 1 lizenziert, 2 physisch vorhanden",
|
||||
schwere: "warnung",
|
||||
hostId: "h4",
|
||||
kundeId: "k2",
|
||||
laufzeitId: null,
|
||||
seit: "2026-09-02T03:00:00Z",
|
||||
erledigt: false,
|
||||
},
|
||||
{
|
||||
id: "a4",
|
||||
typ: "keine-meldung",
|
||||
titel: "Host meldet sich nicht",
|
||||
beschreibung: "pve-nord-01 · letzte Meldung am 14.09.2026",
|
||||
schwere: "warnung",
|
||||
hostId: "h8",
|
||||
kundeId: "k4",
|
||||
laufzeitId: null,
|
||||
seit: "2026-09-16T03:12:00Z",
|
||||
erledigt: false,
|
||||
},
|
||||
{
|
||||
id: "a5",
|
||||
typ: "ohne-kunde",
|
||||
titel: "Host ist keinem Kunden zugewiesen",
|
||||
beschreibung: "pve-demo-neu · seit heute registriert",
|
||||
schwere: "info",
|
||||
hostId: "h9",
|
||||
kundeId: null,
|
||||
laufzeitId: null,
|
||||
seit: "2026-09-17T06:05:00Z",
|
||||
erledigt: false,
|
||||
},
|
||||
{
|
||||
id: "a6",
|
||||
typ: "rechnung-offen",
|
||||
titel: "Proxmox-Rechnung fällig",
|
||||
beschreibung: "PX-2026-14790 · 115,00 € · fällig am 24.09.2026",
|
||||
schwere: "warnung",
|
||||
hostId: "h1",
|
||||
kundeId: "k1",
|
||||
laufzeitId: "l2",
|
||||
seit: "2026-09-17T06:00:00Z",
|
||||
erledigt: false,
|
||||
},
|
||||
{
|
||||
id: "a7",
|
||||
typ: "kundenrechnung-fehlt",
|
||||
titel: "Kundenrechnung noch nicht erstellt",
|
||||
beschreibung: "pve-nord-01 · Systemhaus Nord GmbH · Einkauf bezahlt am 19.01.2026",
|
||||
schwere: "warnung",
|
||||
hostId: "h8",
|
||||
kundeId: "k4",
|
||||
laufzeitId: "l9",
|
||||
seit: "2026-01-19T08:00:00Z",
|
||||
erledigt: false,
|
||||
},
|
||||
{
|
||||
id: "a8",
|
||||
typ: "kundenrechnung-unbezahlt",
|
||||
titel: "Kundenrechnung seit über 30 Tagen offen",
|
||||
beschreibung: "RE-2025-0501 · Huber Logistik AG · 170,00 € netto",
|
||||
schwere: "warnung",
|
||||
hostId: "h4",
|
||||
kundeId: "k2",
|
||||
laufzeitId: "l6",
|
||||
seit: "2026-01-05T08:00:00Z",
|
||||
erledigt: false,
|
||||
},
|
||||
];
|
||||
|
||||
/** Report-Historie (gekürzt) für die Host-Detailansicht. */
|
||||
export const reports: Report[] = hosts.flatMap((host) =>
|
||||
Array.from({ length: 6 }, (_, i) => {
|
||||
const zeitpunkt = new Date(
|
||||
new Date(host.letzterReport ?? HEUTE).getTime() - i * 24 * 60 * 60 * 1000,
|
||||
).toISOString();
|
||||
return {
|
||||
id: `${host.id}-r${i}`,
|
||||
hostId: host.id,
|
||||
zeitpunkt,
|
||||
status: host.subscription.status,
|
||||
key: host.subscription.key,
|
||||
nextduedate: host.subscription.nextduedate,
|
||||
agentVersion: host.agentVersion,
|
||||
paketUpdates: host.paketUpdates === null ? null : Math.max(0, host.paketUpdates - i),
|
||||
};
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,218 @@
|
||||
import { stundenSeit, tageBis } from "@/lib/format";
|
||||
import { HEUTE, alerts, hosts, kunden, laufzeiten, reports } from "./data";
|
||||
import type { Host, Kunde, Laufzeit } from "./types";
|
||||
|
||||
export const MELDUNG_FRIST_STUNDEN = 48;
|
||||
export const ABLAUF_FENSTER_TAGE = 30;
|
||||
|
||||
export function getKunde(id: string | null): Kunde | undefined {
|
||||
if (!id) return undefined;
|
||||
return kunden.find((k) => k.id === id);
|
||||
}
|
||||
|
||||
export function getHost(id: string): Host | undefined {
|
||||
return hosts.find((h) => h.id === id);
|
||||
}
|
||||
|
||||
export function hostsVonKunde(kundeId: string): Host[] {
|
||||
return hosts.filter((h) => h.kundeId === kundeId);
|
||||
}
|
||||
|
||||
export function laufzeitenVonHost(hostId: string): Laufzeit[] {
|
||||
return laufzeiten.filter((l) => l.hostId === hostId).sort((a, b) => b.start.localeCompare(a.start));
|
||||
}
|
||||
|
||||
export function laufzeitenVonKunde(kundeId: string): Laufzeit[] {
|
||||
const hostIds = new Set(hostsVonKunde(kundeId).map((h) => h.id));
|
||||
return laufzeiten.filter((l) => hostIds.has(l.hostId)).sort((a, b) => b.start.localeCompare(a.start));
|
||||
}
|
||||
|
||||
/**
|
||||
* Laufzeit, die zum gemeldeten Ablaufdatum gehört: bevorzugt die Verlängerung, die am
|
||||
* `nextduedate` beginnt, sonst die laufende Laufzeit. Für das Dashboard ist die Verlängerung
|
||||
* der interessante Fall, weil deren Rechnungen noch offen sind.
|
||||
*/
|
||||
export function laufzeitZuAblauf(host: Host, jetzt: Date = HEUTE): Laufzeit | undefined {
|
||||
const eigene = laufzeitenVonHost(host.id).filter((l) => !l.vorschlag);
|
||||
const ablauf = host.subscription.nextduedate?.slice(0, 10);
|
||||
return eigene.find((l) => l.start === ablauf) ?? aktuelleLaufzeit(host.id, jetzt);
|
||||
}
|
||||
|
||||
/** Laufende bzw. zuletzt gekaufte Laufzeit eines Hosts. */
|
||||
export function aktuelleLaufzeit(hostId: string, jetzt: Date = HEUTE): Laufzeit | undefined {
|
||||
const eigene = laufzeitenVonHost(hostId).filter((l) => !l.vorschlag);
|
||||
const heuteIso = jetzt.toISOString().slice(0, 10);
|
||||
return eigene.find((l) => l.start <= heuteIso && l.ende >= heuteIso) ?? eigene[0];
|
||||
}
|
||||
|
||||
export function reportsVonHost(hostId: string) {
|
||||
return reports.filter((r) => r.hostId === hostId).sort((a, b) => b.zeitpunkt.localeCompare(a.zeitpunkt));
|
||||
}
|
||||
|
||||
export function alertsVonHost(hostId: string) {
|
||||
return alerts.filter((a) => a.hostId === hostId && !a.erledigt);
|
||||
}
|
||||
|
||||
export function alertsVonKunde(kundeId: string) {
|
||||
return alerts.filter((a) => a.kundeId === kundeId && !a.erledigt);
|
||||
}
|
||||
|
||||
export function offeneAlerts() {
|
||||
return alerts.filter((a) => !a.erledigt);
|
||||
}
|
||||
|
||||
export function hostMeldetNicht(host: Host, jetzt: Date = HEUTE): boolean {
|
||||
const stunden = stundenSeit(host.letzterReport, jetzt);
|
||||
return stunden === null || stunden > MELDUNG_FRIST_STUNDEN;
|
||||
}
|
||||
|
||||
export function nichtZugeordneteHosts(): Host[] {
|
||||
return hosts.filter((h) => h.kundeId === null);
|
||||
}
|
||||
|
||||
/** Offene Punkte einer Laufzeit: Proxmox-Rechnung unbezahlt bzw. Kundenrechnung fehlt. */
|
||||
export function offenePunkte(laufzeit: Laufzeit): { einkaufOffen: boolean; verkaufOffen: boolean } {
|
||||
const einkaufOffen = laufzeit.einkauf !== null && !laufzeit.einkauf.bezahlt;
|
||||
const verkaufOffen = laufzeit.verkauf === null || !laufzeit.verkauf.erstellt;
|
||||
return { einkaufOffen, verkaufOffen };
|
||||
}
|
||||
|
||||
export function istAbgeschlossen(laufzeit: Laufzeit): boolean {
|
||||
const { einkaufOffen, verkaufOffen } = offenePunkte(laufzeit);
|
||||
return !einkaufOffen && !verkaufOffen;
|
||||
}
|
||||
|
||||
/** Laufzeiten, für die noch etwas zu tun ist (Vorschläge zählen ebenfalls). */
|
||||
export function offeneRechnungsLaufzeiten(): Laufzeit[] {
|
||||
return laufzeiten.filter((l) => !l.vorschlag && !istAbgeschlossen(l));
|
||||
}
|
||||
|
||||
export function dashboardKennzahlen(jetzt: Date = HEUTE) {
|
||||
const aktiv = hosts.filter((h) => h.subscription.status === "active");
|
||||
const bald = hosts.filter((h) => {
|
||||
const tage = tageBis(h.subscription.nextduedate, jetzt);
|
||||
return tage !== null && tage >= 0 && tage <= ABLAUF_FENSTER_TAGE;
|
||||
});
|
||||
const offeneEinkaufsrechnungen = laufzeiten.filter((l) => offenePunkte(l).einkaufOffen);
|
||||
const fehlendeKundenrechnungen = laufzeiten.filter(
|
||||
(l) => !l.vorschlag && l.einkauf !== null && offenePunkte(l).verkaufOffen,
|
||||
);
|
||||
|
||||
return {
|
||||
hostsGesamt: hosts.length,
|
||||
aktivLizenziert: aktiv.length,
|
||||
ohneSubscription: hosts.filter((h) => h.subscription.status !== "active").length,
|
||||
baldAblaufend: bald.length,
|
||||
ohneMeldung: hosts.filter((h) => hostMeldetNicht(h, jetzt)).length,
|
||||
ohneKunde: nichtZugeordneteHosts().length,
|
||||
offeneEinkaufsrechnungen: offeneEinkaufsrechnungen.length,
|
||||
fehlendeKundenrechnungen: fehlendeKundenrechnungen.length,
|
||||
offeneRechnungspunkte: offeneEinkaufsrechnungen.length + fehlendeKundenrechnungen.length,
|
||||
offeneAlerts: offeneAlerts().length,
|
||||
};
|
||||
}
|
||||
|
||||
/** Hosts nach Ablaufdatum der Subscription, ohne Hosts ganz ohne Subscription. */
|
||||
export function hostsNachAblauf(): Host[] {
|
||||
return [...hosts]
|
||||
.filter((h) => h.subscription.nextduedate !== null)
|
||||
.sort((a, b) => (a.subscription.nextduedate ?? "").localeCompare(b.subscription.nextduedate ?? ""));
|
||||
}
|
||||
|
||||
export function summenVonKunde(kundeId: string) {
|
||||
const eigene = laufzeitenVonKunde(kundeId);
|
||||
const einkauf = eigene.reduce((s, l) => s + (l.einkauf?.nettoBetrag ?? 0), 0);
|
||||
const verkauf = eigene.reduce((s, l) => s + (l.verkauf?.nettoBetrag ?? 0), 0);
|
||||
return { einkauf, verkauf, marge: verkauf - einkauf };
|
||||
}
|
||||
|
||||
export function summenProJahr(laufzeitenListe: Laufzeit[]) {
|
||||
const proJahr = new Map<string, { einkauf: number; verkauf: number }>();
|
||||
for (const l of laufzeitenListe) {
|
||||
const jahr = l.start.slice(0, 4);
|
||||
const eintrag = proJahr.get(jahr) ?? { einkauf: 0, verkauf: 0 };
|
||||
eintrag.einkauf += l.einkauf?.nettoBetrag ?? 0;
|
||||
eintrag.verkauf += l.verkauf?.nettoBetrag ?? 0;
|
||||
proJahr.set(jahr, eintrag);
|
||||
}
|
||||
return [...proJahr.entries()]
|
||||
.sort((a, b) => b[0].localeCompare(a[0]))
|
||||
.map(([jahr, werte]) => ({ jahr, ...werte, marge: werte.verkauf - werte.einkauf }));
|
||||
}
|
||||
|
||||
/** Fortschritt „erledigte Rechnungspunkte“ je Kunde für die Dashboard-Card. */
|
||||
export function rechnungsFortschrittProKunde() {
|
||||
return kunden.map((kunde) => {
|
||||
const eigene = laufzeitenVonKunde(kunde.id).filter((l) => !l.vorschlag);
|
||||
const gesamt = eigene.length * 2;
|
||||
const erledigt = eigene.reduce((summe, l) => {
|
||||
const { einkaufOffen, verkaufOffen } = offenePunkte(l);
|
||||
return summe + (einkaufOffen ? 0 : 1) + (verkaufOffen ? 0 : 1);
|
||||
}, 0);
|
||||
return {
|
||||
kunde,
|
||||
gesamt,
|
||||
erledigt,
|
||||
prozent: gesamt === 0 ? 100 : Math.round((erledigt / gesamt) * 100),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function kundenUebersicht(jetzt: Date = HEUTE) {
|
||||
return kunden.map((kunde) => {
|
||||
const eigeneHosts = hostsVonKunde(kunde.id);
|
||||
const naechsterAblauf = eigeneHosts
|
||||
.map((h) => h.subscription.nextduedate)
|
||||
.filter((d): d is string => d !== null)
|
||||
.sort()[0];
|
||||
const eigeneLaufzeiten = laufzeitenVonKunde(kunde.id).filter((l) => !l.vorschlag);
|
||||
const offen = eigeneLaufzeiten.filter((l) => !istAbgeschlossen(l)).length;
|
||||
return {
|
||||
kunde,
|
||||
hosts: eigeneHosts.length,
|
||||
aktiv: eigeneHosts.filter((h) => h.subscription.status === "active").length,
|
||||
naechsterAblauf: naechsterAblauf ?? null,
|
||||
tageBisAblauf: tageBis(naechsterAblauf ?? null, jetzt),
|
||||
offeneAlerts: alertsVonKunde(kunde.id).length,
|
||||
offeneRechnungspunkte: offen,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** Einfache Volltextsuche über Hosts, Kunden und Rechnungsnummern (Strg+K). */
|
||||
export function suche(begriff: string) {
|
||||
const q = begriff.trim().toLowerCase();
|
||||
if (q.length < 2) return { hosts: [], kunden: [], rechnungen: [] };
|
||||
|
||||
const trefferHosts = hosts.filter((h) =>
|
||||
[h.hostname, h.fqdn, h.serverId, h.subscription.key, ...h.ips]
|
||||
.filter(Boolean)
|
||||
.some((wert) => String(wert).toLowerCase().includes(q)),
|
||||
);
|
||||
const trefferKunden = kunden.filter((k) =>
|
||||
[k.name, k.kundennummer, k.ansprechpartner, k.email]
|
||||
.filter(Boolean)
|
||||
.some((wert) => String(wert).toLowerCase().includes(q)),
|
||||
);
|
||||
const trefferRechnungen = laufzeiten.filter((l) =>
|
||||
[l.einkauf?.nummer, l.verkauf?.nummer]
|
||||
.filter(Boolean)
|
||||
.some((wert) => String(wert).toLowerCase().includes(q)),
|
||||
);
|
||||
|
||||
return { hosts: trefferHosts, kunden: trefferKunden, rechnungen: trefferRechnungen };
|
||||
}
|
||||
|
||||
export const PRODUKT_NAME: Record<Host["produkt"], string> = {
|
||||
pve: "Proxmox VE",
|
||||
pbs: "Proxmox Backup Server",
|
||||
pmg: "Proxmox Mail Gateway",
|
||||
};
|
||||
|
||||
export const STATUS_TEXT: Record<Host["subscription"]["status"], string> = {
|
||||
active: "Aktiv",
|
||||
notfound: "Keine Subscription",
|
||||
invalid: "Ungültig",
|
||||
expired: "Abgelaufen",
|
||||
suspended: "Ausgesetzt",
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import { hosts, kunden, laufzeiten } from "./data";
|
||||
import { PRODUKT_NAME, getKunde } from "./queries";
|
||||
|
||||
export interface SuchEintrag {
|
||||
typ: "Host" | "Kunde" | "Rechnung";
|
||||
titel: string;
|
||||
untertitel: string;
|
||||
href: string;
|
||||
/** Alle durchsuchbaren Werte in Kleinschreibung. */
|
||||
begriffe: string[];
|
||||
}
|
||||
|
||||
/** Flacher Suchindex für die globale Suche (Strg+K) im Prototyp. */
|
||||
export function suchIndex(): SuchEintrag[] {
|
||||
const eintraege: SuchEintrag[] = [];
|
||||
|
||||
for (const host of hosts) {
|
||||
const kunde = getKunde(host.kundeId);
|
||||
eintraege.push({
|
||||
typ: "Host",
|
||||
titel: host.hostname,
|
||||
untertitel: `${PRODUKT_NAME[host.produkt]} · ${kunde?.name ?? "Nicht zugeordnet"}${
|
||||
host.subscription.key ? ` · ${host.subscription.key}` : ""
|
||||
}`,
|
||||
href: `/hosts/${host.id}`,
|
||||
begriffe: [host.hostname, host.fqdn, host.serverId, host.subscription.key, ...host.ips]
|
||||
.filter((w): w is string => Boolean(w))
|
||||
.map((w) => w.toLowerCase()),
|
||||
});
|
||||
}
|
||||
|
||||
for (const kunde of kunden) {
|
||||
eintraege.push({
|
||||
typ: "Kunde",
|
||||
titel: kunde.name,
|
||||
untertitel: `${kunde.kundennummer}${kunde.ansprechpartner ? ` · ${kunde.ansprechpartner}` : ""}`,
|
||||
href: `/kunden/${kunde.id}`,
|
||||
begriffe: [kunde.name, kunde.kundennummer, kunde.ansprechpartner, kunde.email]
|
||||
.filter((w): w is string => Boolean(w))
|
||||
.map((w) => w.toLowerCase()),
|
||||
});
|
||||
}
|
||||
|
||||
for (const laufzeit of laufzeiten) {
|
||||
const host = hosts.find((h) => h.id === laufzeit.hostId);
|
||||
const kunde = getKunde(host?.kundeId ?? null);
|
||||
for (const [art, rechnung] of [
|
||||
["Proxmox-Rechnung", laufzeit.einkauf],
|
||||
["Kundenrechnung", laufzeit.verkauf],
|
||||
] as const) {
|
||||
if (!rechnung?.nummer) continue;
|
||||
eintraege.push({
|
||||
typ: "Rechnung",
|
||||
titel: rechnung.nummer,
|
||||
untertitel: `${art} · ${host?.hostname ?? "?"} · ${kunde?.name ?? "Nicht zugeordnet"}`,
|
||||
href: `/hosts/${laufzeit.hostId}`,
|
||||
begriffe: [rechnung.nummer.toLowerCase()],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return eintraege;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Typen für die Beispieldaten des Prototyps (#002).
|
||||
* Sie bilden das geplante Datenmodell (docs/architektur.md) vereinfacht ab und werden
|
||||
* in #003 durch die von Prisma erzeugten Typen ersetzt.
|
||||
*/
|
||||
|
||||
export type Produkt = "pve" | "pbs" | "pmg";
|
||||
|
||||
export type SubscriptionStatus = "active" | "notfound" | "invalid" | "expired" | "suspended";
|
||||
|
||||
export type AlertSchwere = "info" | "warnung" | "gefahr";
|
||||
|
||||
export interface Kunde {
|
||||
id: string;
|
||||
name: string;
|
||||
kundennummer: string;
|
||||
ansprechpartner: string | null;
|
||||
email: string | null;
|
||||
telefon: string | null;
|
||||
notizen: string | null;
|
||||
standorte: Standort[];
|
||||
}
|
||||
|
||||
export interface Standort {
|
||||
id: string;
|
||||
kundeId: string;
|
||||
name: string;
|
||||
ort: string | null;
|
||||
}
|
||||
|
||||
export interface Host {
|
||||
id: string;
|
||||
hostname: string;
|
||||
fqdn: string | null;
|
||||
kundeId: string | null;
|
||||
standortId: string | null;
|
||||
produkt: Produkt;
|
||||
produktVersion: string | null;
|
||||
kernel: string | null;
|
||||
serverId: string;
|
||||
clusterName: string | null;
|
||||
nodeName: string | null;
|
||||
ips: string[];
|
||||
sockets: number | null;
|
||||
cpuModell: string | null;
|
||||
uptimeSekunden: number | null;
|
||||
paketUpdates: number | null;
|
||||
agentVersion: string;
|
||||
letzterReport: string | null;
|
||||
subscription: {
|
||||
key: string | null;
|
||||
status: SubscriptionStatus;
|
||||
level: string | null;
|
||||
produktname: string | null;
|
||||
lizenzierteSockets: number | null;
|
||||
regdate: string | null;
|
||||
nextduedate: string | null;
|
||||
checktime: string | null;
|
||||
};
|
||||
tags: string[];
|
||||
notizen: string | null;
|
||||
}
|
||||
|
||||
export interface Rechnung {
|
||||
nummer: string | null;
|
||||
datum: string | null;
|
||||
faelligAm?: string | null;
|
||||
nettoBetrag: number | null;
|
||||
ustSatz: number;
|
||||
rabatt?: number | null;
|
||||
zahlungsart?: string | null;
|
||||
bezahlt: boolean;
|
||||
bezahltAm: string | null;
|
||||
pdfDateiname: string | null;
|
||||
}
|
||||
|
||||
export interface Laufzeit {
|
||||
id: string;
|
||||
hostId: string;
|
||||
key: string;
|
||||
start: string;
|
||||
ende: string;
|
||||
/** Vom Agent vorgeschlagen und noch nicht bestätigt. */
|
||||
vorschlag: boolean;
|
||||
/** Proxmox-Rechnung (Einkauf). */
|
||||
einkauf: Rechnung | null;
|
||||
/** Kundenrechnung (Verkauf); `erstellt = false`, solange sie noch fehlt. */
|
||||
verkauf: (Rechnung & { erstellt: boolean }) | null;
|
||||
}
|
||||
|
||||
export interface Alert {
|
||||
id: string;
|
||||
typ: string;
|
||||
titel: string;
|
||||
beschreibung: string;
|
||||
schwere: AlertSchwere;
|
||||
hostId: string | null;
|
||||
kundeId: string | null;
|
||||
laufzeitId: string | null;
|
||||
seit: string;
|
||||
erledigt: boolean;
|
||||
}
|
||||
|
||||
export interface Report {
|
||||
id: string;
|
||||
hostId: string;
|
||||
zeitpunkt: string;
|
||||
status: SubscriptionStatus;
|
||||
key: string | null;
|
||||
nextduedate: string | null;
|
||||
agentVersion: string;
|
||||
paketUpdates: number | null;
|
||||
}
|
||||
Reference in New Issue
Block a user