feat(vorlagen): Vorlagen (Presets) für Assistent, API und CLI
Gespeicherte Kombinationen aus Distribution, Ressourcen und Profilen. Im Assistenten mit einem Klick vorbelegt oder dort gespeichert; die API nimmt „vorlage“ entgegen (Grundlage für --preset der CLI). Eine Vorlage liegt unter der Anfrage – was die Anfrage selbst sagt, gewinnt. Beim ersten Start wird das Beispiel „Docker-Host klein“ aus der Vorgabe angelegt, einmalig. Closes #012 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,8 @@ import { profileAufloesen, type AufgeloestesProfil } from "@/engine/profile/aufl
|
||||
import { ProxmoxClient, type Aufruf } from "@/engine/proxmox/client";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { AusrollAnfrage, cliBefehl } from "@/lib/schemas/ausrollen";
|
||||
import { vorlageAnwenden } from "@/lib/schemas/vorlage";
|
||||
import { vorlageHolen } from "@/server/services/vorlagen";
|
||||
import type { Aufrufer } from "@/server/auth/zugang";
|
||||
import { AnwendungsFehler } from "@/server/fehler";
|
||||
import { auftragAnlegen } from "@/server/services/auftraege";
|
||||
@@ -29,7 +31,14 @@ export type Vorbereitet = {
|
||||
};
|
||||
|
||||
export async function ausrollenVorbereiten(roh: unknown): Promise<Vorbereitet> {
|
||||
const anfrage = AusrollAnfrage.parse(roh);
|
||||
// Eine Vorlage (Preset) liegt unter der Anfrage: was die Anfrage selbst sagt, gewinnt.
|
||||
const vorlageName =
|
||||
roh && typeof roh === "object" && "vorlage" in roh ? (roh as { vorlage?: unknown }).vorlage : undefined;
|
||||
const mitVorlage =
|
||||
typeof vorlageName === "string" && vorlageName
|
||||
? vorlageAnwenden((await vorlageHolen(vorlageName)).parameter, roh as Record<string, unknown>)
|
||||
: roh;
|
||||
const anfrage = AusrollAnfrage.parse(mitVorlage);
|
||||
const server = await serverHolen(anfrage.server);
|
||||
const zeile = await prisma.distribution.findUnique({ where: { id: anfrage.distribution } });
|
||||
if (!zeile || zeile.entferntAm || !zeile.aktiv) {
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import type { Prisma } from "@/generated/prisma/client";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { VorlageEingabe, VorlagenParameter } from "@/lib/schemas/vorlage";
|
||||
import type { Aufrufer } from "@/server/auth/zugang";
|
||||
import { AnwendungsFehler, nichtGefunden } from "@/server/fehler";
|
||||
import { protokollieren } from "@/server/services/protokoll";
|
||||
|
||||
export type VorlageSicht = {
|
||||
name: string;
|
||||
titel: string;
|
||||
beschreibung: string | null;
|
||||
parameter: VorlagenParameter;
|
||||
geaendertAm: string;
|
||||
};
|
||||
|
||||
const sicht = (v: {
|
||||
name: string;
|
||||
titel: string;
|
||||
beschreibung: string | null;
|
||||
parameter: unknown;
|
||||
geaendertAm: Date;
|
||||
}): VorlageSicht => ({
|
||||
name: v.name,
|
||||
titel: v.titel,
|
||||
beschreibung: v.beschreibung,
|
||||
parameter: v.parameter as VorlagenParameter,
|
||||
geaendertAm: v.geaendertAm.toISOString(),
|
||||
});
|
||||
|
||||
export async function vorlagenListe(): Promise<VorlageSicht[]> {
|
||||
return (await prisma.vorlage.findMany({ orderBy: { titel: "asc" } })).map(sicht);
|
||||
}
|
||||
|
||||
export async function vorlageHolen(name: string): Promise<VorlageSicht> {
|
||||
const v = await prisma.vorlage.findUnique({ where: { name } });
|
||||
if (!v) throw nichtGefunden(`Die Vorlage „${name}“`);
|
||||
return sicht(v);
|
||||
}
|
||||
|
||||
export async function vorlageSpeichern(
|
||||
roh: unknown,
|
||||
aufrufer: Aufrufer,
|
||||
ip: string | null,
|
||||
bisher?: string,
|
||||
): Promise<VorlageSicht> {
|
||||
const eingabe = VorlageEingabe.parse(roh);
|
||||
if (bisher && bisher !== eingabe.name) {
|
||||
throw new AnwendungsFehler(400, "ungueltig", "Der Kurzname einer Vorlage lässt sich nicht ändern.");
|
||||
}
|
||||
const vorhanden = await prisma.vorlage.findUnique({ where: { name: eingabe.name } });
|
||||
if (vorhanden && !bisher)
|
||||
throw new AnwendungsFehler(409, "vergeben", `Die Vorlage „${eingabe.name}“ gibt es schon.`);
|
||||
if (!vorhanden && bisher) throw nichtGefunden(`Die Vorlage „${bisher}“`);
|
||||
const daten = {
|
||||
titel: eingabe.titel,
|
||||
beschreibung: eingabe.beschreibung ?? null,
|
||||
parameter: eingabe.parameter as Prisma.InputJsonValue,
|
||||
};
|
||||
const v = await prisma.$transaction(async (tx) => {
|
||||
const gespeichert = await tx.vorlage.upsert({
|
||||
where: { name: eingabe.name },
|
||||
create: { name: eingabe.name, ...daten, erstelltVonId: aufrufer.benutzer.id },
|
||||
update: daten,
|
||||
});
|
||||
await protokollieren(
|
||||
{
|
||||
benutzerId: aufrufer.benutzer.id,
|
||||
quelle: aufrufer.quelle,
|
||||
aktion: bisher ? "Vorlage geändert" : "Vorlage angelegt",
|
||||
ziel: `vorlage:${eingabe.name}`,
|
||||
ip,
|
||||
},
|
||||
tx,
|
||||
);
|
||||
return gespeichert;
|
||||
});
|
||||
return sicht(v);
|
||||
}
|
||||
|
||||
export async function vorlageLoeschen(name: string, aufrufer: Aufrufer, ip: string | null): Promise<void> {
|
||||
await vorlageHolen(name);
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.vorlage.delete({ where: { name } });
|
||||
await protokollieren(
|
||||
{
|
||||
benutzerId: aufrufer.benutzer.id,
|
||||
quelle: aufrufer.quelle,
|
||||
aktion: "Vorlage gelöscht",
|
||||
ziel: `vorlage:${name}`,
|
||||
ip,
|
||||
},
|
||||
tx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Beim ersten Start ein Beispiel aus der Vorgabe: „Docker-Host klein“. Nur einmal – wer es löscht,
|
||||
* bekommt es nicht zurück (gemerkt in den Einstellungen).
|
||||
*/
|
||||
export async function beispielVorlageAnlegen(): Promise<void> {
|
||||
const merker = "vorlagen.beispielAngelegt";
|
||||
if (await prisma.einstellung.findUnique({ where: { schluessel: merker } })) return;
|
||||
await prisma.$transaction([
|
||||
prisma.vorlage.upsert({
|
||||
where: { name: "docker-host-klein" },
|
||||
create: {
|
||||
name: "docker-host-klein",
|
||||
titel: "Docker-Host klein",
|
||||
beschreibung: "Debian 13 als VM mit 2 CPU, 4 GB RAM, 40 GB Disk – base und docker",
|
||||
parameter: {
|
||||
typ: "vm",
|
||||
distribution: "debian-13",
|
||||
cores: 2,
|
||||
speicherMb: 4096,
|
||||
diskGb: 40,
|
||||
profile: ["base", "docker"],
|
||||
},
|
||||
},
|
||||
update: {},
|
||||
}),
|
||||
prisma.einstellung.upsert({
|
||||
where: { schluessel: merker },
|
||||
create: { schluessel: merker, wert: true },
|
||||
update: {},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user