feat(server): Standardwerte für Node, Storage und Bridge
Der Server kannte die Standards schon für kurze CLI-Befehle, aber nichts setzte sie. Jetzt im Server-Formular (Auswahl aus dem Verbindungstest) und als Vorbelegung im Assistenten. Closes #023 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,10 +8,12 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
|
|||||||
## [Unveröffentlicht]
|
## [Unveröffentlicht]
|
||||||
|
|
||||||
### Hinzugefügt
|
### Hinzugefügt
|
||||||
|
- Standardwerte je Server (Node, Storage, Bridge) im Server-Formular; der Assistent belegt sie vor, die CLI nutzt sie für kurze Befehle (#023)
|
||||||
|
|
||||||
### Geändert
|
### Geändert
|
||||||
|
|
||||||
### Behoben
|
### Behoben
|
||||||
|
- Beim Wechsel des Servers im Assistenten wurde die erste Bridge statt vmbr0 vorbelegt (#023)
|
||||||
|
|
||||||
### Entfernt
|
### Entfernt
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# #023 Standardwerte je Server
|
||||||
|
**Status:** erledigt
|
||||||
|
**Meilenstein:** v0.7.0
|
||||||
|
**Labels:** feature
|
||||||
|
**Geschätzt:** 1 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Die Vorgabe nennt unter „Einstellungen“ Standardwerte. Der Server kannte sie schon (die CLI füllt
|
||||||
|
damit kurze Befehle), aber es gab keine Oberfläche dafür.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [x] `standards` (Node, Storage, Bridge) im Server-Schema und in der API, leere Felder fallen weg
|
||||||
|
- [x] Server-Formular: Auswahl aus dem letzten Verbindungstest, „automatisch“ möglich; ein nicht mehr bekannter Wert bleibt sichtbar
|
||||||
|
- [x] Assistent belegt Node, Storage und Bridge daraus vor – auch beim Wechsel von Server und Node (vorher nahm der Server-Wechsel die erste statt vmbr0)
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [x] Speichern und Leeren getestet (`tests/server.test.ts`); im Browser bei 390 px geprüft
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#003, #013
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
|
- Persönliche Standardwerte (`Benutzer.standards`) bleiben vorerst ungenutzt – die Werte hängen am Server, nicht an der Person.
|
||||||
@@ -26,3 +26,4 @@ Details und Architektur: [../architektur.md](../architektur.md). Zeiten werden a
|
|||||||
| [#020](020-feldtest.md) | Feldtest 8.x und 9.x, Version 1.0.0 | offen | v1.0.0 | 6 h | – |
|
| [#020](020-feldtest.md) | Feldtest 8.x und 9.x, Version 1.0.0 | offen | v1.0.0 | 6 h | – |
|
||||||
| [#021](021-template-bau-ohne-ssh.md) | Template-Bau ohne SSH (Bau-Adresse) | erledigt | v0.3.0 | 3 h | 0 h 54 min |
|
| [#021](021-template-bau-ohne-ssh.md) | Template-Bau ohne SSH (Bau-Adresse) | erledigt | v0.3.0 | 3 h | 0 h 54 min |
|
||||||
| [#022](022-template-profile.md) | Templates mit eingebackenen Profilen | erledigt | v0.3.0 | 4 h | 0 h 22 min |
|
| [#022](022-template-profile.md) | Templates mit eingebackenen Profilen | erledigt | v0.3.0 | 4 h | 0 h 22 min |
|
||||||
|
| [#023](023-standardwerte.md) | Standardwerte je Server | erledigt | v0.7.0 | 1 h | – |
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ export type AssistentDaten = {
|
|||||||
bridges: Array<{ name: string; vlan: boolean }>;
|
bridges: Array<{ name: string; vlan: boolean }>;
|
||||||
}>;
|
}>;
|
||||||
templates: Array<{ distribution: string; knoten: string; vmid: number }>;
|
templates: Array<{ distribution: string; knoten: string; vmid: number }>;
|
||||||
|
/** Standardwerte aus den Server-Einstellungen. */
|
||||||
|
standards: { knoten?: string; storage?: string; bridge?: string } | null;
|
||||||
}>;
|
}>;
|
||||||
distributionen: Array<{ id: string; name: string; familie: string; vm: boolean; lxc: boolean }>;
|
distributionen: Array<{ id: string; name: string; familie: string; vm: boolean; lxc: boolean }>;
|
||||||
profile: Array<{
|
profile: Array<{
|
||||||
@@ -74,6 +76,17 @@ type Plan = {
|
|||||||
ok: boolean;
|
ok: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Node, Storage und Bridge vorbelegen – Standards des Servers zuerst, sonst das Erste, was passt. */
|
||||||
|
function vorbelegung(s: AssistentDaten["server"][number], knotenName?: string) {
|
||||||
|
const std = s.standards ?? {};
|
||||||
|
const k = s.knoten.find((x) => x.name === (knotenName || std.knoten)) ?? s.knoten[0];
|
||||||
|
const vmStorages = k?.storages.filter((x) => x.vm) ?? [];
|
||||||
|
const storage = (vmStorages.find((x) => x.name === std.storage) ?? vmStorages[0])?.name ?? "";
|
||||||
|
const bridge =
|
||||||
|
[std.bridge, "vmbr0"].find((b) => b && k?.bridges.some((x) => x.name === b)) ?? k?.bridges[0]?.name ?? "";
|
||||||
|
return { knoten: k?.name ?? "", storage, bridge };
|
||||||
|
}
|
||||||
|
|
||||||
// ── Zustand ─────────────────────────────────────────────────────────────────────────────────────
|
// ── Zustand ─────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
type Werte = {
|
type Werte = {
|
||||||
@@ -295,10 +308,10 @@ export function Assistent({
|
|||||||
|
|
||||||
const anfang = useMemo<Werte>(() => {
|
const anfang = useMemo<Werte>(() => {
|
||||||
const server = daten.server.find((s) => s.name === vorauswahl) ?? daten.server[0]!;
|
const server = daten.server.find((s) => s.name === vorauswahl) ?? daten.server[0]!;
|
||||||
const knoten = server.knoten[0];
|
const vor = vorbelegung(server);
|
||||||
return {
|
return {
|
||||||
server: server.name,
|
server: server.name,
|
||||||
knoten: knoten?.name ?? "",
|
knoten: vor.knoten,
|
||||||
typ: "vm",
|
typ: "vm",
|
||||||
distribution:
|
distribution:
|
||||||
daten.distributionen.find((d) => d.id === "debian-13")?.id ?? daten.distributionen[0]?.id ?? "",
|
daten.distributionen.find((d) => d.id === "debian-13")?.id ?? daten.distributionen[0]?.id ?? "",
|
||||||
@@ -308,11 +321,11 @@ export function Assistent({
|
|||||||
cores: "2",
|
cores: "2",
|
||||||
speicherMb: "2048",
|
speicherMb: "2048",
|
||||||
diskGb: "20",
|
diskGb: "20",
|
||||||
storage: knoten?.storages.find((s) => s.vm)?.name ?? "",
|
storage: vor.storage,
|
||||||
swapMb: "512",
|
swapMb: "512",
|
||||||
unprivilegiert: true,
|
unprivilegiert: true,
|
||||||
nesting: false,
|
nesting: false,
|
||||||
bridge: knoten?.bridges.find((b) => b.name === "vmbr0")?.name ?? knoten?.bridges[0]?.name ?? "",
|
bridge: vor.bridge,
|
||||||
vlan: "",
|
vlan: "",
|
||||||
modus: "dhcp",
|
modus: "dhcp",
|
||||||
ip: "",
|
ip: "",
|
||||||
@@ -554,13 +567,7 @@ export function Assistent({
|
|||||||
value={werte.server}
|
value={werte.server}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const s = daten.server.find((x) => x.name === e.target.value)!;
|
const s = daten.server.find((x) => x.name === e.target.value)!;
|
||||||
const k = s.knoten[0];
|
setze({ server: s.name, ...vorbelegung(s) });
|
||||||
setze({
|
|
||||||
server: s.name,
|
|
||||||
knoten: k?.name ?? "",
|
|
||||||
storage: k?.storages.find((x) => x.vm)?.name ?? "",
|
|
||||||
bridge: k?.bridges[0]?.name ?? "",
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{daten.server.map((s) => (
|
{daten.server.map((s) => (
|
||||||
@@ -580,12 +587,8 @@ export function Assistent({
|
|||||||
className={EINGABE_KLASSE}
|
className={EINGABE_KLASSE}
|
||||||
value={werte.knoten}
|
value={werte.knoten}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const k = server.knoten.find((x) => x.name === e.target.value);
|
const vor = vorbelegung(server, e.target.value);
|
||||||
setze({
|
setze({ knoten: e.target.value, storage: vor.storage, bridge: vor.bridge });
|
||||||
knoten: e.target.value,
|
|
||||||
storage: k?.storages.find((x) => x.vm)?.name ?? werte.storage,
|
|
||||||
bridge: k?.bridges[0]?.name ?? werte.bridge,
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="">automatisch</option>
|
<option value="">automatisch</option>
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export default async function Ausrollen({
|
|||||||
})),
|
})),
|
||||||
bridges: k.bridges.map((b) => ({ name: b.name, vlan: b.vlanBewusst })),
|
bridges: k.bridges.map((b) => ({ name: b.name, vlan: b.vlanBewusst })),
|
||||||
})),
|
})),
|
||||||
|
standards: (s.standards as { knoten?: string; storage?: string; bridge?: string } | null) ?? null,
|
||||||
templates: templates
|
templates: templates
|
||||||
.filter((t) => t.serverId === s.id)
|
.filter((t) => t.serverId === s.id)
|
||||||
.map((t) => ({ distribution: t.distributionId, knoten: t.knoten, vmid: t.vmid })),
|
.map((t) => ({ distribution: t.distributionId, knoten: t.knoten, vmid: t.vmid })),
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ type Werte = {
|
|||||||
bauIp: string;
|
bauIp: string;
|
||||||
bauGateway: string;
|
bauGateway: string;
|
||||||
bauDns: string;
|
bauDns: string;
|
||||||
|
stdKnoten: string;
|
||||||
|
stdStorage: string;
|
||||||
|
stdBridge: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function anfangswerte(server?: ServerSicht): Werte {
|
function anfangswerte(server?: ServerSicht): Werte {
|
||||||
@@ -66,6 +69,9 @@ function anfangswerte(server?: ServerSicht): Werte {
|
|||||||
bauIp: server?.bauNetz?.ip ?? "",
|
bauIp: server?.bauNetz?.ip ?? "",
|
||||||
bauGateway: server?.bauNetz?.gateway ?? "",
|
bauGateway: server?.bauNetz?.gateway ?? "",
|
||||||
bauDns: server?.bauNetz?.dns.join(", ") ?? "",
|
bauDns: server?.bauNetz?.dns.join(", ") ?? "",
|
||||||
|
stdKnoten: server?.standards?.knoten ?? "",
|
||||||
|
stdStorage: server?.standards?.storage ?? "",
|
||||||
|
stdBridge: server?.standards?.bridge ?? "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +129,7 @@ export function ServerFormular({ server }: { server?: ServerSicht }) {
|
|||||||
setSpeichert(true);
|
setSpeichert(true);
|
||||||
setFelder({});
|
setFelder({});
|
||||||
setMeldung(null);
|
setMeldung(null);
|
||||||
const { bauIp, bauGateway, bauDns, ...ohneBau } = werte;
|
const { bauIp, bauGateway, bauDns, stdKnoten, stdStorage, stdBridge, ...ohneBau } = werte;
|
||||||
const rumpf = {
|
const rumpf = {
|
||||||
...ohneBau,
|
...ohneBau,
|
||||||
sshPort: Number(werte.sshPort),
|
sshPort: Number(werte.sshPort),
|
||||||
@@ -133,6 +139,7 @@ export function ServerFormular({ server }: { server?: ServerSicht }) {
|
|||||||
bauNetz: bauIp.trim()
|
bauNetz: bauIp.trim()
|
||||||
? { ip: bauIp.trim(), gateway: bauGateway.trim(), dns: bauDns.split(/[\s,;]+/).filter(Boolean) }
|
? { ip: bauIp.trim(), gateway: bauGateway.trim(), dns: bauDns.split(/[\s,;]+/).filter(Boolean) }
|
||||||
: null,
|
: null,
|
||||||
|
standards: { knoten: stdKnoten, storage: stdStorage, bridge: stdBridge },
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const gespeichert = bearbeiten
|
const gespeichert = bearbeiten
|
||||||
@@ -409,6 +416,8 @@ export function ServerFormular({ server }: { server?: ServerSicht }) {
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
{server?.letzterTest ? <StandardwerteFelder server={server} werte={werte} setze={setze} /> : null}
|
||||||
|
|
||||||
<Schalter
|
<Schalter
|
||||||
id="sshAktiv"
|
id="sshAktiv"
|
||||||
label="SSH zu den Nodes nutzen"
|
label="SSH zu den Nodes nutzen"
|
||||||
@@ -469,3 +478,85 @@ function fehlerText(fehler: unknown): { text: string; hinweis?: string } {
|
|||||||
if (fehler instanceof ApiFehler) return { text: fehler.message, hinweis: fehler.hinweis };
|
if (fehler instanceof ApiFehler) return { text: fehler.message, hinweis: fehler.hinweis };
|
||||||
return { text: "Unerwarteter Fehler." };
|
return { text: "Unerwarteter Fehler." };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standardwerte für Assistent und CLI – Auswahl aus dem letzten Verbindungstest. „Automatisch“:
|
||||||
|
* ProxDeploy nimmt den ersten passenden Node, Storage bzw. vmbr0.
|
||||||
|
*/
|
||||||
|
function StandardwerteFelder({
|
||||||
|
server,
|
||||||
|
werte,
|
||||||
|
setze,
|
||||||
|
}: {
|
||||||
|
server: ServerSicht;
|
||||||
|
werte: Werte;
|
||||||
|
setze: <K extends keyof Werte>(feld: K, wert: Werte[K]) => void;
|
||||||
|
}) {
|
||||||
|
const knotenListe = (server.letzterTest?.knoten ?? []).filter((k) => k.online);
|
||||||
|
const knoten = knotenListe.find((k) => k.name === werte.stdKnoten) ?? knotenListe[0];
|
||||||
|
const storages = (knoten?.storages ?? []).filter(
|
||||||
|
(st) => st.aktiv && (st.inhalte.includes("images") || st.inhalte.includes("rootdir")),
|
||||||
|
);
|
||||||
|
const bridges = knoten?.bridges ?? [];
|
||||||
|
// Ein gespeicherter Wert, den der letzte Test nicht mehr kennt, bleibt sichtbar statt still zu verschwinden.
|
||||||
|
const mitAltem = (liste: string[], wert: string) =>
|
||||||
|
wert && !liste.includes(wert) ? [...liste, wert] : liste;
|
||||||
|
return (
|
||||||
|
<fieldset className="flex flex-col gap-3">
|
||||||
|
<legend className="text-sm font-medium">Standardwerte</legend>
|
||||||
|
<p className="text-muted text-sm">
|
||||||
|
Vorbelegung im Assistenten; die CLI nimmt sie, wenn --node, --storage oder --bridge fehlen.
|
||||||
|
</p>
|
||||||
|
<div className="grid gap-4 sm:grid-cols-3">
|
||||||
|
<Feld id="stdKnoten" label="Node">
|
||||||
|
<select
|
||||||
|
id="stdKnoten"
|
||||||
|
className={EINGABE_KLASSE}
|
||||||
|
value={werte.stdKnoten}
|
||||||
|
onChange={(e) => setze("stdKnoten", e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">automatisch</option>
|
||||||
|
{mitAltem(
|
||||||
|
knotenListe.map((k) => k.name),
|
||||||
|
werte.stdKnoten,
|
||||||
|
).map((n) => (
|
||||||
|
<option key={n}>{n}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</Feld>
|
||||||
|
<Feld id="stdStorage" label="Storage">
|
||||||
|
<select
|
||||||
|
id="stdStorage"
|
||||||
|
className={EINGABE_KLASSE}
|
||||||
|
value={werte.stdStorage}
|
||||||
|
onChange={(e) => setze("stdStorage", e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">automatisch</option>
|
||||||
|
{mitAltem(
|
||||||
|
storages.map((st) => st.storage),
|
||||||
|
werte.stdStorage,
|
||||||
|
).map((n) => (
|
||||||
|
<option key={n}>{n}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</Feld>
|
||||||
|
<Feld id="stdBridge" label="Bridge">
|
||||||
|
<select
|
||||||
|
id="stdBridge"
|
||||||
|
className={EINGABE_KLASSE}
|
||||||
|
value={werte.stdBridge}
|
||||||
|
onChange={(e) => setze("stdBridge", e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">automatisch (vmbr0)</option>
|
||||||
|
{mitAltem(
|
||||||
|
bridges.map((b) => b.name),
|
||||||
|
werte.stdBridge,
|
||||||
|
).map((n) => (
|
||||||
|
<option key={n}>{n}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</Feld>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,6 +59,22 @@ export const BauNetz = z
|
|||||||
|
|
||||||
export type BauNetz = NonNullable<z.infer<typeof BauNetz>>;
|
export type BauNetz = NonNullable<z.infer<typeof BauNetz>>;
|
||||||
|
|
||||||
|
const Auswahl = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.max(64)
|
||||||
|
.optional()
|
||||||
|
.transform((wert) => wert || undefined);
|
||||||
|
|
||||||
|
/** Standardwerte des Servers für Assistent und CLI (leer = ProxDeploy wählt). */
|
||||||
|
export const Standards = z
|
||||||
|
.object({ knoten: Auswahl, storage: Auswahl, bridge: Auswahl })
|
||||||
|
.nullable()
|
||||||
|
.default(null)
|
||||||
|
.transform((s) => (s && (s.knoten || s.storage || s.bridge) ? s : null));
|
||||||
|
|
||||||
|
export type Standards = NonNullable<z.infer<typeof Standards>>;
|
||||||
|
|
||||||
const Vmid = z.coerce.number().int("Ganze Zahl erwartet.").min(100, "Mindestens 100.").max(999_999_999);
|
const Vmid = z.coerce.number().int("Ganze Zahl erwartet.").min(100, "Mindestens 100.").max(999_999_999);
|
||||||
|
|
||||||
const Basis = z.object({
|
const Basis = z.object({
|
||||||
@@ -82,6 +98,7 @@ const Basis = z.object({
|
|||||||
templateVmidBis: Vmid.default(9999),
|
templateVmidBis: Vmid.default(9999),
|
||||||
gastVmidVon: Vmid.default(100),
|
gastVmidVon: Vmid.default(100),
|
||||||
bauNetz: BauNetz,
|
bauNetz: BauNetz,
|
||||||
|
standards: Standards,
|
||||||
});
|
});
|
||||||
|
|
||||||
function bereicheGueltig<T extends { templateVmidVon: number; templateVmidBis: number }>(wert: T) {
|
function bereicheGueltig<T extends { templateVmidVon: number; templateVmidBis: number }>(wert: T) {
|
||||||
@@ -143,6 +160,7 @@ export type ServerSicht = {
|
|||||||
templateVmidBis: number;
|
templateVmidBis: number;
|
||||||
gastVmidVon: number;
|
gastVmidVon: number;
|
||||||
bauNetz: BauNetz | null;
|
bauNetz: BauNetz | null;
|
||||||
|
standards: Standards | null;
|
||||||
erreichbar: boolean | null;
|
erreichbar: boolean | null;
|
||||||
pveVersion: string | null;
|
pveVersion: string | null;
|
||||||
geprueftAm: string | null;
|
geprueftAm: string | null;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
ServerAnlegen,
|
ServerAnlegen,
|
||||||
ServerPruefen,
|
ServerPruefen,
|
||||||
type BauNetz,
|
type BauNetz,
|
||||||
|
type Standards,
|
||||||
type ServerSicht,
|
type ServerSicht,
|
||||||
} from "@/lib/schemas/server";
|
} from "@/lib/schemas/server";
|
||||||
import { AnwendungsFehler, nichtGefunden } from "@/server/fehler";
|
import { AnwendungsFehler, nichtGefunden } from "@/server/fehler";
|
||||||
@@ -34,6 +35,7 @@ export function serverSicht(server: Server): ServerSicht {
|
|||||||
templateVmidBis: server.templateVmidBis,
|
templateVmidBis: server.templateVmidBis,
|
||||||
gastVmidVon: server.gastVmidVon,
|
gastVmidVon: server.gastVmidVon,
|
||||||
bauNetz: (server.bauNetz as BauNetz | null) ?? null,
|
bauNetz: (server.bauNetz as BauNetz | null) ?? null,
|
||||||
|
standards: (server.standards as Standards | null) ?? null,
|
||||||
erreichbar: server.erreichbar,
|
erreichbar: server.erreichbar,
|
||||||
pveVersion: server.pveVersion,
|
pveVersion: server.pveVersion,
|
||||||
geprueftAm: server.geprueftAm?.toISOString() ?? null,
|
geprueftAm: server.geprueftAm?.toISOString() ?? null,
|
||||||
@@ -86,6 +88,7 @@ export async function serverAnlegen(
|
|||||||
data: {
|
data: {
|
||||||
...eingabe,
|
...eingabe,
|
||||||
bauNetz: eingabe.bauNetz ?? Prisma.JsonNull,
|
bauNetz: eingabe.bauNetz ?? Prisma.JsonNull,
|
||||||
|
standards: eingabe.standards ?? Prisma.JsonNull,
|
||||||
fingerabdruck: fingerabdruckOderFehler(eingabe.fingerabdruck),
|
fingerabdruck: fingerabdruckOderFehler(eingabe.fingerabdruck),
|
||||||
tokenGeheimnis: verschluesseln(eingabe.tokenGeheimnis),
|
tokenGeheimnis: verschluesseln(eingabe.tokenGeheimnis),
|
||||||
},
|
},
|
||||||
@@ -121,6 +124,7 @@ export async function serverAendern(
|
|||||||
const daten = {
|
const daten = {
|
||||||
...rest,
|
...rest,
|
||||||
bauNetz: rest.bauNetz ?? Prisma.JsonNull,
|
bauNetz: rest.bauNetz ?? Prisma.JsonNull,
|
||||||
|
standards: rest.standards ?? Prisma.JsonNull,
|
||||||
fingerabdruck: fingerabdruckOderFehler(eingabe.fingerabdruck),
|
fingerabdruck: fingerabdruckOderFehler(eingabe.fingerabdruck),
|
||||||
...(tokenGeheimnis ? { tokenGeheimnis: verschluesseln(tokenGeheimnis) } : {}),
|
...(tokenGeheimnis ? { tokenGeheimnis: verschluesseln(tokenGeheimnis) } : {}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -130,6 +130,27 @@ describe.runIf(datenbankVerfuegbar)("Server-Verwaltung", () => {
|
|||||||
expect(alles).not.toContain(MOCK_TOKEN_GEHEIMNIS);
|
expect(alles).not.toContain(MOCK_TOKEN_GEHEIMNIS);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Standardwerte: gespeichert, leere Felder fallen weg, ganz leer ist null", async () => {
|
||||||
|
await liste.POST(anfrage("POST", "/api/v1/server", admin, eingabe()));
|
||||||
|
const setzen = async (standards: unknown) =>
|
||||||
|
await (
|
||||||
|
await einzeln.PATCH(
|
||||||
|
anfrage("PATCH", "/api/v1/server/rz1", admin, eingabe({ tokenGeheimnis: "", standards })),
|
||||||
|
kontext({ name: "rz1" }),
|
||||||
|
)
|
||||||
|
).json();
|
||||||
|
expect((await setzen({ knoten: "pve01", storage: " local-zfs ", bridge: "" })).standards).toEqual({
|
||||||
|
knoten: "pve01",
|
||||||
|
storage: "local-zfs",
|
||||||
|
});
|
||||||
|
expect((await testPrisma.server.findFirstOrThrow()).standards).toEqual({
|
||||||
|
knoten: "pve01",
|
||||||
|
storage: "local-zfs",
|
||||||
|
});
|
||||||
|
expect((await setzen({ knoten: "", storage: "", bridge: "" })).standards).toBeNull();
|
||||||
|
expect((await testPrisma.server.findFirstOrThrow()).standards).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it("löscht und protokolliert", async () => {
|
it("löscht und protokolliert", async () => {
|
||||||
await liste.POST(anfrage("POST", "/api/v1/server", admin, eingabe()));
|
await liste.POST(anfrage("POST", "/api/v1/server", admin, eingabe()));
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
Reference in New Issue
Block a user