feat(260923-dhh): Proxmox-Modul Aufgabe 5 - Einstellungsseite (anlegen, bearbeiten, loeschen, testen)
- proxmox.service.ts: updateServer (Muster LdapConfigService.updateConfig - nicht gesendet laesst unveraendert, leer loescht, gefuellt verschluesselt neu; PMG+Token auch beim Bearbeiten abgelehnt, geprueft gegen den EFFEKTIVEN Stand nach Zusammenfuehren), deleteServer - proxmox.controller.ts: PUT/DELETE servers/:id, beide zusaetzlich mit scheduler.refreshTenant() nach dem Schreiben - Frontend: proxmox-api.ts (updateServer/deleteServer/testServer), settings/page.tsx (Rollenpruefung nur Anzeige, Serverliste, Loeschen mit Rueckfrage), ServerForm.tsx (PMG bietet Token gar nicht an, Geheimnisfelder nie vorbefuellt, Zertifikatspruefung-Schalter Standard "pruefen", Verbindungstest mit Klartext-Fehlertext) - umlaut-dictionary.ts: zwei neue, bereits korrekte Woerter (bewusst/gemessene) auf die Positivliste des Regressions-Waechters Tore: api 1311/1311 (>=1240), web 701/701 (>=693), type-check 4/4, lint 5/5, Biome apps/web 53 Warnungen (unveraendert). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||
import type { ProxmoxServer } from '@prisma/client';
|
||||
import { CryptoService } from '../crypto/crypto.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
@@ -6,7 +6,7 @@ import { forSystem, forTenant } from '../prisma/prisma-tenant.extension';
|
||||
import { buildTicketCookieHeader, buildTokenAuthHeader, loginTicket } from './proxmox-auth';
|
||||
import { proxmoxGet, type ProxmoxGetResult } from './proxmox-client.service';
|
||||
import { listPbsDatastoreNames, normalizePbs, normalizePmg, normalizePve } from './proxmox-normalize';
|
||||
import type { CreateProxmoxServerDto } from './dto/proxmox-server.dto';
|
||||
import type { CreateProxmoxServerDto, UpdateProxmoxServerDto } from './dto/proxmox-server.dto';
|
||||
import type { ProxmoxErrorKind, ProxmoxPollResult, ProxmoxProductType } from './proxmox.types';
|
||||
|
||||
/**
|
||||
@@ -144,6 +144,73 @@ export class ProxmoxService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bearbeiten (Aufgabe 5). Dieselbe Regel wie
|
||||
* `LdapConfigService.updateConfig`: ein NICHT gesendetes Geheimnisfeld
|
||||
* laesst den gespeicherten Wert unveraendert, eine LEERE Zeichenkette
|
||||
* bedeutet "loeschen", ein gefuellter Wert wird neu verschluesselt. Die
|
||||
* Ablehnung "PMG plus Token" gilt auch hier — geprueft gegen den
|
||||
* EFFEKTIVEN Stand nach dem Zusammenfuehren mit der vorhandenen Zeile,
|
||||
* nicht nur gegen die gesendeten Felder (ein Teil-Update, das nur
|
||||
* `authMethod` aendert, wuerde die DTO-eigene Pruefung sonst umgehen,
|
||||
* weil `productType` in diesem Aufruf gar nicht gesendet wird).
|
||||
*/
|
||||
async updateServer(tenantId: string, serverId: string, dto: UpdateProxmoxServerDto) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
const existing = await tenantPrisma.proxmoxServer.findUnique({ where: { id: serverId } });
|
||||
if (!existing || existing.tenantId !== tenantId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const effectiveProductType = dto.productType ?? existing.productType;
|
||||
const effectiveAuthMethod = dto.authMethod ?? existing.authMethod;
|
||||
if (effectiveProductType === 'pmg' && effectiveAuthMethod === 'token') {
|
||||
throw new BadRequestException(
|
||||
'PMG unterstuetzt keinen API-Token-Zugang. Bitte Benutzer und Passwort waehlen.',
|
||||
);
|
||||
}
|
||||
|
||||
const data: Record<string, unknown> = {};
|
||||
if (dto.name !== undefined) data.name = dto.name;
|
||||
if (dto.productType !== undefined) data.productType = dto.productType;
|
||||
if (dto.baseUrl !== undefined) data.baseUrl = dto.baseUrl;
|
||||
if (dto.authMethod !== undefined) data.authMethod = dto.authMethod;
|
||||
if (dto.tokenId !== undefined) data.tokenId = dto.tokenId || null;
|
||||
if (dto.tokenSecret !== undefined) {
|
||||
data.encryptedTokenSecret = dto.tokenSecret ? this.crypto.encrypt(dto.tokenSecret) : null;
|
||||
}
|
||||
if (dto.username !== undefined) data.username = dto.username || null;
|
||||
if (dto.password !== undefined) {
|
||||
data.encryptedPassword = dto.password ? this.crypto.encrypt(dto.password) : null;
|
||||
}
|
||||
if (dto.tlsRejectUnauthorized !== undefined) {
|
||||
data.tlsRejectUnauthorized = dto.tlsRejectUnauthorized;
|
||||
}
|
||||
if (dto.pollIntervalMin !== undefined) data.pollIntervalMin = dto.pollIntervalMin;
|
||||
if (dto.isActive !== undefined) data.isActive = dto.isActive;
|
||||
|
||||
return tenantPrisma.proxmoxServer.update({
|
||||
where: { id: serverId },
|
||||
data,
|
||||
select: SAFE_SERVER_SELECT,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loeschen (Aufgabe 5) — entfernt den Server samt Zwischenlagerzeile
|
||||
* (Fremdschluessel mit Loeschweitergabe, `onDelete: Cascade`). Liefert
|
||||
* `false`, wenn der Server unter diesem Mandanten nicht existiert.
|
||||
*/
|
||||
async deleteServer(tenantId: string, serverId: string): Promise<boolean> {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
const existing = await tenantPrisma.proxmoxServer.findUnique({ where: { id: serverId } });
|
||||
if (!existing || existing.tenantId !== tenantId) {
|
||||
return false;
|
||||
}
|
||||
await tenantPrisma.proxmoxServer.delete({ where: { id: serverId } });
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut die Anmeldekopfzeile fuer GENAU diesen Server ueber
|
||||
* `proxmox-auth.ts` (D-03). Beim Passwort-Zweig loest das eine
|
||||
|
||||
Reference in New Issue
Block a user