feat(260923-dhh): Proxmox-Modul Aufgabe 1 - PVE per Token, Ende-zu-Ende
- ProxmoxServer/ProxmoxServerStatus mit RLS (tenant_isolation_policy +
system_read_policy auf ProxmoxServer fuer den kommenden Planer)
- proxmox-auth.ts (Token-Kopfzeilen PVE/PBS), proxmox-client.service.ts
(proxmoxGet, ausschliesslich lesend, Dispatcher je Aufruf aus
tlsRejectUnauthorized, nie global)
- proxmox.service.ts: Server anlegen (Geheimnis verschluesselt,
select ohne Geheimnisfelder), Serverliste, PVE-Abfrage mit
nachsichtiger Grundauswertung (Knoten/Gaeste)
- Controller/Modul/Seed nach Domaincheck-Vorbild, Kategorie
"infrastructure", @UseModule('proxmox') + @Roles auf Schreibwegen
- Modulseite (duenne Liste) + proxmox-api.ts + Registrierung in
MODULE_REGISTRY
- Zugriffsklassifikation nachgezogen (rls-access-inventory.spec.ts gruen)
Tore: api 1247/1247 (>=1240), web 693/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:
@@ -0,0 +1,149 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user