710034c80a
Nachbesserung aus dem Browser-Rundgang zu 260923-dhh (Befund 1): "Verbindung testen" pruefte bislang immer den in der Datenbank gespeicherten Server, nicht das ungespeicherte Formular. Eine im Formular abgeschaltete Zertifikatspruefung oder ein neu eingetipptes Token-Geheimnis wurden dadurch beim Test ignoriert und erst nach "Speichern" wirksam — eine Falle fuer genau den Ablauf, den Nutzer instinktiv waehlen (eintippen, testen, dann erst speichern). Neues `TestProxmoxServerDto` plus Merge-Baustein `resolveEffectiveTestServer` in `ProxmoxService`: normale Felder folgen dem Formular (auch wenn absichtlich geleert), Geheimnisfelder folgen der bestehenden "leer -> gespeicherten Wert behalten"-Regel, weil `ServerForm` sie beim Laden nie aus der Datenbank vorbefuellt. Neue Route `POST servers/test` (ohne `:id`) deckt die Neuanlage ab, wo es noch keinen gespeicherten Server gibt. Der Testen-Knopf steht jetzt immer zur Verfuegung, nicht mehr nur nach dem ersten Speichern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
164 lines
4.3 KiB
TypeScript
164 lines
4.3 KiB
TypeScript
import {
|
|
IsBoolean,
|
|
IsIn,
|
|
IsInt,
|
|
IsNotEmpty,
|
|
IsOptional,
|
|
IsString,
|
|
IsUrl,
|
|
Max,
|
|
Min,
|
|
Validate,
|
|
ValidateIf,
|
|
type ValidationArguments,
|
|
ValidatorConstraint,
|
|
type ValidatorConstraintInterface,
|
|
} from 'class-validator';
|
|
|
|
/**
|
|
* D-03: PMG kennt laut Recherche keinen API-Token (Annahme A1) — ein Server
|
|
* vom Typ `pmg` mit `authMethod: 'token'` wird bereits beim Speichern mit
|
|
* einer deutschen Klartextmeldung abgelehnt (400), nicht erst beim
|
|
* Abfragen. Angebracht am Feld `authMethod`, liest aber `productType`
|
|
* desselben Objekts (`args.object`) — class-validator erlaubt das.
|
|
*/
|
|
@ValidatorConstraint({ name: 'pmgOhneToken', async: false })
|
|
class PmgOhneTokenConstraint implements ValidatorConstraintInterface {
|
|
validate(_value: unknown, args: ValidationArguments): boolean {
|
|
const obj = args.object as { productType?: string; authMethod?: string };
|
|
return !(obj.productType === 'pmg' && obj.authMethod === 'token');
|
|
}
|
|
|
|
defaultMessage(): string {
|
|
return 'PMG unterstuetzt keinen API-Token-Zugang. Bitte Benutzer und Passwort waehlen.';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DTO fuer das Anlegen eines Proxmox-Servers (Aufgabe 1). Pflichtfelder je
|
|
* `authMethod` mit `@ValidateIf` (Aufgabe 2): ein Token-Zugang verlangt
|
|
* `tokenId`/`tokenSecret`, ein Passwort-Zugang `username`/`password`.
|
|
*/
|
|
export class CreateProxmoxServerDto {
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
name!: string;
|
|
|
|
@IsIn(['pve', 'pbs', 'pmg'])
|
|
productType!: 'pve' | 'pbs' | 'pmg';
|
|
|
|
// require_tld: false — interne Namen wie "pve.intern" sind sonst abgelehnt.
|
|
@IsUrl({ protocols: ['http', 'https'], require_tld: false })
|
|
baseUrl!: string;
|
|
|
|
@IsIn(['token', 'password'])
|
|
@Validate(PmgOhneTokenConstraint)
|
|
authMethod!: 'token' | 'password';
|
|
|
|
@ValidateIf((o) => o.authMethod === 'token')
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
tokenId?: string;
|
|
|
|
@ValidateIf((o) => o.authMethod === 'token')
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
tokenSecret?: string;
|
|
|
|
@ValidateIf((o) => o.authMethod === 'password')
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
username?: string;
|
|
|
|
@ValidateIf((o) => o.authMethod === 'password')
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
password?: string;
|
|
|
|
@IsBoolean()
|
|
@IsOptional()
|
|
tlsRejectUnauthorized?: boolean;
|
|
|
|
@IsInt()
|
|
@Min(1)
|
|
@Max(1440)
|
|
@IsOptional()
|
|
pollIntervalMin?: number;
|
|
|
|
@IsBoolean()
|
|
@IsOptional()
|
|
isActive?: boolean;
|
|
}
|
|
|
|
/**
|
|
* DTO fuer das Bearbeiten (Aufgabe 5). Alle Felder optional; ein NICHT
|
|
* gesendetes Geheimnisfeld laesst den gespeicherten Wert unveraendert, eine
|
|
* LEERE Zeichenkette bedeutet "loeschen" (Muster `LdapConfigService.updateConfig`)
|
|
* — diese Unterscheidung lebt im Service, nicht im DTO, deshalb bleiben
|
|
* `tokenSecret`/`password` hier einfache optionale Zeichenketten ohne
|
|
* `IsNotEmpty`.
|
|
*/
|
|
export class UpdateProxmoxServerDto {
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@IsOptional()
|
|
name?: string;
|
|
|
|
@IsIn(['pve', 'pbs', 'pmg'])
|
|
@IsOptional()
|
|
productType?: 'pve' | 'pbs' | 'pmg';
|
|
|
|
@IsUrl({ protocols: ['http', 'https'], require_tld: false })
|
|
@IsOptional()
|
|
baseUrl?: string;
|
|
|
|
@IsIn(['token', 'password'])
|
|
@Validate(PmgOhneTokenConstraint)
|
|
@IsOptional()
|
|
authMethod?: 'token' | 'password';
|
|
|
|
@IsString()
|
|
@IsOptional()
|
|
tokenId?: string;
|
|
|
|
@IsString()
|
|
@IsOptional()
|
|
tokenSecret?: string;
|
|
|
|
@IsString()
|
|
@IsOptional()
|
|
username?: string;
|
|
|
|
@IsString()
|
|
@IsOptional()
|
|
password?: string;
|
|
|
|
@IsBoolean()
|
|
@IsOptional()
|
|
tlsRejectUnauthorized?: boolean;
|
|
|
|
@IsInt()
|
|
@Min(1)
|
|
@Max(1440)
|
|
@IsOptional()
|
|
pollIntervalMin?: number;
|
|
|
|
@IsBoolean()
|
|
@IsOptional()
|
|
isActive?: boolean;
|
|
}
|
|
|
|
/**
|
|
* DTO fuer den Verbindungstest (Nachbesserung Befund 1, Rundgang zu Aufgabe 4):
|
|
* derselbe Feldsatz wie `UpdateProxmoxServerDto` — der Test soll auf JEDEM
|
|
* dieser Felder den ungespeicherten Formularwert pruefen koennen, nicht den
|
|
* gespeicherten Stand. Ein NICHT gesendetes oder leeres Geheimnisfeld heisst
|
|
* "gespeicherten Wert weiterverwenden" (Merge-Logik in
|
|
* `ProxmoxService.resolveEffectiveTestServer`), genau wie beim Bearbeiten.
|
|
* Fuer die Neuanlage (noch kein gespeicherter Server) bleiben alle Felder
|
|
* optional, weil es dort keinen gespeicherten Fallback gibt — ein fehlendes
|
|
* Pflichtfeld fuehrt dort einfach zum selben Fehlerschluessel wie ein leer
|
|
* gelassenes Feld beim Anlegen selbst (z. B. `zugang` ohne Geheimnis).
|
|
*/
|
|
export class TestProxmoxServerDto extends UpdateProxmoxServerDto {}
|