From f7e3d7f8d0b8580dc73e443efc544abc6b72d765 Mon Sep 17 00:00:00 2001 From: Lukas Alstrup Date: Mon, 6 Apr 2026 22:28:06 +0200 Subject: [PATCH 1/6] feat: add health score tooltips and unify cluster health calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unified the cluster health formula across the overview panel and cluster card badge to use the same weighted calculation: 100 - (CPU×30% + RAM×30% + Storage×20% + Offline Nodes×20%). Added hover tooltips explaining the cluster health and node score formulas in all 5 languages (DE, EN, FR, ES, PT). Replaced the now-unused "Avg. Score" stat with "Avg. Storage" in the health panel. --- web/index.html | 24 ++++++++++++------------ web/src/tables.js | 4 ++-- web/src/translations.js | 15 +++++++++++++++ web/src/vm_modals.js | 17 +++++++++-------- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/web/index.html b/web/index.html index a141bdd..7d21973 100644 --- a/web/index.html +++ b/web/index.html @@ -2048,7 +2048,7 @@ poolPermissions:'Pool-Berechtigungen',poolPermissionsDesc:'Gewähren Sie Benutzern oder Gruppen Zugriff auf Proxmox Resource Pools. Berechtigungen gelten für alle VMs im Pool.',managePools:'Pools verwalten',poolManagerDesc:'Erstellen, bearbeiten und löschen Sie Resource Pools. Weisen Sie VMs zu Pools für organisierte Berechtigungsverwaltung zu.',createPool:'Pool erstellen',editPool:'Pool bearbeiten',deletePool:'Pool löschen',poolId:'Pool-ID',poolIdRequired:'Pool-ID ist erforderlich',poolIdHint:'Nur Buchstaben, Zahlen, Bindestriche und Unterstriche',poolIdCannotChange:'Pool-ID kann nicht geändert werden',poolCreated:'Pool erfolgreich erstellt',poolUpdated:'Pool erfolgreich aktualisiert',poolDeleted:'Pool erfolgreich gelöscht',confirmDeletePool:'Sind Sie sicher, dass Sie diesen Pool löschen möchten? Dies kann nicht rückgängig gemacht werden.',noPoolsYet:'Noch keine Pools',createFirstPool:'Erstellen Sie Ihren ersten Pool, um VMs zu organisieren',poolMembers:'Mitglieder',members:'Mitglieder',addVmToPool:'VM zum Pool hinzufügen',assignToPool:'Pool zuweisen',removeFromPool:'Aus Pool entfernen',vmAddedToPool:'VM zum Pool hinzugefügt',vmRemovedFromPool:'VM aus Pool entfernt',confirmRemoveVmFromPool:'VM aus diesem Pool entfernen?',selectVmToAdd:'Wählen Sie eine VM zum Hinzufügen aus',allVmsInPools:'Alle VMs sind bereits in Pools',optionalDescription:'Optionale Beschreibung...',userPermissions:'Benutzer-Berechtigungen',selectPool:'Pool auswählen',noPools:'Keine Resource Pools in diesem Cluster gefunden',noPoolPerms:'Keine Berechtigungen für diesen Pool konfiguriert',selectPoolFirst:'Wählen Sie einen Cluster und Pool zur Verwaltung',addPoolPerm:'Pool-Berechtigung hinzufügen',editPoolPerm:'Pool-Berechtigung bearbeiten',poolPermSaved:'Pool-Berechtigung gespeichert',poolPermDeleted:'Pool-Berechtigung entfernt',refreshPools:'Pools von Proxmox aktualisieren',poolCacheRefreshed:'Pool-Cache aktualisiert',confirmDeletePoolPerm:'Berechtigung entfernen?',subjectType:'Typ',permissionsFor:'Berechtigungen für',addPermission:'Berechtigung hinzufügen',groupName:'Gruppenname',auditLog:'Audit Log',auditLogDescription:'Alle Benutzeraktionen der letzten 90 Tage',noAuditLogs:'Keine Audit-Einträge vorhanden',action:'Aktion',details:'Details',timestamp:'Zeitstempel',ipAddress:'IP-Adresse',userCreated:'Benutzer erstellt',userUpdated:'Benutzer aktualisiert',userDeleted:'Benutzer gelöscht',userLogin:'Anmeldung',userLogout:'Abmeldung',passwordChanged:'Passwort geändert',clusterAdded:'Cluster hinzugefügt',apiTokenWarningTitle:'API Token Authentifizierung',apiTokenWarningDesc:'Ohne Passwort funktionieren SSH-Features nicht (HA, Rolling Updates, SMBIOS, Node Shell). Empfohlen: root@pam + Passwort verwenden — PegaProx erstellt automatisch einen API-Token für 2FA-Kompatibilität.',apiTokenRecommended:'Empfohlen bei aktivierter 2FA',apiTokenCreated:'API-Token auf PVE erstellt — 2FA kann jetzt aktiviert werden',sshPasswordStillNeeded:'SSH nutzt weiterhin das Passwort (HA, Wartung) — bitte nicht ändern',authModeToken:'API: Token (2FA-sicher)',authModePassword:'API: Passwort',sshAuthMode:'SSH: Passwort',dontChangePvePassword:'PVE-Passwort nicht ändern ohne es hier zu aktualisieren',clusterDeleted:'Cluster gelöscht',clusterConfigChanged:'Cluster-Konfiguration geändert',vmStarted:'VM gestartet',vmStopped:'VM gestoppt',vmRestarted:'VM neu gestartet',vmCreated:'VM erstellt',vmDeleted:'VM gelöscht',vmCloned:'VM geklont',vmMigrated:'VM migriert',vmUnlocked:'VM entsperrt',vmLocked:'VM gesperrt',unlockVm:'VM entsperren',lockReason:'Sperrgrund',unlockWarning:'Warnung: Das Entsperren einer VM während einer aktiven Operation kann zu Datenverlust oder anderen Problemen führen. Nur fortfahren wenn die Operation fehlgeschlagen oder abgebrochen wurde.',vmBulkMigrated:'Massen-Migration',vmConfigChanged:'VM-Konfiguration geändert',vmSuspended:'VM angehalten',vmResumed:'VM fortgesetzt',vmDiskAdded:'Festplatte hinzugefügt',vmDiskRemoved:'Festplatte entfernt',vmDiskResized:'Festplatte vergrößert',vmDiskMoved:'Festplatte verschoben',vmNetworkAdded:'Netzwerk hinzugefügt',vmNetworkRemoved:'Netzwerk entfernt',vmNetworkUpdated:'Netzwerk aktualisiert',removeDiskConfirm:'Festplatte wirklich entfernen',detachDisk:'Abklemmen',importDisk:'Festplatte importieren',importDiskDesc:'Vorhandenes Disk-Image von Storage importieren',selectImportStorage:'Quell-Storage auswählen',selectDiskImage:'Disk-Image auswählen',targetBus:'Ziel-Bus',reassignOwner:'Besitzer ändern',reassignOwnerDesc:'Festplatte einer anderen VM zuweisen',targetVm:'Ziel-VM',diskReassigned:'Festplatte neu zugewiesen',diskImported:'Festplatte importiert',noImportableDisks:'Keine importierbaren Disk-Images gefunden',reassign:'Neu zuweisen',import:'Importieren',detachDiskConfirm:'Festplatte wirklich abklemmen? Sie wird zu einer ungenutzten Festplatte.',diskDetached:'Festplatte abgeklemmt',diskDeleted:'Festplatte gelöscht',diskAdded:'Festplatte hinzugefügt',dataWillBeDeleted:'Daten werden dauerhaft gelöscht!',removeNetworkConfirm:'Netzwerk wirklich entfernen',snapshotCreated:'Snapshot erstellt',snapshotDeleted:'Snapshot gelöscht',snapshotRestored:'Snapshot wiederhergestellt',rollbackStarted:'Rollback gestartet',includeRam:'RAM einschließen',snapshotName:'Snapshot Name',rollbackConfirm:'Wirklich zu diesem Snapshot zurückrollen? Dies kann nicht rückgängig gemacht werden!',replicationCreated:'Replikation erstellt',replicationDeleted:'Replikation gelöscht',replicationTriggered:'Replikation manuell gestartet',haEnabled:'HA aktiviert',haDisabled:'HA deaktiviert',noFallbackHosts:'Keine Fallback-Hosts gefunden (Single-Node Cluster?)',haVmAdded:'VM zu HA hinzugefügt',haVmRemoved:'VM aus HA entfernt',nodeMaintenanceEntered:'Wartungsmodus aktiviert',nodeMaintenanceExited:'Wartungsmodus beendet',nodeUpdateStarted:'Node-Update gestartet',twoFactorAuth:'2-Faktor-Authentifizierung',twoFactorEnabled:'2FA aktiviert',twoFactorDisabled:'2FA deaktiviert',enable2FA:'2FA aktivieren',disable2FA:'2FA deaktivieren',setup2FA:'2FA einrichten',scan2FACode:'QR-Code mit Authenticator-App scannen',enter2FACode:'6-stelligen Code eingeben',verify2FA:'Verifizieren',secretKey:'Geheimer Schlüssel',twoFARequired:'2FA-Code erforderlich',invalid2FACode:'Ungültiger 2FA-Code',force2FA:'2FA erzwingen',force2FADesc:'Alle Benutzer müssen Zwei-Faktor-Authentifizierung einrichten bevor sie PegaProx verwenden können.',force2FAHint:'OIDC/Entra-Benutzer sind ausgenommen (nutzen MFA ihres Identity Providers). Benutzer ohne 2FA sehen beim Login einen Einrichtungs-Dialog.',force2FAExcludeAdmins:'Admin-Konten ausschließen',force2FAExcludeAdminsDesc:'Admins können PegaProx ohne 2FA nutzen',force2FASetupTitle:'2FA-Einrichtung erforderlich',force2FASetupDesc:'Ihr Administrator hat Zwei-Faktor-Authentifizierung für alle Benutzer verpflichtend gemacht. Bitte richten Sie 2FA ein um fortzufahren.',resetPassword:'Passwort zurücksetzen',passwordManagedExternally:'Ihr Passwort wird extern verwaltet.',passwordManagedExternallyHint:'Bitte ändern Sie Ihr Passwort direkt in Ihrem Verzeichnisdienst.',newPassword:'Neues Passwort',confirmPassword:'Passwort bestätigen',currentPassword:'Aktuelles Passwort',passwordsDoNotMatch:'Passwörter stimmen nicht überein',passwordTooShort:'Passwort muss mindestens 4 Zeichen haben',passwordResetSuccess:'Passwort erfolgreich geändert',myProfile:'Mein Profil',security:'Sicherheit',snapshotNotSupported:'Snapshots werden nicht unterstützt',snapshotWarnings:'Snapshot-Warnungen',filterByUser:'Nach Benutzer filtern',filterByAction:'Nach Aktion filtern',allUsers:'Alle Benutzer',allActions:'Alle Aktionen',exportAuditLog:'Exportieren',refreshAuditLog:'Aktualisieren',// Header addCluster:'Cluster hinzufügen',addXcpngPool:'XCP-ng Pool hinzufügen (Tech Preview)',xcpngConnectHint:'Verbinde dich mit dem Pool-Master. XAPI Port 443 wird standardmäßig verwendet.',xcpngTechPreviewNote:'Einige Funktionen sind eingeschränkt oder können sich ändern.',pveClusterDesc:'Virtuelle Maschinen & Container',pbsDesc:'Backup-Verwaltung',vmwareDesc:'ESXi-Infrastruktur',xcpngDesc:'XCP-ng / Xen Hypervisor (Tech Preview)',addConnection:'Verbindung hinzufügen',connectionType:'Verbindungstyp',clusterManagement:'PegaProx Cluster Management for Proxmox VE',// Tabs overview:'Übersicht',resources:'Ressourcen',datacenter:'Datacenter',settings:'Einstellungen',of:'von',showing:'Zeige',perPage:'Pro Seite',loadingDatacenter:'Lade Datacenter Daten...',// Cluster -clusters:'Cluster',noClusterSelected:'Kein Cluster ausgewählt',allClustersOverview:'Alle Cluster Übersicht',allClusters:'Alle Cluster',multiClusterSummary:'Zusammenfassung aller verwalteten Cluster',clustersConnected:'Cluster verbunden',clusterOverview:'Cluster Übersicht',vmsRunning:'VMs laufend',vmsStopped:'VMs gestoppt',noClustersConfigured:'Keine Cluster konfiguriert',addClusterToStart:'Füge einen Cluster hinzu um zu beginnen',clickClusterTip:'Klicke auf eine Cluster-Zeile oder wähle aus der Sidebar um Details anzuzeigen und Ressourcen zu verwalten.',health:'Zustand',average:'Durchschnitt',noDataAvailable:'Keine Daten verfügbar',clickToManage:'Klicken zum Verwalten',sortBy:'Sortieren nach',ungrouped:'Nicht gruppiert',alerts:'Warnungen',updated:'Aktualisiert',justNow:'gerade eben',topResources:'Top Ressourcen',highestCpuUsage:'Höchste CPU- und RAM-Auslastung über alle Cluster',clickToOpenVm:'Klicken um VM zu öffnen',selectCluster:'Wähle einen Cluster aus der Liste aus',addFirstCluster:'Ersten Cluster hinzufügen',connectCluster:'Verbinde einen Proxmox Cluster mit PegaProx',clusterName:'Cluster Name',host:'Host',username:'Benutzername',password:'Passwort',passwordOrToken:'Passwort / Token',apiTokenHint:'Für API Tokens: user@realm!tokenid',sslVerification:'SSL Verifizierung',connecting:'Verbinde...',addNewCluster:'Neuen Cluster hinzufügen',testConnection:'Verbindung testen',deleteCluster:'Cluster löschen',deleteClusterConfirm:'Cluster wirklich löschen?',reconfigureCluster:'Cluster neu konfigurieren',reconfigureHint:'Geben Sie Ihr PegaProx-Passwort ein, um Ihre Identität zu bestätigen.',clusterReconfigured:'Cluster erfolgreich neu konfiguriert',reconfigure:'Neu konfigurieren',clusterHealth:'Cluster Gesundheit',excellent:'Ausgezeichnet',good:'Gut',warning:'Warnung',critical:'Kritisch',nodesOnline:'Nodes Online',nodeJoinHint:'Um einen neuen Node hinzuzufügen, führe auf dem neuen Node aus:',avgScore:'Avg. Score',avgCpu:'Avg. CPU',avgRam:'Avg. RAM',// Nodes +clusters:'Cluster',noClusterSelected:'Kein Cluster ausgewählt',allClustersOverview:'Alle Cluster Übersicht',allClusters:'Alle Cluster',multiClusterSummary:'Zusammenfassung aller verwalteten Cluster',clustersConnected:'Cluster verbunden',clusterOverview:'Cluster Übersicht',vmsRunning:'VMs laufend',vmsStopped:'VMs gestoppt',noClustersConfigured:'Keine Cluster konfiguriert',addClusterToStart:'Füge einen Cluster hinzu um zu beginnen',clickClusterTip:'Klicke auf eine Cluster-Zeile oder wähle aus der Sidebar um Details anzuzeigen und Ressourcen zu verwalten.',health:'Zustand',average:'Durchschnitt',noDataAvailable:'Keine Daten verfügbar',clickToManage:'Klicken zum Verwalten',sortBy:'Sortieren nach',ungrouped:'Nicht gruppiert',alerts:'Warnungen',updated:'Aktualisiert',justNow:'gerade eben',topResources:'Top Ressourcen',highestCpuUsage:'Höchste CPU- und RAM-Auslastung über alle Cluster',clickToOpenVm:'Klicken um VM zu öffnen',selectCluster:'Wähle einen Cluster aus der Liste aus',addFirstCluster:'Ersten Cluster hinzufügen',connectCluster:'Verbinde einen Proxmox Cluster mit PegaProx',clusterName:'Cluster Name',host:'Host',username:'Benutzername',password:'Passwort',passwordOrToken:'Passwort / Token',apiTokenHint:'Für API Tokens: user@realm!tokenid',sslVerification:'SSL Verifizierung',connecting:'Verbinde...',addNewCluster:'Neuen Cluster hinzufügen',testConnection:'Verbindung testen',deleteCluster:'Cluster löschen',deleteClusterConfirm:'Cluster wirklich löschen?',reconfigureCluster:'Cluster neu konfigurieren',reconfigureHint:'Geben Sie Ihr PegaProx-Passwort ein, um Ihre Identität zu bestätigen.',clusterReconfigured:'Cluster erfolgreich neu konfiguriert',reconfigure:'Neu konfigurieren',clusterHealth:'Cluster Gesundheit',clusterHealthTooltip:'Gesundheit = 100 − (CPU×30% + RAM×30% + Speicher×20% + Offline-Nodes×20%)\n\n80+: Ausgezeichnet\n60–79: Gut\n40–59: Warnung\n< 40: Kritisch',nodeScoreTooltip:'Node-Score = CPU% + RAM% (niedriger ist besser)\n\n< 100: Gut (grün)\n100–150: Erhöht (gelb)\n> 150: Kritisch (rot)',excellent:'Ausgezeichnet',good:'Gut',warning:'Warnung',critical:'Kritisch',nodesOnline:'Nodes Online',nodeJoinHint:'Um einen neuen Node hinzuzufügen, führe auf dem neuen Node aus:',avgScore:'Avg. Score',avgStorage:'Avg. Speicher',avgCpu:'Avg. CPU',avgRam:'Avg. RAM',// Nodes nodes:'Nodes',node:'Node',loadingMetrics:'Lade Metriken...',connectionError:'Verbindungsfehler',retry:'Erneut versuchen',checkConnectionAndRetry:'Bitte überprüfen Sie Ihre Verbindung und versuchen Sie es erneut.',connectionTimeout:'Zeitüberschreitung - Proxmox nicht erreichbar',maintenance:'Wartung',enterMaintenance:'Wartungsmodus aktivieren',exitMaintenance:'Wartungsmodus beenden',maintenanceMode:'Wartungsmodus',update:'Update',startUpdate:'Update starten',nodeConfig:'Node Konfiguration',cpuHistory:'CPU Verlauf',ramHistory:'RAM Verlauf',ramUsage:'RAM Nutzung',cpuUsage:'CPU Nutzung',diskUsage:'Disk Nutzung',allocated:'zugewiesen',showMore:'Mehr anzeigen',showLess:'Weniger anzeigen',// VMs & Resources virtualMachines:'Virtuelle Maschinen',containers:'Container',vm:'VM',lxc:'LXC',lxcContainer:'LXC Container',container:'Container',guests:'Guests',start:'Starten',stop:'Stoppen',shutdown:'Herunterfahren',reboot:'Neustarten',forceStop:'Force Stop',forceReset:'Force Reset',forceStopConfirm:'wirklich hart ausschalten? Dies kann zu Datenverlust führen!',migrate:'Migrieren',migrateVm:'VM migrieren',crossClusterMigration:'Cross-Cluster Migration',crossClusterMigrateDesc:'VM zu anderem Proxmox-Cluster migrieren',crossClusterMigrate:'Cross-Cluster migrieren',crossClusterStarted:'Cross-Cluster Migration gestartet',crossClusterFailed:'Cross-Cluster Migration fehlgeschlagen',crossClusterLB:'Cross-Cluster Lastverteilung',crossClusterLBEnabled:'Cross-Cluster LB aktiviert',crossClusterLBDisabled:'Cross-Cluster LB deaktiviert',crossClusterLBThreshold:'Score-Schwellenwert',crossClusterLBInterval:'Prüfintervall',crossClusterLBDryRun:'Simulation (Dry Run)',crossClusterLBLastRun:'Letzter Lauf',crossClusterReplication:'Cross-Cluster Replikation',crossClusterReplicationDesc:'VM-Snapshots zu anderem Cluster replizieren (DR)',groupOverview:'Gruppenübersicht',groupSettings:'Gruppeneinstellungen',groupSettingsSaved:'Gruppeneinstellungen gespeichert',replicationSchedule:'Zeitplan',replicationRetention:'Aufbewahrung',maxMigrations:'Max. Migrationen pro Zyklus',lbHistory:'LB-Verlauf',clusterScore:'Cluster-Score',noReplicationJobs:'Keine Cross-Cluster Replikationsjobs',createReplicationJob:'DR-Job erstellen',replicationStarted:'Replikation gestartet',replicationJobCreated:'Replikationsjob erstellt',replicationJobDeleted:'Replikationsjob gelöscht',recentLbActions:'Letzte Cross-Cluster LB-Aktionen',noLbEvents:'Noch keine Cross-Cluster LB-Ereignisse',enableCrossClusterLB:'Cross-Cluster Lastverteilung aktivieren',lbDescription:'VMs automatisch zwischen Clustern migrieren, wenn Ressourcen-Schwellenwerte überschritten werden',dryRunMode:'Dry Run / Simulationsmodus',cpuThreshold:'CPU-Schwellenwert (%)',lbStatus:'Lastverteilungs-Status',lbExplanation:'Die Cross-Cluster Lastverteilung überwacht CPU- und RAM-Auslastung über alle Cluster in dieser Gruppe. Wenn ein Cluster den konfigurierten Schwellenwert überschreitet, werden VMs automatisch zu einem weniger ausgelasteten Cluster migriert.',lbDryRunExplanation:'Aktivieren Sie zuerst den Dry Run-Modus, um geplante Aktionen zu prüfen, bevor Sie Live-Migrationen aktivieren.',neverRun:'Noch nie ausgeführt',newCrossClusterReplication:'Neue Cross-Cluster Replikation',confirmDeleteXRepl:'Diesen Cross-Cluster Replikationsjob wirklich löschen?',addDrJob:'DR-Job hinzufügen',scheduleCron:'Zeitplan (Cron)',targetStorageHint:'Storage-Name auf dem Ziel-Cluster',maxMigrationsHint:'Maximale Anzahl VMs pro Prüfzyklus (1-5)',proxlbCredit:'ProxLB von gyptazy',proxlbCreditDesc:'Unsere Lastverteilungsfunktionalität basiert auf der hervorragenden Arbeit von ProxLB. Besonderer Dank an gyptazy für die Erstellung und Open-Source-Bereitstellung dieses großartigen Tools!',xReplCreated:'Cross-Cluster Replikation erstellt',xReplDeleted:'Cross-Cluster Replikation gelöscht',xReplStarted:'Cross-Cluster Replikation gestartet',xReplCreateFailed:'Job-Erstellung fehlgeschlagen',xReplDeleteFailed:'Löschen fehlgeschlagen',xReplStartFailed:'Start fehlgeschlagen',lastRunPrefix:'Zuletzt',runNow:'Jetzt ausführen',loadingCrossClusterResources:'Lade Storage/Netzwerk für alle Cluster...',noCommonStorages:'Kein gemeinsamer Storage über alle Cluster gefunden',noCommonBridges:'Keine gemeinsame Bridge über alle Cluster gefunden',selectBridge:'Bridge auswählen...',crossClusterThresholdDesc:'CPU-Schwellenwert für Cluster-Imbalance (10-80%)',crossClusterIntervalDesc:'Zeit zwischen Prüfzyklen',crossClusterMaxMigrationsDesc:'Maximale Migrationen pro Prüfzyklus',commonStorageHint:'Nur Storages, die auf allen Clustern in der Gruppe verfügbar sind',commonBridgeHint:'Nur Bridges, die auf allen Clustern in der Gruppe verfügbar sind',includeContainers:'Container einbeziehen',includeContainersDesc:'Container (LXC) beim Cross-Cluster-Balancing berücksichtigen',containerMigrationWarning:'Container werden beim Migrieren neu gestartet (Downtime)',excludedVMsCrossCluster:'Ausgeschlossene VMs/Container',excludedVMsCrossClusterDesc:'VMs und Container, die vom automatischen Cross-Cluster-Balancing ausgeschlossen sind',clusterExcludedVMs:'Ausgeschlossene VMs',noExcludedVMsInGroup:'Keine VMs ausgeschlossen',selectCluster:'Cluster auswählen...',selectClusterFirst:'Bitte zuerst einen Ziel-Cluster auswählen',simulationMode:'Simulationsmodus',sourceVm:'Quell-VM',targetCluster:'Ziel-Cluster',targetNode:'Ziel-Node',targetStorage:'Ziel-Storage',targetBridge:'Ziel-Netzwerk (Bridge)',moveDisk:'Festplatte verschieben',resizeDisk:'Festplatte vergrößern',diskResized:'Festplatte vergrößert',deleteSourceDisk:'Quelle nach Verschieben löschen',deleteSourceDiskWarning:'Die Original-Festplatte bleibt auf dem Quell-Storage. Sie können sie später manuell entfernen.',move:'Verschieben',from:'von',noNetworkInterfaces:'Keine Netzwerk-Interfaces',nameRequired:'Name erforderlich',newVmid:'Neue VMID (optional)',sameIdPlaceholder:'Leer = gleiche ID',liveMigrationOption:'Live-Migration (VM läuft weiter)',deleteSourceAfter:'Quell-VM nach Migration löschen',largeDiskWarning:'Große Disk erkannt',largeDiskExplanation:'Live-Migration bei Disks >100GB kann mit "401 Unauthorized" fehlschlagen (Proxmox WebSocket-Ticket Timeout). Der Server wechselt automatisch auf Offline-Migration, es sei denn, Sie erzwingen Online.',forceOnlineMigration:'Online-Migration trotzdem erzwingen (kann fehlschlagen)',autoTokenInfo:'PegaProx erstellt temporäre API-Tokens für die Migration und löscht diese automatisch nach Abschluss.',clusterReachableInfo:'Die Cluster müssen sich gegenseitig über das Netzwerk erreichen können. Der konfigurierte Benutzer muss Rechte zum Erstellen von API-Tokens haben.',selectCluster:'Cluster auswählen...',selectNode:'Node auswählen',selectStorage:'Storage auswählen...',loadingNodes:'Lade Ziel-Nodes...',loadingStorageNetwork:'Lade Storage/Netzwerk...',liveMigration:'Live-Migration (ohne Downtime)',on:'auf',targetStorage:'Ziel-Storage',sameAsSource:'Gleich wie Quelle',loadingStorages:'Lade Storages',free:'frei',withLocalDisks:'Mit lokalen Disks',withLocalDisksDesc:'Lokale Disks zum Ziel-Storage migrieren',localDisksDetected:'Diese VM hat lokale Disks. Migration erfordert Kopieren der Disk-Daten.',requiredForThisVm:'Erforderlich für diese VM',cdDvdMounted:'CD/DVD eingelegt',cdDvdMigrationWarning:'Live-Migration ist mit eingelegter CD/DVD nicht möglich. Bitte zuerst die CD/DVD auswerfen oder Offline-Migration verwenden.',isoMounted:'ISO/CD-ROM eingelegt',isoEjected:'CD-ROM ausgeworfen',isoMigrationWarning:'Migration kann fehlschlagen wenn die ISO auf dem Ziel-Node nicht verfügbar ist. CD/DVD auswerfen oder ISO auf Shared Storage sicherstellen.',localStorage:'Lokal',bootOrderIssue:'Boot Order Problem',bootOrderWarning:'Boot Order referenziert nicht-existierende Disks',bootOrder:'Boot-Reihenfolge',dragToReorder:'Klicken zum Umschalten, Pfeile zum Sortieren',noBootDevices:'Keine Boot-Geräte gefunden',resizeDiskHint:'Erhöhen um (z.B. +10G) oder neue Größe',// SMBIOS Settings smbiosSettings:'SMBIOS Einstellungen',smbiosHint:'System Management BIOS - nützlich für Windows-Lizenzierung und VM-Identifikation',applySmbiosFromClusterConfig:'SMBIOS-Einstellungen aus Cluster-Konfiguration anwenden',requiresRestart:'NEUSTART',readOnly:'schreibgeschützt',autoGenerated:'auto-generiert',smbiosFormatHint:'Nur Buchstaben und Zahlen erlaubt (A-Za-z0-9)',preview:'Vorschau',currentValue:'Aktueller Wert',managedByProxmox:'von Proxmox verwaltet',willBeAutoGenerated:'Wird beim Speichern automatisch generiert',forceConntrack:'Force (Conntrack State)',forceConntrackDesc:'Erzwingt Migration auch wenn Conntrack-Einträge existieren',containerNoLiveMigration:'Container unterstützen keine echte Live-Migration',startingMigration:'Starte Migration von',migrationStarted:'Migration gestartet:',migrationFailed:'Migration fehlgeschlagen',clone:'Klonen',console:'Konsole',openConsole:'Konsole öffnen',metrics:'Metriken',config:'Konfiguration',configuration:'Konfiguration',createVm:'Neue VM erstellen',createContainer:'Neuen Container erstellen',newVm:'Neue VM',newContainer:'Neuer Container',power:'Energieoptionen',snapshot:'Snapshot',editSettings:'Einstellungen',refreshData:'Aktualisieren',sshConsole:'SSH-Konsole',noNodesAvailable:'Keine Nodes verfügbar. Bitte warten Sie bis die Cluster-Daten geladen sind.',loadingStorage:'Lade Storage-Liste...',noIsoAvailable:'Keine ISO-Images gefunden',noTemplateAvailable:'Keine Templates gefunden',noStorageAvailable:'Keine Storage verfügbar',noIsoStorage:'Kein Storage mit ISO-Inhalt gefunden',ram:'RAM',cpu:'CPU',disk:'Disk',network:'Netzwerk',// VM Creation Wizard @@ -2136,7 +2136,7 @@ poolPermissions:'Pool Permissions',poolPermissionsDesc:'Grant users or groups access to Proxmox resource pools. Permissions apply to all VMs within the pool.',managePools:'Manage Pools',poolManagerDesc:'Create, edit, and delete resource pools. Assign VMs to pools for organized permission management.',createPool:'Create Pool',editPool:'Edit Pool',deletePool:'Delete Pool',poolId:'Pool ID',poolIdRequired:'Pool ID is required',poolIdHint:'Letters, numbers, dashes and underscores only',poolIdCannotChange:'Pool ID cannot be changed',poolCreated:'Pool created successfully',poolUpdated:'Pool updated successfully',poolDeleted:'Pool deleted successfully',confirmDeletePool:'Are you sure you want to delete this pool? This cannot be undone.',noPoolsYet:'No pools yet',createFirstPool:'Create your first pool to organize VMs',poolMembers:'Members',members:'members',addVmToPool:'Add VM to Pool',assignToPool:'Assign to Pool',removeFromPool:'Remove from pool',vmAddedToPool:'VM added to pool',vmRemovedFromPool:'VM removed from pool',confirmRemoveVmFromPool:'Remove VM from this pool?',selectVmToAdd:'Select a VM to add to this pool',allVmsInPools:'All VMs are already in pools',optionalDescription:'Optional description...',userPermissions:'User Permissions',selectPool:'Select Pool',noPools:'No resource pools found in this cluster',noPoolPerms:'No permissions configured for this pool',selectPoolFirst:'Select a cluster and pool to manage permissions',addPoolPerm:'Add Pool Permission',editPoolPerm:'Edit Pool Permission',poolPermSaved:'Pool permission saved',poolPermDeleted:'Pool permission removed',refreshPools:'Refresh pools from Proxmox',poolCacheRefreshed:'Pool cache refreshed',confirmDeletePoolPerm:'Remove permission?',subjectType:'Type',permissionsFor:'Permissions for',addPermission:'Add Permission',groupName:'Group Name',auditLog:'Audit Log',auditLogDescription:'All user actions from the last 90 days',noAuditLogs:'No audit entries available',action:'Action',details:'Details',timestamp:'Timestamp',ipAddress:'IP Address',userCreated:'User created',userUpdated:'User updated',userDeleted:'User deleted',userLogin:'Login',userLogout:'Logout',passwordChanged:'Password changed',clusterAdded:'Cluster added',apiTokenWarningTitle:'API Token Authentication',apiTokenWarningDesc:'Without a password, SSH features won\'t work (HA, Rolling Updates, SMBIOS, Node Shell). Recommended: Use root@pam + password — PegaProx auto-creates an API token for 2FA compatibility.',apiTokenRecommended:'Recommended when 2FA is enabled',apiTokenCreated:'API token created on PVE — 2FA can now be safely enabled',sshPasswordStillNeeded:'SSH still uses the password (HA, maintenance) — don\'t change it',authModeToken:'API: Token (2FA-safe)',authModePassword:'API: Password',sshAuthMode:'SSH: Password',dontChangePvePassword:'Don\'t change the PVE password without updating it here',clusterDeleted:'Cluster deleted',clusterConfigChanged:'Cluster config changed',vmStarted:'VM started',vmStopped:'VM stopped',vmRestarted:'VM restarted',vmCreated:'VM created',vmDeleted:'VM deleted',vmCloned:'VM cloned',vmMigrated:'VM migrated',vmUnlocked:'VM unlocked',vmLocked:'VM locked',unlockVm:'Unlock VM',lockReason:'Lock Reason',unlockWarning:'Warning: Unlocking a VM during an active operation may cause data corruption or other issues. Only proceed if you are sure the operation has failed or been cancelled.',vmBulkMigrated:'Bulk migration',vmConfigChanged:'VM config changed',vmSuspended:'VM suspended',vmResumed:'VM resumed',vmDiskAdded:'Disk added',vmDiskRemoved:'Disk removed',vmDiskResized:'Disk resized',vmDiskMoved:'Disk moved',vmNetworkAdded:'Network added',vmNetworkRemoved:'Network removed',vmNetworkUpdated:'Network updated',removeDiskConfirm:'Really remove disk',detachDisk:'Detach',importDisk:'Import Disk',importDiskDesc:'Import existing disk image from storage',selectImportStorage:'Select Source Storage',selectDiskImage:'Select Disk Image',targetBus:'Target Bus',reassignOwner:'Reassign Owner',reassignOwnerDesc:'Assign disk to a different VM',targetVm:'Target VM',diskReassigned:'Disk reassigned',diskImported:'Disk imported',noImportableDisks:'No importable disk images found',reassign:'Reassign',import:'Import',detachDiskConfirm:'Really detach disk? It will become an unused disk.',diskDetached:'Disk detached',diskDeleted:'Disk deleted',diskAdded:'Disk added',dataWillBeDeleted:'Data will be permanently deleted!',removeNetworkConfirm:'Really remove network',snapshotCreated:'Snapshot created',snapshotDeleted:'Snapshot deleted',snapshotRestored:'Snapshot restored',rollbackStarted:'Rollback started',includeRam:'Include RAM',snapshotName:'Snapshot name',rollbackConfirm:'Really rollback to this snapshot? This cannot be undone!',replicationCreated:'Replication created',replicationDeleted:'Replication deleted',replicationTriggered:'Replication triggered',haEnabled:'HA enabled',haDisabled:'HA disabled',noFallbackHosts:'No fallback hosts found (Single-Node Cluster?)',haVmAdded:'VM added to HA',haVmRemoved:'VM removed from HA',nodeMaintenanceEntered:'Maintenance mode entered',nodeMaintenanceExited:'Maintenance mode exited',nodeUpdateStarted:'Node update started',twoFactorAuth:'Two-Factor Authentication',twoFactorEnabled:'2FA enabled',twoFactorDisabled:'2FA disabled',enable2FA:'Enable 2FA',disable2FA:'Disable 2FA',setup2FA:'Setup 2FA',scan2FACode:'Scan QR code with authenticator app',enter2FACode:'Enter 6-digit code',verify2FA:'Verify',secretKey:'Secret Key',twoFARequired:'2FA code required',invalid2FACode:'Invalid 2FA code',force2FA:'Enforce 2FA',force2FADesc:'Require all users to set up Two-Factor Authentication before they can use PegaProx.',force2FAHint:'OIDC/Entra users are exempt (they use their Identity Provider\'s MFA). Users without 2FA will see a setup dialog on login.',force2FAExcludeAdmins:'Exclude admin accounts',force2FAExcludeAdminsDesc:'Admins can use PegaProx without 2FA',force2FASetupTitle:'2FA Setup Required',force2FASetupDesc:'Your administrator has made Two-Factor Authentication mandatory for all users. Please set up 2FA to continue.',resetPassword:'Reset Password',passwordManagedExternally:'Your password is managed externally.',passwordManagedExternallyHint:'Please change your password directly in your directory service.',newPassword:'New Password',confirmPassword:'Confirm Password',currentPassword:'Current Password',passwordsDoNotMatch:'Passwords do not match',passwordTooShort:'Password must be at least 4 characters',passwordResetSuccess:'Password changed successfully',myProfile:'My Profile',security:'Security',snapshotNotSupported:'Snapshots not supported',snapshotWarnings:'Snapshot warnings',filterByUser:'Filter by user',filterByAction:'Filter by action',allUsers:'All Users',allActions:'All Actions',exportAuditLog:'Export',refreshAuditLog:'Refresh',// Header addCluster:'Add Cluster',addXcpngPool:'Add XCP-ng Pool (Tech Preview)',xcpngConnectHint:'Connect to the pool master host. XAPI port 443 is used by default.',xcpngTechPreviewNote:'Some features may be limited or subject to change.',pveClusterDesc:'Virtual machines & containers',pbsDesc:'Backup management',vmwareDesc:'ESXi infrastructure',xcpngDesc:'XCP-ng / Xen Hypervisor (Tech Preview)',addConnection:'Add Connection',connectionType:'Connection Type',clusterManagement:'PegaProx Cluster Management for Proxmox VE',// Tabs overview:'Overview',resources:'Resources',datacenter:'Datacenter',settings:'Settings',of:'of',showing:'Showing',perPage:'Per page',loadingDatacenter:'Loading datacenter data...',// Cluster -clusters:'Clusters',noClusterSelected:'No Cluster Selected',allClustersOverview:'All Clusters Overview',allClusters:'All Clusters',multiClusterSummary:'Summary of all managed clusters',clustersConnected:'Clusters Connected',clusterOverview:'Cluster Overview',vmsRunning:'VMs Running',vmsStopped:'VMs Stopped',noClustersConfigured:'No clusters configured',addClusterToStart:'Add a cluster to get started',clickClusterTip:'Click on a cluster row or select from the sidebar to view detailed information and manage resources.',health:'Health',average:'Average',noDataAvailable:'No data available',clickToManage:'Click to manage',sortBy:'Sort by',ungrouped:'Ungrouped',alerts:'Alerts',updated:'Updated',justNow:'just now',topResources:'Top Resources',highestCpuUsage:'Highest CPU and RAM usage across all clusters',clickToOpenVm:'Click to open VM',selectCluster:'Select a cluster from the list',addFirstCluster:'Add First Cluster',connectCluster:'Connect a Proxmox cluster with PegaProx',clusterName:'Cluster Name',host:'Host',username:'Username',password:'Password',passwordOrToken:'Password / Token',apiTokenHint:'For API tokens: user@realm!tokenid',sslVerification:'SSL Verification',connecting:'Connecting...',addNewCluster:'Add New Cluster',testConnection:'Test Connection',deleteCluster:'Delete Cluster',deleteClusterConfirm:'Really delete cluster?',reconfigureCluster:'Re-configure Cluster',reconfigureHint:'Enter your PegaProx password to verify your identity.',clusterReconfigured:'Cluster re-configured successfully',reconfigure:'Re-configure',clusterHealth:'Cluster Health',excellent:'Excellent',good:'Good',warning:'Warning',critical:'Critical',nodesOnline:'Nodes Online',nodeJoinHint:'To add a new node, run on the new node:',avgScore:'Avg. Score',avgCpu:'Avg. CPU',avgRam:'Avg. RAM',// Nodes +clusters:'Clusters',noClusterSelected:'No Cluster Selected',allClustersOverview:'All Clusters Overview',allClusters:'All Clusters',multiClusterSummary:'Summary of all managed clusters',clustersConnected:'Clusters Connected',clusterOverview:'Cluster Overview',vmsRunning:'VMs Running',vmsStopped:'VMs Stopped',noClustersConfigured:'No clusters configured',addClusterToStart:'Add a cluster to get started',clickClusterTip:'Click on a cluster row or select from the sidebar to view detailed information and manage resources.',health:'Health',average:'Average',noDataAvailable:'No data available',clickToManage:'Click to manage',sortBy:'Sort by',ungrouped:'Ungrouped',alerts:'Alerts',updated:'Updated',justNow:'just now',topResources:'Top Resources',highestCpuUsage:'Highest CPU and RAM usage across all clusters',clickToOpenVm:'Click to open VM',selectCluster:'Select a cluster from the list',addFirstCluster:'Add First Cluster',connectCluster:'Connect a Proxmox cluster with PegaProx',clusterName:'Cluster Name',host:'Host',username:'Username',password:'Password',passwordOrToken:'Password / Token',apiTokenHint:'For API tokens: user@realm!tokenid',sslVerification:'SSL Verification',connecting:'Connecting...',addNewCluster:'Add New Cluster',testConnection:'Test Connection',deleteCluster:'Delete Cluster',deleteClusterConfirm:'Really delete cluster?',reconfigureCluster:'Re-configure Cluster',reconfigureHint:'Enter your PegaProx password to verify your identity.',clusterReconfigured:'Cluster re-configured successfully',reconfigure:'Re-configure',clusterHealth:'Cluster Health',clusterHealthTooltip:'Health = 100 − (CPU×30% + RAM×30% + Storage×20% + Offline Nodes×20%)\n\n80+: Excellent\n60–79: Good\n40–59: Warning\n< 40: Critical',nodeScoreTooltip:'Node score = CPU% + RAM% (lower is better)\n\n< 100: Good (green)\n100–150: Elevated (yellow)\n> 150: Critical (red)',excellent:'Excellent',good:'Good',warning:'Warning',critical:'Critical',nodesOnline:'Nodes Online',nodeJoinHint:'To add a new node, run on the new node:',avgScore:'Avg. Score',avgStorage:'Avg. Storage',avgCpu:'Avg. CPU',avgRam:'Avg. RAM',// Nodes nodes:'Nodes',node:'Node',loadingMetrics:'Loading metrics...',connectionError:'Connection Error',retry:'Retry',checkConnectionAndRetry:'Please check your connection and try again.',connectionTimeout:'Timeout - Proxmox unreachable',maintenance:'Maintenance',enterMaintenance:'Enter Maintenance Mode',exitMaintenance:'Exit Maintenance Mode',maintenanceMode:'Maintenance Mode',update:'Update',startUpdate:'Start Update',nodeConfig:'Node Configuration',cpuHistory:'CPU History',ramHistory:'RAM History',ramUsage:'RAM Usage',cpuUsage:'CPU Usage',diskUsage:'Disk Usage',allocated:'allocated',showMore:'Show more',showLess:'Show less',// VMs & Resources virtualMachines:'Virtual Machines',containers:'Containers',vm:'VM',lxc:'LXC',lxcContainer:'LXC Container',container:'Container',guests:'Guests',start:'Start',stop:'Stop',shutdown:'Shutdown',reboot:'Reboot',forceStop:'Force Stop',forceReset:'Force Reset',forceStopConfirm:'really force stop? This may cause data loss!',migrate:'Migrate',migrateVm:'Migrate VM',crossClusterMigration:'Cross-Cluster Migration',crossClusterMigrateDesc:'Migrate VM to another Proxmox cluster',crossClusterMigrate:'Cross-Cluster Migrate',crossClusterStarted:'Cross-Cluster Migration started',crossClusterFailed:'Cross-Cluster Migration failed',crossClusterLB:'Cross-Cluster Load Balancing',crossClusterLBEnabled:'Cross-Cluster LB enabled',crossClusterLBDisabled:'Cross-Cluster LB disabled',crossClusterLBThreshold:'Score Threshold',crossClusterLBInterval:'Check Interval',crossClusterLBDryRun:'Simulation (Dry Run)',crossClusterLBLastRun:'Last Run',crossClusterReplication:'Cross-Cluster Replication',crossClusterReplicationDesc:'Replicate VM snapshots to another cluster (DR)',groupOverview:'Group Overview',groupSettings:'Group Settings',groupSettingsSaved:'Group settings saved',replicationSchedule:'Schedule',replicationRetention:'Retention',maxMigrations:'Max Migrations per Cycle',lbHistory:'LB History',clusterScore:'Cluster Score',noReplicationJobs:'No cross-cluster replication jobs',createReplicationJob:'Create DR Job',replicationStarted:'Replication started',replicationJobCreated:'Replication job created',replicationJobDeleted:'Replication job deleted',recentLbActions:'Recent cross-cluster LB actions',noLbEvents:'No cross-cluster LB events yet',enableCrossClusterLB:'Enable Cross-Cluster Load Balancing',lbDescription:'Automatically migrate VMs between clusters when resource thresholds are exceeded',dryRunMode:'Dry Run / Simulation Mode',cpuThreshold:'CPU Threshold (%)',lbStatus:'Load Balancing Status',lbExplanation:'Cross-Cluster Load Balancing monitors CPU and RAM usage across all clusters in this group. When a cluster exceeds the configured threshold, VMs are automatically migrated to a less loaded cluster.',lbDryRunExplanation:'Enable Dry Run mode first to review what actions would be taken before enabling live migrations.',neverRun:'Never run',newCrossClusterReplication:'New Cross-Cluster Replication',confirmDeleteXRepl:'Delete this cross-cluster replication job?',addDrJob:'Add DR Job',scheduleCron:'Schedule (cron)',targetStorageHint:'Storage name on destination cluster',maxMigrationsHint:'Limit how many VMs are moved each check cycle (1-5)',proxlbCredit:'ProxLB by gyptazy',proxlbCreditDesc:'Our load balancing functionality is based on the excellent work from ProxLB. Special thanks to gyptazy for creating and open-sourcing this amazing tool!',xReplCreated:'Cross-cluster replication created',xReplDeleted:'Cross-cluster replication deleted',xReplStarted:'Cross-cluster replication started',xReplCreateFailed:'Failed to create job',xReplDeleteFailed:'Delete failed',xReplStartFailed:'Failed to start',lastRunPrefix:'Last',runNow:'Run now',loadingCrossClusterResources:'Loading storage/network for all clusters...',noCommonStorages:'No common storage found across all clusters',noCommonBridges:'No common bridge found across all clusters',selectBridge:'Select bridge...',crossClusterThresholdDesc:'CPU threshold for cluster imbalance (10-80%)',crossClusterIntervalDesc:'Time between check cycles',crossClusterMaxMigrationsDesc:'Max migrations per check cycle',commonStorageHint:'Only storages available on all clusters in this group',commonBridgeHint:'Only bridges available on all clusters in this group',includeContainers:'Include Containers',includeContainersDesc:'Include containers (LXC) in cross-cluster balancing',containerMigrationWarning:'Containers are restarted during migration (downtime)',excludedVMsCrossCluster:'Excluded VMs/Containers',excludedVMsCrossClusterDesc:'VMs and containers excluded from automatic cross-cluster balancing',clusterExcludedVMs:'Excluded VMs',noExcludedVMsInGroup:'No VMs excluded',selectCluster:'Select cluster...',selectClusterFirst:'Select a target cluster first',simulationMode:'Simulation Mode',sourceVm:'Source VM',targetCluster:'Target Cluster',targetNode:'Target Node',targetStorage:'Target Storage',targetBridge:'Target Network (Bridge)',moveDisk:'Move Disk',resizeDisk:'Resize Disk',diskResized:'Disk resized',deleteSourceDisk:'Delete source after move',deleteSourceDiskWarning:'The original disk will remain on the source storage. You can remove it manually later.',move:'Move',from:'from',noNetworkInterfaces:'No network interfaces',nameRequired:'Name required',newVmid:'New VMID (optional)',sameIdPlaceholder:'Empty = same ID',liveMigrationOption:'Live Migration (VM keeps running)',deleteSourceAfter:'Delete source VM after migration',largeDiskWarning:'Large Disk Detected',largeDiskExplanation:'Live migration for disks >100GB may fail with "401 Unauthorized" due to Proxmox WebSocket ticket timeout. The server will automatically use offline migration unless forced.',forceOnlineMigration:'Force online migration anyway (may fail)',autoTokenInfo:'PegaProx automatically creates temporary API tokens for migration and deletes them after completion.',clusterReachableInfo:'Clusters must be able to reach each other over the network. The configured user must have permissions to create API tokens.',selectCluster:'Select cluster...',selectNode:'Select Node',selectStorage:'Select storage...',loadingNodes:'Loading target nodes...',loadingStorageNetwork:'Loading storage/network...',liveMigration:'Live Migration (no downtime)',on:'on',targetStorage:'Target Storage',sameAsSource:'Same as source',loadingStorages:'Loading storages',free:'free',withLocalDisks:'With Local Disks',withLocalDisksDesc:'Migrate local disks to target storage',localDisksDetected:'This VM has local disks. Migration requires copying disk data.',requiredForThisVm:'Required for this VM',cdDvdMounted:'CD/DVD Drive Mounted',cdDvdMigrationWarning:'Live migration is not possible with a CD/DVD mounted. Please eject the CD/DVD first or use offline migration.',isoMounted:'ISO/CD-ROM Mounted',isoEjected:'CD-ROM Ejected',isoMigrationWarning:'Migration may fail if the ISO is not available on the target node. Eject the CD/DVD or ensure the ISO exists on shared storage.',localStorage:'Local',bootOrderIssue:'Boot Order Issue',bootOrderWarning:'Boot order references non-existent disks',bootOrder:'Boot Order',dragToReorder:'Click to toggle, use arrows to reorder',noBootDevices:'No boot devices found',resizeDiskHint:'Increase by (e.g. +10G) or new size',// SMBIOS Settings smbiosSettings:'SMBIOS Settings',smbiosHint:'System Management BIOS - useful for Windows licensing and VM identification',applySmbiosFromClusterConfig:'Apply SMBIOS settings from cluster configuration',requiresRestart:'RESTART',readOnly:'read-only',autoGenerated:'auto-generated',smbiosFormatHint:'Only letters and numbers allowed (A-Za-z0-9)',preview:'Preview',currentValue:'Current Value',managedByProxmox:'managed by Proxmox',willBeAutoGenerated:'Will be auto-generated on save',forceConntrack:'Force (Conntrack State)',forceConntrackDesc:'Force migration even if conntrack entries exist',containerNoLiveMigration:'Containers do not support true live migration',startingMigration:'Starting migration of',migrationStarted:'Migration started:',migrationFailed:'Migration failed',clone:'Clone',console:'Console',openConsole:'Open Console',metrics:'Metrics',config:'Configuration',configuration:'Configuration',createVm:'Create new VM',createContainer:'Create new Container',newVm:'New VM',newContainer:'New Container',power:'Power',snapshot:'Snapshot',editSettings:'Settings',refreshData:'Refresh',sshConsole:'SSH Console',noNodesAvailable:'No nodes available. Please wait for cluster data to load.',loadingStorage:'Loading storage list...',noIsoAvailable:'No ISO images found',noTemplateAvailable:'No templates found',noStorageAvailable:'No storage available',noIsoStorage:'No storage with ISO content found',ram:'RAM',cpu:'CPU',disk:'Disk',network:'Network',// VM Creation Wizard @@ -2225,7 +2225,7 @@ poolPermissions:'Permissions de Pool',poolPermissionsDesc:'Accordez aux utilisateurs ou groupes l\'accès aux pools Proxmox. Les permissions s\'appliquent à tous les VMs du groupe.',managePools:'Gérer les groupes',poolManagerDesc:'Créer, modifier et supprimer des pools. Attribuer les VMs au sein des groupes pour une gestion des autorisations organisée.',createPool:'Créer un Pool',editPool:'Modifier le Pool',deletePool:'Supprimer le pool',poolId:'ID du pool',poolIdRequired:'L\'ID du pool est requis.',poolIdHint:'Lettres, chiffres, tirets et soulignements seulement',poolIdCannotChange:'L\'ID de la piscine ne peut pas être modifié.',poolCreated:'Pool créé avec succès',poolUpdated:'Le pool a été mis à jour avec succès.',poolDeleted:'Pool supprimé avec succès',confirmDeletePool:'Êtes-vous sûr de vouloir supprimer ce pool ? Cela ne peut pas être annulé.',noPoolsYet:'Aucun pool pour l\'instant',createFirstPool:'Créez votre premier pool pour organiser les VMs',poolMembers:'Membres',members:'membres',addVmToPool:'Ajouter une VM au pool',assignToPool:'Affecter au pool',removeFromPool:'Retirer du pool',vmAddedToPool:'Une VM a été ajoutée au pool.',vmRemovedFromPool:'Une VM a été retirée du pool',confirmRemoveVmFromPool:'Retirer la VM de ce pool ?',selectVmToAdd:'Sélectionnez une VM à ajouter à ce pool',allVmsInPools:'Toutes les VMs sont déjà dans des pools',optionalDescription:'Description facultative...',userPermissions:'Permissions Utilisateur',selectPool:'Sélectionnez le Pool',noPools:'Aucun groupe de ressources trouvé dans ce cluster',noPoolPerms:'Aucune permission configurée pour ce pool',selectPoolFirst:'Sélectionnez un cluster et un pool pour gérer les autorisations',addPoolPerm:'Ajouter une permission de pool',editPoolPerm:'Modifier les permissions du pool',poolPermSaved:'Permission de groupe enregistrée',poolPermDeleted:'Permission de pool supprimée',refreshPools:'Rafraîchir les pools depuis Proxmox',poolCacheRefreshed:'Le cache du pool a été mis à jour',confirmDeletePoolPerm:'Supprimer cette permission ?',subjectType:'Type',permissionsFor:'Permissions pour',addPermission:'Ajouter une permission',groupName:'Nom du groupe',auditLog:'Journal d\'audit',auditLogDescription:'Toutes les actions des utilisateurs au cours des 90 derniers jours',noAuditLogs:'Aucune entrée d\'audit disponible',action:'Action',details:'Détails',timestamp:'Horodatage',ipAddress:'Adresse IP',userCreated:'Utilisateur créé',userUpdated:'Utilisateur mis à jour',userDeleted:'Utilisateur supprimé',userLogin:'Se connecter',userLogout:'Déconnexion',passwordChanged:'Mot de passe modifié',clusterAdded:'Cluster ajouté',apiTokenWarningTitle:'Authentification par jeton API',apiTokenWarningDesc:'Sans mot de passe, les fonctionnalités SSH ne fonctionneront pas (HA, Mises à jour enroulées, SMBIOS, Shell du nœud). Recommandé : Utilisez root@pam + mot de passe — PegaProx crée automatiquement un jeton API pour la compatibilité avec le 2FA.',apiTokenRecommended:'Recommandé lorsque l\'authentification à deux facteurs est activée',apiTokenCreated:'Un jeton API créé sur Proxmox VE — la 2FA peut maintenant être activée en toute sécurité.',sshPasswordStillNeeded:'SSH utilise toujours le mot de passe (HA, maintenance) - ne pas le changer',authModeToken:'API : Jeton (sécurisé 2FA)',authModePassword:'API : Mot de passe',sshAuthMode:'SSH : Mot de passe',dontChangePvePassword:'Ne pas changer le mot de passe PVE sans le changer ici aussi',clusterDeleted:'Cluster supprimé',clusterConfigChanged:'La configuration du cluster a changé.',vmStarted:'La VM a démarré',vmStopped:'VM arrêtée',vmRestarted:'La machine virtuelle a été redémarrée.',vmCreated:'Une VM a été créée.',vmDeleted:'VM supprimée',vmCloned:'VM clonée',vmMigrated:'VM migrée',vmUnlocked:'VM déverrouillée',vmLocked:'La machine virtuelle est verrouillée.',unlockVm:'Déverrouiller la VM',lockReason:'Raison du verrouillage',unlockWarning:'Attention : Déverrouiller une VM pendant une opération active peut entraîner des dommages aux données ou d\'autres problèmes. Ne procédez que si vous êtes sûr que l\'opération a échoué ou été annulée.',vmBulkMigrated:'Migration en masse',vmConfigChanged:'La configuration de la VM a changé.',vmSuspended:'VM suspendue',vmResumed:'La VM a été reprise.',vmDiskAdded:'Disque ajouté',vmDiskRemoved:'Disque supprimé',vmDiskResized:'Disque redimensionné',vmDiskMoved:'Disque déplacé',vmNetworkAdded:'Réseau ajouté',vmNetworkRemoved:'Réseau supprimé',vmNetworkUpdated:'Réseau mis à jour',removeDiskConfirm:'Voulez-vous vraiment supprimer le disque ?',detachDisk:'Détacher',importDisk:'Importer un disque',importDiskDesc:'Importer une image de disque existante depuis le stockage',selectImportStorage:'Sélectionner le stockage source',selectDiskImage:'Sélectionnez l\'image du disque',targetBus:'Bus cible',reassignOwner:'Transférer le propriétaire',reassignOwnerDesc:'Attribuez un disque à une autre machine virtuelle',targetVm:'VM cible',diskReassigned:'Disque réaffecté',diskImported:'Disque importé',noImportableDisks:'Aucune image de disque importable trouvée',reassign:'Réaffecter',import:'Importer',detachDiskConfirm:'Voulez-vous vraiment détacher le disque ? Il deviendra un disque inutilisé.',diskDetached:'Disque détaché',diskDeleted:'Disque supprimé',diskAdded:'Disque ajouté',dataWillBeDeleted:'Les données seront définitivement supprimées!',removeNetworkConfirm:'Voulez-vous vraiment supprimer le réseau ?',snapshotCreated:'Point de restauration créé',snapshotDeleted:'Point de restauration supprimé',snapshotRestored:'Le point de restauration a été restauré.',rollbackConfirm:'Voulez-vous vraiment revenir à ce point de restauration ? Cela ne peut pas être annulé !',replicationCreated:'Réplication créée',replicationDeleted:'La réplication a été supprimée.',replicationTriggered:'Réplication déclenchée',haEnabled:'HA Activé',haDisabled:'HA désactivé',noFallbackHosts:'Aucun hôte de secours trouvé (Cluster à un nœud ?)',haVmAdded:'Une VM a été ajoutée au HA',haVmRemoved:'VM retirée de l\'HA',nodeMaintenanceEntered:'Mode de maintenance entré',nodeMaintenanceExited:'Mode de maintenance sorti',nodeUpdateStarted:'Mise à jour du nœud démarrée',twoFactorAuth:'Authentification à deux facteurs',twoFactorEnabled:'2FA activé',twoFactorDisabled:'2FA désactivé',enable2FA:'Activer la 2FA',disable2FA:'Désactiver 2FA',setup2FA:'Configurer 2FA',scan2FACode:'Scanner le code QR avec l\'application d\'authentification',enter2FACode:'Entrez un code à 6 chiffres',verify2FA:'Vérifier',secretKey:'Clé secrète',twoFARequired:'Code 2FA requis',invalid2FACode:'Code 2FA invalide',force2FA:'Imposez la 2FA',force2FADesc:'Tous les utilisateurs doivent configurer l\'authentification à deux facteurs avant de pouvoir utiliser PegaProx.',force2FAHint:'Les utilisateurs OIDC/Entra sont exemptés (ils utilisent la MFA de leur fournisseur d\'identité). Les utilisateurs sans 2FA verront un assistant de configuration à l\'inscription.',force2FAExcludeAdmins:'Exclure les comptes administrateurs',force2FAExcludeAdminsDesc:'Les administrateurs peuvent utiliser PegaProx sans 2FA',force2FASetupTitle:'Configuration de la 2FA requise',force2FASetupDesc:'Votre administrateur a rendu l\'authentification à deux facteurs obligatoire pour tous les utilisateurs. Veuillez configurer 2FA pour continuer.',resetPassword:'Réinitialiser le mot de passe',passwordManagedExternally:'Votre mot de passe est géré en externe.',passwordManagedExternallyHint:'Veuillez modifier votre mot de passe directement dans votre service d\'annuaire.',newPassword:'Nouveau mot de passe',confirmPassword:'Confirmer le mot de passe',currentPassword:'Mot de passe actuel',passwordsDoNotMatch:'Les mots de passe ne correspondent pas.',passwordTooShort:'Le mot de passe doit comporter au moins 4 caractères.',passwordResetSuccess:'Mot de passe modifié avec succès',myProfile:'Mon profil',security:'sécurité',snapshotNotSupported:'Les point de restauration ne sont pas pris en charge.',snapshotWarnings:'Avertissements sur le point de restauration',filterByUser:'Filtrer par utilisateur',filterByAction:'Filtrer par action',allUsers:'Tous les utilisateurs',allActions:'Toutes les actions',exportAuditLog:'Exporter',refreshAuditLog:'Actualiser',// Header addCluster:'Ajouter un Cluster',addXcpngPool:'Ajouter un Pool XCP-ng (Préversion Technique)',xcpngConnectHint:'Connectez-vous au serveur maître du pool. Le port XAPI 443 est utilisé par défaut.',xcpngTechPreviewNote:'Certaines fonctionnalités peuvent être limitées ou sujettes à modification.',pveClusterDesc:'Machines virtuelles et conteneurs',pbsDesc:'Gestion des sauvegardes',vmwareDesc:'Infrastructure ESXi',xcpngDesc:'XCP-ng / Hyperviseur Xen (Prévisualisation technique)',addConnection:'Ajouter une Connexion',connectionType:'Type de connexion',clusterManagement:'Gestion de cluster',// Tabs overview:'Vue d\\',resources:'Ressources',datacenter:'Centre de données',settings:'Paramètres',of:'de',showing:'Affichant',perPage:'Par page',loadingDatacenter:'Chargement...',// Cluster -clusters:'Clusters',noClusterSelected:'Aucun cluster sélectionné',allClustersOverview:'Vue d\'ensemble de tous les clusters',allClusters:'Tous les clusters',multiClusterSummary:'Résumé de tous les clusters gérés',clustersConnected:'Clusters Connectés',clusterOverview:'Vue d\'ensemble du cluster',vmsRunning:'VMs en cours d\'exécution',vmsStopped:'Les VMs ont été arrêtées.',noClustersConfigured:'Aucun cluster configuré',addClusterToStart:'Ajoutez un cluster pour commencer',clickClusterTip:'Cliquez sur une ligne de cluster ou sélectionnez dans la barre latérale pour afficher des informations détaillées et gérer les ressources.',health:'Santé',average:'Moyenne',noDataAvailable:'Aucune donnée disponible',clickToManage:'Cliquer pour gérer',sortBy:'Trier par',ungrouped:'Non regroupé',alerts:'Alertes',updated:'Mis à jour',justNow:'Il y a quelques instants',topResources:'Ressources principales',highestCpuUsage:'Utilisation maximale de CPU et de RAM sur tous les clusters',clickToOpenVm:'Cliquez pour ouvrir la VM',selectCluster:'Sélectionnez un cluster de stockage',addFirstCluster:'Ajouter le Premier Cluster',connectCluster:'Connectez un cluster Proxmox avec PegaProx',clusterName:'Nom du cluster',host:'Serveur',username:'Nom d\\',password:'Mot de passe',passwordOrToken:'Mot de passe / Jeton',apiTokenHint:'Pour les jetons API : utilisateur@domaine!identifiant_token',sslVerification:'Vérification SSL',connecting:'Connexion...',addNewCluster:'Ajouter un Nouveau Cluster',testConnection:'Tester la Connexion',deleteCluster:'Supprimer le cluster',deleteClusterConfirm:'Supprimer vraiment le cluster ?',reconfigureCluster:'Reconfigurer le cluster',reconfigureHint:'Entrez votre mot de passe PegaProx pour vérifier votre identité.',clusterReconfigured:'Cluster reconfiguré avec succès',reconfigure:'Reconfigurer',clusterHealth:'Santé du Cluster',excellent:'Excellente',good:'Bien',warning:'Avertissement',critical:'Critique',nodesOnline:'Nœuds en ligne',nodeJoinHint:'Pour ajouter un nouveau nœud, exécutez sur le nouveau nœud :',avgScore:'Moyenne des notes',avgCpu:'Moyenne CPU',avgRam:'Moyenne de la RAM',// Nodes +clusters:'Clusters',noClusterSelected:'Aucun cluster sélectionné',allClustersOverview:'Vue d\'ensemble de tous les clusters',allClusters:'Tous les clusters',multiClusterSummary:'Résumé de tous les clusters gérés',clustersConnected:'Clusters Connectés',clusterOverview:'Vue d\'ensemble du cluster',vmsRunning:'VMs en cours d\'exécution',vmsStopped:'Les VMs ont été arrêtées.',noClustersConfigured:'Aucun cluster configuré',addClusterToStart:'Ajoutez un cluster pour commencer',clickClusterTip:'Cliquez sur une ligne de cluster ou sélectionnez dans la barre latérale pour afficher des informations détaillées et gérer les ressources.',health:'Santé',average:'Moyenne',noDataAvailable:'Aucune donnée disponible',clickToManage:'Cliquer pour gérer',sortBy:'Trier par',ungrouped:'Non regroupé',alerts:'Alertes',updated:'Mis à jour',justNow:'Il y a quelques instants',topResources:'Ressources principales',highestCpuUsage:'Utilisation maximale de CPU et de RAM sur tous les clusters',clickToOpenVm:'Cliquez pour ouvrir la VM',selectCluster:'Sélectionnez un cluster de stockage',addFirstCluster:'Ajouter le Premier Cluster',connectCluster:'Connectez un cluster Proxmox avec PegaProx',clusterName:'Nom du cluster',host:'Serveur',username:'Nom d\\',password:'Mot de passe',passwordOrToken:'Mot de passe / Jeton',apiTokenHint:'Pour les jetons API : utilisateur@domaine!identifiant_token',sslVerification:'Vérification SSL',connecting:'Connexion...',addNewCluster:'Ajouter un Nouveau Cluster',testConnection:'Tester la Connexion',deleteCluster:'Supprimer le cluster',deleteClusterConfirm:'Supprimer vraiment le cluster ?',reconfigureCluster:'Reconfigurer le cluster',reconfigureHint:'Entrez votre mot de passe PegaProx pour vérifier votre identité.',clusterReconfigured:'Cluster reconfiguré avec succès',reconfigure:'Reconfigurer',clusterHealth:'Santé du Cluster',clusterHealthTooltip:'Santé = 100 − (CPU×30% + RAM×30% + Stockage×20% + Nœuds hors ligne×20%)\n\n80+ : Excellente\n60–79 : Bien\n40–59 : Avertissement\n< 40 : Critique',nodeScoreTooltip:'Score nœud = CPU% + RAM% (plus bas est mieux)\n\n< 100 : Bon (vert)\n100–150 : Élevé (jaune)\n> 150 : Critique (rouge)',excellent:'Excellente',good:'Bien',warning:'Avertissement',critical:'Critique',nodesOnline:'Nœuds en ligne',nodeJoinHint:'Pour ajouter un nouveau nœud, exécutez sur le nouveau nœud :',avgScore:'Moyenne des notes',avgStorage:'Moy. Stockage',avgCpu:'Moyenne CPU',avgRam:'Moyenne de la RAM',// Nodes nodes:'Nœuds',node:'Nœud',loadingMetrics:'Chargement...',connectionError:'Erreur de connexion',retry:'Réessayer',checkConnectionAndRetry:'Veuillez check your connection and try again.',connectionTimeout:'Délai d\'expiration - Proxmox inaccessible',maintenance:'Maintenance',enterMaintenance:'Entrez en mode maintenance',exitMaintenance:'Sortir du mode maintenance',maintenanceMode:'Mode de maintenance',update:'Mettre à jour',startUpdate:'Commencer la mise à jour',nodeConfig:'Configuration du nœud',cpuHistory:'Historique du processeur',ramHistory:'Historique de la RAM',ramUsage:'Utilisation RAM',cpuUsage:'Utilisation CPU',diskUsage:'Utilisation du disque',showMore:'Montrer plus',showLess:'Montrer moins',// VMs & Resources virtualMachines:'Machines virtuelles',containers:'Conteneurs',vm:'VM',lxc:'LXC',lxcContainer:'Conteneur LXC',container:'Conteneur',guests:'Clients',start:'Démarrer',stop:'Arrêtez',shutdown:'Arrêter',reboot:'Redémarrer',forceStop:'Forcer l\'arrêt',forceReset:'Forcer le redémarrage',forceStopConfirm:'Forcer l\'arrêt ? Cela peut causer une perte de données !',migrate:'Migrer',migrateVm:'Migrer la VM',crossClusterMigration:'Migration inter-cluster',crossClusterMigrateDesc:'Migrer une VM vers un autre cluster Proxmox',crossClusterMigrate:'Migration entre clusters',crossClusterStarted:'La migration entre clusters a commencé.',crossClusterFailed:'La migration entre clusters a échoué.',crossClusterLB:'Équilibrage de charge inter-cluster',crossClusterLBEnabled:'Balanceur de charge entre clusters activé',crossClusterLBDisabled:'Désactivation de l\'équilibreur de charge entre clusters',crossClusterLBThreshold:'Seuil de notation',crossClusterLBInterval:'Intervalle de vérification',crossClusterLBDryRun:'Simulation (Dry Run)',crossClusterLBLastRun:'Dernière Exécution',crossClusterReplication:'Réplication Inter-Clustère',crossClusterReplicationDesc:'Répliquer les point de restauration de VM vers un autre cluster (DR)',groupOverview:'Vue d\'ensemble du groupe',groupSettings:'Paramètres du groupe',groupSettingsSaved:'Paramètres du groupe enregistrés',replicationSchedule:'Planification',replicationRetention:'Retenue',maxMigrations:'Max Migrations par Cycle',lbHistory:'Historique du Load Balancer',clusterScore:'Score du Cluster',noReplicationJobs:'Aucun Job de Réplication',createReplicationJob:'Créer une tâche de réplication',replicationStarted:'La réplication a commencé.',replicationJobCreated:'Tâche de réplication créée',replicationJobDeleted:'Tâche de réplication supprimée',recentLbActions:'Actions récentes de load balancing entre clusters',noLbEvents:'Aucun événement de load balancing inter-cluster',enableCrossClusterLB:'Activer le équilibrage de charge inter-clusters',lbDescription:'Migrer automatiquement les VMs entre les clusters lorsque les seuils de ressources sont dépassés',dryRunMode:'Mode de simulation / Exécution sèche',cpuThreshold:'Seuil de processeur (%)',lbStatus:'État du Balanceur de charge',lbExplanation:'Le bilan de charge inter-cluster surveille l\'utilisation du CPU et de la RAM sur tous les clusters de ce groupe. Lorsque un cluster dépasse le seuil configuré, les VMs sont automatiquement migrées vers un cluster moins chargé.',lbDryRunExplanation:'Activer d\'abord le mode de simulation pour examiner les actions qui seraient prises avant d\'activer les migrations en direct.',neverRun:'N\'exécutez jamais',newCrossClusterReplication:'Nouvelle Réplication Inter-Clustère',confirmDeleteXRepl:'Supprimer ce job de réplication inter-cluster ?',addDrJob:'Ajouter une tâche de réplication',scheduleCron:'Planification (cron)',targetStorageHint:'Nom du stockage sur le cluster de destination',maxMigrationsHint:'Limitez le nombre de VMs déplacées chaque cycle de vérification (1-5)',proxlbCredit:'ProxLB par gyptazy',proxlbCreditDesc:'Notre fonctionnalité de balanceur de charge est basée sur le travail exceptionnel de ProxLB. Nous tenons à remercier gyptazy pour avoir créé et open-sourced cet outil incroyable !',xReplCreated:'Réplication entre clusters créée',xReplDeleted:'La réplication entre clusters a été supprimée.',xReplStarted:'La réplication entre clusters a commencé.',xReplCreateFailed:'Échec de la création du job',xReplDeleteFailed:'Suppression échouée',xReplStartFailed:'Échec du démarrage',lastRunPrefix:'Dernier',runNow:'Exécuter maintenant',loadingCrossClusterResources:'Chargement du stockage/réseau pour tous les clusters…',noCommonStorages:'Aucun stockage commun trouvé dans tous les clusters',noCommonBridges:'Aucun pont commun trouvé dans tous les clusters',selectBridge:'Sélectionnez le pont...',crossClusterThresholdDesc:'Seuil de CPU pour l\'équilibre du cluster (10-80%)',crossClusterIntervalDesc:'Intervalle entre les cycles de vérification',crossClusterMaxMigrationsDesc:'Nombre maximal de migrations par cycle de vérification',commonStorageHint:'Seulement les stockages disponibles sur tous les clusters de ce groupe',commonBridgeHint:'Seulement les ponts disponibles sur tous les clusters de ce groupe',includeContainers:'Inclure les Conteneurs',includeContainersDesc:'Inclure les conteneurs (LXC) dans l\'équilibrage inter-cluster',containerMigrationWarning:'Les conteneurs sont redémarrés pendant la migration (temps d\'arrêt)',excludedVMsCrossCluster:'VMs/Conteneurs exclus',excludedVMsCrossClusterDesc:'Les VMs et les conteneurs exclus de l\'équilibrage automatique entre les clusters',clusterExcludedVMs:'VMs exclues',noExcludedVMsInGroup:'Aucune VM exclue',selectCluster:'Sélectionnez un cluster de stockage',selectClusterFirst:'Veuillez sélectionner d\'abord un cluster cible.',simulationMode:'Mode de simulation',sourceVm:'Source VM',targetCluster:'Cluster cible',targetNode:'Nœud cible',targetStorage:'Stockage cible',targetBridge:'Réseau cible (Bridge)',moveDisk:'Déplacer le disque dur',resizeDisk:'Redimensionner le disque dur',diskResized:'Disque redimensionné',deleteSourceDisk:'Supprimer la source après le déplacement',deleteSourceDiskWarning:'Le disque original restera sur le stockage source. Vous pouvez le supprimer manuellement plus tard.',move:'Déplacer',from:'de',noNetworkInterfaces:'Aucune interface réseau',nameRequired:'Nom requis',newVmid:'Nouvel ID de machine virtuelle (facultatif)',sameIdPlaceholder:'Vide = même ID',liveMigrationOption:'Migration en direct (la VM continue d\'exécuter)',deleteSourceAfter:'Supprimer la VM source après la migration',largeDiskWarning:'Disque de grande taille détecté',largeDiskExplanation:'La migration en direct des disques >100GB peut échouer avec "401 Unauthorized" en raison du timeout du ticket WebSocket de Proxmox. Le serveur utilisera automatiquement la migration hors ligne sauf s\'il est forcé.',forceOnlineMigration:'Forcer la migration en ligne même si elle échoue',autoTokenInfo:'PegaProx crée automatiquement des jetons API temporaires pour la migration et les supprime après la complétion.',clusterReachableInfo:'Les clusters doivent pouvoir se connecter entre eux au réseau. L\'utilisateur configuré doit avoir les autorisations pour créer des jetons API.',selectCluster:'Sélectionnez un cluster de stockage',selectNode:'-- Sélectionnez un nœud --',selectStorage:'Sélectionnez un stockage pour afficher le contenu',loadingNodes:'Chargement de la node (noeud) cible…',loadingStorageNetwork:'Chargement du stockage/réseau…',liveMigration:'Migration en direct (pas d\'arrêt)',on:'sur',targetStorage:'Stockage cible',sameAsSource:'Même que la source',loadingStorages:'Chargement des stockages',free:'libre',withLocalDisks:'Avec les disques locaux',withLocalDisksDesc:'Migrer les disques locaux vers le stockage cible',localDisksDetected:'Cette VM a des disques locaux. La migration nécessite la copie des données de disque.',requiredForThisVm:'Nécessaire pour cette VM',cdDvdMounted:'Disque CD/DVD Monté',cdDvdMigrationWarning:'La migration en direct n\'est pas possible avec un CD/DVD monté. Veuillez éjecter le CD/DVD d\'abord ou utilisez la migration hors ligne.',isoMounted:'ISO/CD-ROM Monté',isoEjected:'CD-ROM éjecté',isoMigrationWarning:'La migration peut échouer si l\'ISO n\'est pas disponible sur le nœud cible. Éjectez le CD/DVD ou assurez-vous que l\'ISO existe sur un stockage partagé.',localStorage:'Local',bootOrderIssue:'Problème d\'ordre de démarrage',bootOrderWarning:'L\'ordre de démarrage fait référence à des disques qui n\'existent pas.',bootOrder:'Ordre de démarrage',dragToReorder:'Cliquez pour basculer, utilisez les flèches pour réorganiser',noBootDevices:'Aucun dispositif de démarrage trouvé',resizeDiskHint:'Augmenter de (par exemple +10G) ou nouvelle taille',// SMBIOS Settings smbiosSettings:'Paramètres SMBIOS',smbiosHint:'Gestion du système BIOS - utile pour la licence Windows et l\'identification des VMs',applySmbiosFromClusterConfig:'Appliquer les paramètres SMBIOS de la configuration du cluster',requiresRestart:'REDEMARRER',readOnly:'lecture seule',autoGenerated:'auto-généré',smbiosFormatHint:'Seulement les lettres et chiffres sont autorisés (A-Za-z0-9)',preview:'Aperçu',currentValue:'Valeur Actuelle',managedByProxmox:'géré par Proxmox',willBeAutoGenerated:'Serait généré automatiquement à la sauvegarde',forceConntrack:'Forcer l\'état de Conntrack',forceConntrackDesc:'Forcer la migration même si des entrées conntrack existent',containerNoLiveMigration:'Les conteneurs ne prennent pas en charge une migration de vie réelle véritable.',startingMigration:'Démarrage de la migration de',migrationStarted:'La migration a commencé!',migrationFailed:'La migration a échoué.',clone:'Cloner',console:'Console',openConsole:'Ouvrir la console',metrics:'Métriques',config:'Configuration',configuration:'Configuration',createVm:'Créer une nouvelle VM',createContainer:'Créer un nouveau conteneur',newVm:'Nouvelle VM',newContainer:'Nouveau conteneur',power:'Alimentation',snapshot:'Instantané',editSettings:'Paramètres',refreshData:'Actualiser',sshConsole:'Console SSH',noNodesAvailable:'Aucun nœud disponible. Veuillez patienter tandis que les données du cluster se chargent.',loadingStorage:'Chargement de la liste de stockage…',noIsoAvailable:'Aucune image ISO trouvée',noTemplateAvailable:'Aucun modèle trouvé',noStorageAvailable:'Aucun stockage disponible',noIsoStorage:'Aucun stockage avec du contenu ISO trouvé',ram:'Mémoire vive',cpu:'CPU',disk:'Disque',network:'Réseau',// VM Creation Wizard @@ -2312,7 +2312,7 @@ poolPermissions:'Permisos de pool',poolPermissionsDesc:'Asigne acceso a usuario o grupos a pools de recursos de Proxmos. Los permisos aplican para todas la VMs en el pool.',managePools:'Gestionar pools',poolManagerDesc:'Crear, editar y remover pools de recursos. Asigne VMs a pools para la gestión organizada de los permisos.',createPool:'Crear pool',editPool:'Editar pool',deletePool:'Remover pool',poolId:'ID de pool',poolIdRequired:'El ID de pool es requerimiento',poolIdHint:'Solo letras, números, guiones y guiones bajos',poolIdCannotChange:'No es posible cambiar el ID de pool',poolCreated:'Pool creado con éxito',poolUpdated:'Pool acualizado con éxito',poolDeleted:'Pool removido con éxito',confirmDeletePool:'¿Está seguro de querer remover este pool? ¡No podrá deshacerlo!',noPoolsYet:'Aún no hay pools definidos',createFirstPool:'Cree un primer pool para organizar las VMs',poolMembers:'Miembros',members:'miembros',addVmToPool:'Agregar VM al pool',assignToPool:"Asignar a pool",removeFromPool:'Remover del pool',vmAddedToPool:'VM agregada al pool',vmRemovedFromPool:'VM removida del pool',confirmRemoveVmFromPool:'¿Remover la VM del pool?',selectVmToAdd:'Seleccione una VM para agregarla a este pool',allVmsInPools:'Ya están en pools todas las VMs',optionalDescription:'Descripción opcional...',userPermissions:'Permisos de usuario',selectPool:'Seleccione el pool',noPools:'No se hallaron recursos de pool',noPoolPerms:'No hay permisos configurados en este pool',selectPoolFirst:'Seleccione un cluster y un pool para gestionar los permisos',addPoolPerm:'Agregar permisos de pool',editPoolPerm:'Editar permisos de pool',poolPermSaved:'Permisos de pool guardados',poolPermDeleted:'Permisos de pool removidos',refreshPools:'Refrescar información de pools de Proxmox',poolCacheRefreshed:'Caché de pools refrescado',confirmDeletePoolPerm:'¿Remover permisos?',subjectType:'Tipo',permissionsFor:'Permisos para',addPermission:'Agregar permisos',groupName:'Nombre de grupo',auditLog:'Registros de auditoría',auditLogDescription:'Todas las acciones de usuario de los últimos 90 días',noAuditLogs:'No hay registros de auditoría disponibles',action:'Acción',details:'Detalles',timestamp:'Marca de tiempo',ipAddress:'Dirección IP',userCreated:'Usuario creado',userUpdated:'Usuario actualizado',userDeleted:'Usuario removido',userLogin:'Entrar',userLogout:'Cerrar sesión',passwordChanged:'Contraseña cambiada',clusterAdded:'Cluster agregado',apiTokenWarningTitle:'Autenticación por token de API',apiTokenWarningDesc:'Sin una contraseña, las características de SSH no funcionan (HA, actualizaciones )" "Without a password, SSH features won\'t work (HA, actualizaciones graduales, SMBIOS, indicador de nodo). Recomendado: usar root@pam + contraseña — PegaProx crea un token de API automáticamente para compatibilidad con 2FA.',apiTokenRecommended:'Se recomienda cuando se activa 2FA.',apiTokenCreated:'Se creó un token de API con PVE — Se activó 2FA de manera segura',sshPasswordStillNeeded:'SSH continúa usando contraseña (HA, mantenimiento) - no la cambie',authModeToken:'API: Token (seguro para 2FA)',authModePassword:'API: Contraseña',sshAuthMode:'SSH: Contraseña',dontChangePvePassword:'No cambie la contraseña de PVE sin actualizarla aquí',clusterDeleted:'Cluster removido',clusterConfigChanged:'Configuración de cluster cambiada',vmStarted:'VM iniciada',vmStopped:'VM detenida',vmRestarted:'VM reiniciada',vmCreated:'VM creada',vmDeleted:'VM removida',vmCloned:'VM clonada',vmMigrated:'VM migrada',vmUnlocked:'VM desbloqueada',vmLocked:'VM bloqueada',unlockVm:'Desbloquear VM',lockReason:'Razón para el bloqueo',unlockWarning:'Desbloquear una VM durante una operación activa puede causar corrupción u otros efectos. Proceda solo si está seguro de que la operación ha sido cancelada o ha fallado.',vmBulkMigrated:'Migración en lote',vmConfigChanged:'Configuración de VM cambiada',vmSuspended:'VM suspendida',vmResumed:'VM recontinuada',vmDiskAdded:'Disco agregado',vmDiskRemoved:'Disco removido',vmDiskResized:'Disco cambiado de tamaño',vmDiskMoved:'Disco movido',vmNetworkAdded:'Network agregada',vmNetworkRemoved:'Network removida',vmNetworkUpdated:'Network actualizada',removeDiskConfirm:'Realmente remover el disco',detachDisk:'Desconectar',importDisk:'Importar disco',importDiskDesc:'Importar una imagen de disco preexistente desde el almacenamiento',selectImportStorage:'Seleccionar el almacenamiento fuente',selectDiskImage:'Seleccionar la imagen de disco',targetBus:'Bus destino',reassignOwner:'Reasignar dueño',reassignOwnerDesc:'Asignar el disco a una VM diferente',targetVm:'VM destino',diskReassigned:'Disco reasignado',diskImported:'Disco importado',noImportableDisks:'No se encontraron imágenes de disco importables',reassign:'Reasignar',import:'Importar',detachDiskConfirm:'¿Realmente desconectar el disco? Se hará inusable el disco.',diskDetached:'Disco desconectado',diskDeleted:'Disco removido',diskAdded:'Disco agregado',dataWillBeDeleted:'¡Los datos se removerán permanentemente!',removeNetworkConfirm:'Remover realmente la red',snapshotCreated:'Instantánea creada',snapshotDeleted:'Instantánea removida',snapshotRestored:'Instantánea restaurada',snapshotDesc:"Enfoque de clonar + migrar. Funciona con cualquier almacenamiento (LVM, dir, etc).",snapshotName:"Nombre de la instantánea",snapshotReplNote:"Crea un clon completo de la VM y lo migra al nodo destino. La réplica anterior se reemplaza en cada ejecución.",rollbackConfirm:'¿Reversar realmente a esta instantánea? ¡Esto no podría deshacerse!',rollbackStarted:"Reversión (Rollback) iniciada",rpo:"RPO",rpoDesc:"Objetivo de punto de recuperación (RPO) - tiempo desde la última replicación",replicationCreated:'Replicación creada',replicationDeleted:'Replicación removida',replicationTriggered:'Replicación disparada',replicationHint:"Cree una tarea de replicación para mantener los datos de las VMs sincronizados entre los nodos",replicationInfoDesc:"Mantenga los datos de las VMs sincronizados entre los nodos para failover y recuperación de desastres. Dos modos disponibles:",replicationInfoTitle:"Replicación de VMs",haEnabled:'HA activada',haDisabled:'HA desactivada',noFallbackHosts:'No se encontraron máquinas para retornar (¿Cluster de un único nodo?)',haVmAdded:'VM agregada a HA',haVmRemoved:'VM removida de HA',nodeMaintenanceEntered:'Se entró a modo mantenimiento',nodeMaintenanceExited:'Se ha salido de modo mantenimiento',nodeUpdateStarted:'Inició la actualización de nodo',twoFactorAuth:'Autenticación de dos factores (2FA)',twoFactorEnabled:'2FA activada',twoFactorDisabled:'2FA desactivada',enable2FA:'Activar 2FA',disable2FA:'Desactivar 2FA',setup2FA:'Configurar 2FA',scan2FACode:'Escanee el código QR con una app de autenticación',enter2FACode:'Entre el código de 6 dígitos',verify2FA:'Verificar',secretKey:'Llave secreta',twoFARequired:'Se requiere código 2FA',invalid2FACode:'Código 2FA inválido',force2FA:'Forzar 2FA',force2FADesc:'Requerir que todos los usuarios tengan 2FA para poder usar PegaProx.',force2FAHint:'Usuarios OIDC/Entra son exentos (usan la MFA de su proveedor de identidad). Los usuarios sin 2FA verán un díalogo de configuración al entrar.',force2FAExcludeAdmins:'Excluir las cuentas de administrador',force2FAExcludeAdminsDesc:'Los administradores pueden usar PegaProx sin 2FA',force2FASetupTitle:'Se requiere configuración de 2FA',force2FASetupDesc:'Su administrador ha activado autenticación con dos factores de manera obligatoria para todos los usuarios. Favor configure 2FA para continuar',resetPassword:'Resetear contraseña',passwordManagedExternally:'Su contraseña es administrada externamente.',passwordManagedExternallyHint:'Cambie su contraseña directamente en su servicio de directorio.',newPassword:'Nueva contraseña',confirmPassword:'Confirmar contraseña',currentPassword:'Contraseña actual',passwordsDoNotMatch:'Las contraseñas no concuerdan',passwordTooShort:'Las contraseñas deben ser de al menos 4 caracteres',passwordResetSuccess:'Contraseña cambiada con éxito',myProfile:'Mi perfil',security:'Seguridad',snapshotNotSupported:'Las instantáneas no están soportadas',snapshotWarnings:'Advertencias por instantáneas',filterByUser:'Filtrado por usuario',filterByAction:'Filtrado por acción',allUsers:'Todos los usuarios',allActions:'Todas las acciones',exportAuditLog:'Exportar',refreshAuditLog:'Refrescar',// Header / Encabezado addCluster:'Agregar cluster',pveClusterDesc:'Máquinas virtuales y contenedores',pbsDesc:'Gestión de los respaldos',vmwareDesc:'Infraestructura ESXi',addConnection:'Agregar conexión',connectionType:'Tipo de conexión',clusterManagement:'PegaProx Cluster Management for Proxmox VE',// Tabs / Pestañas overview:'Vista general',resources:'Recursos',datacenter:'Datacenter',settings:'Valores',of:'de',showing:'Mostrando',perPage:'Por página',loadingDatacenter:'Cargando datos de datacenter...',// Cluster -clusters:'Clústeres',noClusterSelected:'No hay un cluster seleccionado',allClustersOverview:'Vista general de los clústeres',allClusters:'Todos los clústeres',multiClusterSummary:'Resumen de todos los clústeres gestionados',clustersConnected:'Clústeres conectados',clusterOverview:'Vista general de cluster',clusterCpu:"CPU en el cluster",clusterMemory:"Memoria en el cluster",clusterServices:"Servicios de cluster",clusterStorage:"Almacenamiento en el cluster",vmsRunning:'VMs corriendo',vmsStopped:'VMs detenidas',noClustersConfigured:'No hay clústeres configurados',addClusterToStart:'Agregar un cluster para comenzar',clickClusterTip:'Seleccionar la fila de un cluster o seleccionar de la barra lateral para ver información detallada y manejar los recursos',health:'Salud',average:'Promedio',noDataAvailable:'No hay datos disponibles',clickToManage:'Clic para gestionar',sortBy:'Ordenar por',ungrouped:'Sin agrupar',alerts:'Alertas',updated:'Actualizado',justNow:'justo ahora',topResources:'Recursos más',highestCpuUsage:'Consumo más alto de CPU y de RAM en todos los clústeres',clickToOpenVm:'Clic para abrir la VM',selectCluster:'Seleccionar un cluster',addFirstCluster:'Agregar el primer cluster',connectCluster:'Conectar un cluster a PegaProx',clusterName:'Nombre del cluster',host:'Máquina',username:'Usuario',password:'Contraseña',passwordOrToken:'Contraseña / Token',apiTokenHint:'Para tokes de API: usuario@dominio!idtoken',sslVerification:'Verificación de SSL',connecting:'Conectando...',addNewCluster:'Agregar un nuevo cluster',testConnection:'Probar la conexión',testCleanup:"Limpiar prueba",deleteCluster:'Remover cluster',deleteClusterConfirm:'¿Remover realmente el cluster?',reconfigureCluster:'Reconfigurar cluster',reconfigureHint:'Ingrese su contraseña de PegaProx para verificar su identidad.',clusterReconfigured:'Cluster reconfigurado exitosamente',reconfigure:'Reconfigurar',clusterHealth:'Salud del cluster',excellent:'Excelente',good:'Buena',warning:'Advertencia',critical:'Crítica',nodesOnline:'Nodos en línea',nodeJoinHint:'Para agregar un nuevo nodo, ejecute en el nodo nuevo:',avgScore:'Puntos promedio',avgCpu:'CPU promedio',avgRam:'RAM promedio',// Nodes / Nodos +clusters:'Clústeres',noClusterSelected:'No hay un cluster seleccionado',allClustersOverview:'Vista general de los clústeres',allClusters:'Todos los clústeres',multiClusterSummary:'Resumen de todos los clústeres gestionados',clustersConnected:'Clústeres conectados',clusterOverview:'Vista general de cluster',clusterCpu:"CPU en el cluster",clusterMemory:"Memoria en el cluster",clusterServices:"Servicios de cluster",clusterStorage:"Almacenamiento en el cluster",vmsRunning:'VMs corriendo',vmsStopped:'VMs detenidas',noClustersConfigured:'No hay clústeres configurados',addClusterToStart:'Agregar un cluster para comenzar',clickClusterTip:'Seleccionar la fila de un cluster o seleccionar de la barra lateral para ver información detallada y manejar los recursos',health:'Salud',average:'Promedio',noDataAvailable:'No hay datos disponibles',clickToManage:'Clic para gestionar',sortBy:'Ordenar por',ungrouped:'Sin agrupar',alerts:'Alertas',updated:'Actualizado',justNow:'justo ahora',topResources:'Recursos más',highestCpuUsage:'Consumo más alto de CPU y de RAM en todos los clústeres',clickToOpenVm:'Clic para abrir la VM',selectCluster:'Seleccionar un cluster',addFirstCluster:'Agregar el primer cluster',connectCluster:'Conectar un cluster a PegaProx',clusterName:'Nombre del cluster',host:'Máquina',username:'Usuario',password:'Contraseña',passwordOrToken:'Contraseña / Token',apiTokenHint:'Para tokes de API: usuario@dominio!idtoken',sslVerification:'Verificación de SSL',connecting:'Conectando...',addNewCluster:'Agregar un nuevo cluster',testConnection:'Probar la conexión',testCleanup:"Limpiar prueba",deleteCluster:'Remover cluster',deleteClusterConfirm:'¿Remover realmente el cluster?',reconfigureCluster:'Reconfigurar cluster',reconfigureHint:'Ingrese su contraseña de PegaProx para verificar su identidad.',clusterReconfigured:'Cluster reconfigurado exitosamente',reconfigure:'Reconfigurar',clusterHealth:'Salud del cluster',clusterHealthTooltip:'Salud = 100 − (CPU×30% + RAM×30% + Almacenamiento×20% + Nodos fuera de línea×20%)\n\n80+: Excelente\n60–79: Buena\n40–59: Advertencia\n< 40: Crítica',nodeScoreTooltip:'Puntuación nodo = CPU% + RAM% (menor es mejor)\n\n< 100: Bueno (verde)\n100–150: Elevado (amarillo)\n> 150: Crítico (rojo)',excellent:'Excelente',good:'Buena',warning:'Advertencia',critical:'Crítica',nodesOnline:'Nodos en línea',nodeJoinHint:'Para agregar un nuevo nodo, ejecute en el nodo nuevo:',avgScore:'Puntos promedio',avgStorage:'Almac. promedio',avgCpu:'CPU promedio',avgRam:'RAM promedio',// Nodes / Nodos nodes:'Nodos',node:'Nodo',loadingMetrics:'Cargando métricas...',connectionError:'Error de conexión',retry:'Reintentar',checkConnectionAndRetry:'Favor revise su conexión e intente nuevamente.',connectionTimeout:'Timeout - Proxmox inalcanzable',maintenance:'Mantenimiento',enterMaintenance:'Enter a modo mantenimiento',exitMaintenance:'Salir de modo mantenimiento',maintenanceMode:'Modo mantenimiento',update:'Actualizar',startUpdate:'Iniciar actualización',nodeConfig:'Configuración de nodo',cpuHistory:'Historia de CPU',ramHistory:'Historia de RAM',ramUsage:'Consumo de RAM',cpuUsage:'Consumo de CPU',diskUsage:'Consumo de disco',allocated:'asignado',showMore:'Mostrar más',showLess:'Mostrar menos',fixAvailable:"actualización disponible",installing:"Instalando...",inventoryOverview:"Vista General de Inventario",items:"ítems",recentItems:"Recientes",rename:"Renombrar",// VMs & Resources / VMs y recursos virtualMachines:'Máquinas virtuales',containers:'Contenedores',vm:'VM',lxc:'LXC',lxcContainer:'Contenedor LXC',container:'Contenedor',guests:'Huéspedes',start:'Iniciar',stop:'Detener',shutdown:'Concluir',reboot:'Reiniciar',forceStop:'Detener forzado',forceReset:'Reset forzado',forceStopConfirm:'¿forzar realmente la parada? ¡puede perder datos!',migrate:'Migrar',migrateVm:'Migrar VM',crossClusterMigration:'Migración Cross-Cluster',crossClusterMigrateDesc:'Migrar una VM a otro cluster Proxmox',crossClusterMigrate:'Migración Cross-Cluster',crossClusterStarted:'Migración Cross-Cluster iniciada',crossClusterFailed:'Migración Cross-Cluster fallida',crossClusterLB:'Balanceo de cargas (LB) Cross-Cluster',crossClusterLBEnabled:'Cross-Cluster LB activado',crossClusterLBDisabled:'Cross-Cluster LB desactivado',crossClusterLBThreshold:'Límite de puntaje',crossClusterLBInterval:'Intervalo de chequeo',crossClusterLBDryRun:'Simular (prueba en vacío)',crossClusterLBLastRun:'Última ejecución',crossClusterReplication:'Replicación Cross-Cluster',crossClusterReplicationDesc:'Replicar instantáneas de VM a otro cluster (DR)',groupOverview:'Vista general de group',groupSettings:'Valores de grupo',groupSettingsSaved:'Valores de grupo guardados',replicationSchedule:'Agendar',replicationRetention:'Retención',maxMigrations:'Migraciones máximas por ciclo',lbHistory:'Historia de balanceo',clusterScore:'Puntaje de cluster',noReplicationJobs:'No hay tareas de replicación',createReplicationJob:'Crear tarea de replicación',replicationStarted:'Replicación iniciada',replicationJobCreated:'Tarea de replicación creada',replicationJobDeleted:'Tarea de replicación removida',recentLbActions:'Acciones recientes de LB cross-cluster',noLbEvents:'No hay eventos de LB Cross-Cluster',enableCrossClusterLB:'Activar balanceo (LB) Cross-Cluster',lbDescription:'Migrar automáticamente VMs entre clústeres cuando los umbrales se excedan',dryRunMode:'Ejecución seca / Modo simulación',cpuThreshold:'Límite (%) de CPU',lbStatus:'Estado de balanceo',lbExplanation:'El balanceo de cargas Cross-Cluster monitorea el consumo de CPU y de RAM a través de todos los clústeres del grupo. Cuando un cluster excede el umbral configurado las VMs son migradas automáticamente a un cluster con menos carga',lbDryRunExplanation:'Active primero el modo de ejecución en vacío para revisar primero cuáles acciones deberán tomarse antes de activar migraciones en vivo',neverRun:'Nunca ejecutar',newCrossClusterReplication:'Nueva replicación Cross-Cluster',confirmDeleteXRepl:'¿remover la tarea de replicación?',addDrJob:'Agregar una tarea de DR',scheduleCron:'Agendar (cron)',targetStorageHint:'Nombre del almacenamiento en el cluster destino',maxMigrationsHint:'Límite de cuántas VMs se pueden mover por ciclo (1-5)',proxlbCredit:'ProxLB by gyptazy',proxlbCreditDesc:'Nuestra funcionalidad de balanceo de cargas se base en el excelente trabajo de ProxLB ¡Gracias especiales a gyptazy por crear y liberar el código de esta asombrosa herramienta!',xReplCreated:'Replicación Cross-Cluster creada',xReplDeleted:'Replicación Cross-Cluster removida',xReplStarted:'Replicación Cross-Cluster iniciada',xReplCreateFailed:'Falló la creación de la tarea',xReplDeleteFailed:'Falló la remoción de la tarea',xReplStartFailed:'Falló el iniciar',lastRunPrefix:'Última',runNow:'Ejecutar ahora',loadingCrossClusterResources:'Cargando el almacenamiento y las redes de todos los clústeres...',noCommonStorages:'No se encontró almacenamiento común a todos los clústeres',noCommonBridges:'No se encontró un puente común a todos los clústeres',selectBridge:'Seleccionar el puente...',crossClusterThresholdDesc:'Límite de CPU para el desbalance de cluster',crossClusterIntervalDesc:'Tiempo entre ciclos de chequeo',crossClusterMaxMigrationsDesc:'Máximo de migraciones por ciclo',commonStorageHint:'Solo los almacenamientos comunes al grupo',commonBridgeHint:'Solo los puentes comunes al grupo',includeContainers:'Incluir contenedores',includeContainersDesc:'Incluir los contenedores LXC en el balanceo Cross-Cluster',containerMigrationWarning:'Los contenedores son reiniciados durante las migraciones (interrupciones)',excludedVMsCrossCluster:'Excluir VMs/Contenedores',excludedVMsCrossClusterDesc:'VMs y contenedores excluidos del balanceo automático Cross-Cluster',clusterExcludedVMs:'VMs excluidas',noExcludedVMsInGroup:'No hay VMs excluidas',selectCluster:'Seleccionar el cluster...',selectClusterFirst:'Primero seleccione un cluster destino',simulationMode:'Modo simulación',sourceVm:'VM origen',sourceBridge:"Red (puente) origen",sourceStorage:"Almacenamiento origen",targetCluster:'Cluster destino',targetNode:'Nodo destino',targetStorage:'Almacenamiento destino',targetBridge:'Red (puente) destino',moveDisk:'Mover disco',resizeDisk:'Cambiar tamaño de disco',diskResized:'Disco cambiado',deleteSourceDisk:'Remover la fuente luego del movimiento',deleteSourceDiskWarning:'El disco original se mantendrá en el almacenamiento origen. Puede removerlo manualmente después.',move:'Mover',from:'desde',nativeProxmoxReplication:"Replicación Nativa (ZFS) de Proxmox",noNetworkInterfaces:'No hay interfaces de red',nameRequired:'Se requiere el nombre',newVmid:'Nuevo VM ID (opcional)',sameIdPlaceholder:'Vacío = mismo ID',liveMigrationOption:'Migración en vivo (la VM sigue corriendo)',deleteSourceAfter:'Remover la fuente luego de la migración',largeDiskWarning:'Se detectó un disco grande',largeDiskExplanation:'La migración de discos >100GB puede fallar con un código "401 Unauthorized" debido a timeouts del ticket WebSocket de Proxmox. El servidor usará migración fuera de línea de manera automática a menos que se force otra cosa.',forceOnlineMigration:'Forzar la migración en línea (puede fallar)',autoTokenInfo:'PegaProx crea tokens de API temporales automáticamente para la migración y los borra luego de completar',clusterReachableInfo:'Los clústeres deben poderse alcanzar entre ellos a través de la red. El usuario seleccionado debe tener permisos para crear tokens de API.',selectCluster:'Seleccionar el cluster...',selectNode:'Seleccionar el nodo',selectStorage:'Seleccionar el almacenamiento...',loadingNodes:'Cargando los nodos destino...',loadingStorageNetwork:'Cargando la red y el almacenamiento...',liveMigration:'Migración en vivo (sin interrupción)',on:'en',targetStorage:'Almacenamiento destino',sameAsSource:'Lo mismo que en la fuente',loadingStorages:'Cargando almacenamiento',free:'libre',withLocalDisks:'Con discos locales',withLocalDisksDesc:'Migrar discos locales al almacenamiento destino',localDisksDetected:'Esta VM tiene discos locales. La migración así requiere copiar datos de discos.',requiredForThisVm:'Se requiere para esta VM',cdDvdMounted:'La unidad CD/DVD está montada',cdDvdMigrationWarning:'La migración en vivo no es posible con un CD/DVD montado. Favor expulse el CD/DVD antes o intente una migración en frío',isoMounted:'ISO/CD-ROM montado',isoEjected:'CD-ROM expulsado',isoMigrationWarning:'La migración puede fallar si el ISO no está disponible en el nodo destino. Expulse el CD/DVD o asegúrese de que el ISO exista en almacenamiento compartido.',localStorage:'Local',bootOrderIssue:'Asunto de orden de arranque (boot)',bootOrderWarning:'La lista de arranque (boot) menciona discos no existentes',bootOrder:'Lista de inicio (boot)',dragToReorder:'Clic para alternar, arrastre para reorganizar',noBootDevices:'No se encontraron dispositivos de arranque',resizeDiskHint:'Aumentar en (p.ej. +10G) o un nuevo tamaño',// SMBIOS Settings / Valores de SMBIOS smbiosSettings:'Valores de SMBIOS',smbiosHint:'System Management BIOS - útil para el licenciamiento de Windows y para identificación de VMs',applySmbiosFromClusterConfig:'Aplicar valores de SMBIOS para configuración de cluster',requiresRestart:'REINICIO',readOnly:'solo-lectura',autoGenerated:'auto-generado',smbiosFormatHint:'Solo se permiten letras y números (A-Za-z0-9)',preview:'Vista previa',currentValue:'Valor actual',managedByProxmox:'gestionado por Proxmox',willBeAutoGenerated:'Se autogenerará al guardar',forceConntrack:'Forzar (estado Conntrack)',forceConntrackDesc:'Forzar la migración aún si se hallan entradas Conntrack',containerNoLiveMigration:'Los contenedores no soportan migración en vivo real',startingMigration:'Iniciando la migración de',migrationStarted:'Migración iniciada:',migrationFailed:'Migración fallida',clone:'Clonar',console:'Consola',openConsole:'Abrir la consola',metrics:'Métricas',config:'Configuración',configuration:'Configuración',createVm:'Crear una nueva VM',createContainer:'Crear un nuevo contenedor',newVm:'Nueva VM',newContainer:'Nuevo contenedor',power:'Energía',snapshot:'Instantánea',editSettings:'Valores',refreshData:'Refrescar',sshConsole:'Consola SSH',noNodesAvailable:'No hay nodos disponibles. Favor esperar a que carguen los valores de cluster',loadingStorage:'Cargando la lista de almacenamiento...',noIsoAvailable:'No se encontraron imágenes ISO',noTemplateAvailable:'No hay plantillas disponibles',noStorageAvailable:'No hay almacenamienot disponible',noIsoStorage:'No se encontró almacenamiento conteniendo ISOs',ram:'RAM',cpu:'CPU',disk:'Disco',network:'Red',networkMappings:"Mapeos de red",networkOverview:"Vista General de la Red",networking:"Redes",// VM Creation Wizard @@ -2402,8 +2402,8 @@ poolPermissions:'Permissões de Pool',poolPermissionsDesc:'Conceda a usuários ou grupos acesso a pools de recursos do Proxmox. As permissões se aplicam a todas as VMs dentro do pool.',managePools:'Gerenciar Pools',poolManagerDesc:'Crie, edite e exclua pools de recursos. Atribua VMs a pools para um gerenciamento de permissões organizado.',createPool:'Criar Pool',editPool:'Editar Pool',deletePool:'Excluir Pool',poolId:'ID do Pool',poolIdRequired:'O ID do Pool é obrigatório',poolIdHint:'Apenas letras, números, hifens e sublinhados',poolIdCannotChange:'O ID do Pool não pode ser alterado',poolCreated:'Pool criado com sucesso',poolUpdated:'Pool atualizado com sucesso',poolDeleted:'Pool excluído com sucesso',confirmDeletePool:'Tem certeza de que deseja excluir este pool? Isso não pode ser desfeito.',noPoolsYet:'Nenhum pool ainda',createFirstPool:'Crie seu primeiro pool para organizar as VMs',poolMembers:'Membros',members:'membros',addVmToPool:'Adicionar VM ao Pool',assignToPool:'Atribuir ao Pool',removeFromPool:'Remover do pool',vmAddedToPool:'VM adicionada ao pool',vmRemovedFromPool:'VM removida do pool',confirmRemoveVmFromPool:'Remover VM deste pool?',selectVmToAdd:'Selecione uma VM para adicionar a este pool',allVmsInPools:'Todas as VMs já estão em pools',optionalDescription:'Descrição opcional...',userPermissions:'Permissões de Usuário',selectPool:'Selecionar Pool',noPools:'Nenhum pool de recursos encontrado neste cluster',noPoolPerms:'Nenhuma permissão configurada para este pool',selectPoolFirst:'Selecione um cluster e um pool para gerenciar permissões',addPoolPerm:'Adicionar Permissão de Pool',editPoolPerm:'Editar Permissão de Pool',poolPermSaved:'Permissão de pool salva',poolPermDeleted:'Permissão de pool removida',refreshPools:'Atualizar pools do Proxmox',poolCacheRefreshed:'Cache de pools atualizado',confirmDeletePoolPerm:'Remover permissão?',subjectType:'Tipo',permissionsFor:'Permissões para',addPermission:'Adicionar Permissão',groupName:'Nome do Grupo',auditLog:'Log de Auditoria',auditLogDescription:'Todas as ações de usuários dos últimos 90 dias',noAuditLogs:'Nenhuma entrada de auditoria disponível',action:'Ação',details:'Detalhes',timestamp:'Data/Hora',ipAddress:'Endereço IP',userCreated:'Usuário criado',userUpdated:'Usuário atualizado',userDeleted:'Usuário excluído',userLogin:'Login',userLogout:'Logout',passwordChanged:'Senha alterada',clusterAdded:'Cluster adicionado',apiTokenWarningTitle:'Autenticação por Token de API',apiTokenWarningDesc:'Sem uma senha, os recursos de SSH não funcionarão (HA, Atualizações Rolantes, SMBIOS, Shell do Nó). Recomendado: Use root@pam + senha — o PegaProx cria automaticamente um token de API para compatibilidade com 2FA.',apiTokenRecommended:'Recomendado quando 2FA está habilitado',apiTokenCreated:'Token de API criado no PVE — o 2FA agora pode ser habilitado com segurança',sshPasswordStillNeeded:'O SSH ainda usa a senha (HA, manutenção) — não a altere',authModeToken:'API: Token (Seguro para 2FA)',authModePassword:'API: Senha',sshAuthMode:'SSH: Senha',dontChangePvePassword:'Não altere a senha do PVE sem atualizá-la aqui',clusterDeleted:'Cluster excluído',clusterConfigChanged:'Configuração do cluster alterada',vmStarted:'VM iniciada',vmStopped:'VM parada',vmRestarted:'VM reiniciada',vmCreated:'VM criada',vmDeleted:'VM excluída',vmCloned:'VM clonada',vmMigrated:'VM migrada',vmUnlocked:'VM desbloqueada',vmLocked:'VM bloqueada',unlockVm:'Desbloquear VM',lockReason:'Motivo do Bloqueio',unlockWarning:'Aviso: Desbloquear uma VM durante uma operação ativa pode causar corrupção de dados ou outros problemas. Prossiga apenas se tiver certeza de que a operação falhou ou foi cancelada.',vmBulkMigrated:'Migração em lote',vmConfigChanged:'Configuração da VM alterada',vmSuspended:'VM suspensa',vmResumed:'VM retomada',vmDiskAdded:'Disco adicionado',vmDiskRemoved:'Disco removido',vmDiskResized:'Disco redimensionado',vmDiskMoved:'Disco movido',vmNetworkAdded:'Rede adicionada',vmNetworkRemoved:'Rede removida',vmNetworkUpdated:'Rede atualizada',removeDiskConfirm:'Realmente remover disco',detachDisk:'Desacoplar',importDisk:'Importar Disco',importDiskDesc:'Importar imagem de disco existente do armazenamento',selectImportStorage:'Selecionar Armazenamento de Origem',selectDiskImage:'Selecionar Imagem de Disco',targetBus:'Barramento de Destino',reassignOwner:'Reatribuir Proprietário',reassignOwnerDesc:'Atribuir disco a uma VM diferente',targetVm:'VM de Destino',diskReassigned:'Disco reatribuído',diskImported:'Disco importado',noImportableDisks:'Nenhuma imagem de disco importável encontrada',reassign:'Reatribuir',import:'Importar',detachDiskConfirm:'Realmente desacoplar o disco? Ele se tornará um disco não utilizado.',diskDetached:'Disco desacoplado',diskDeleted:'Disco excluído',diskAdded:'Disco adicionado',dataWillBeDeleted:'Os dados serão excluídos permanentemente!',removeNetworkConfirm:'Realmente remover rede',snapshotCreated:'Snapshot criado',snapshotDeleted:'Snapshot excluído',snapshotRestored:'Snapshot restaurado',rollbackConfirm:'Deseja realmente reverter para este snapshot? Isso não pode ser desfeito!',replicationCreated:'Replicação criada',replicationDeleted:'Replicação excluída',replicationTriggered:'Replicação acionada',haEnabled:'HA habilitado',haDisabled:'HA desabilitado',noFallbackHosts:'Nenhum host de fallback encontrado (Cluster de Nó Único?)',haVmAdded:'VM adicionada ao HA',haVmRemoved:'VM removida do HA',nodeMaintenanceEntered:'Modo de manutenção ativado',nodeMaintenanceExited:'Modo de manutenção desativado',nodeUpdateStarted:'Atualização do nó iniciada',twoFactorAuth:'Autenticação de Dois Fatores',twoFactorEnabled:'2FA habilitado',twoFactorDisabled:'2FA desabilitado',enable2FA:'Habilitar 2FA',disable2FA:'Desabilitar 2FA',setup2FA:'Configurar 2FA',scan2FACode:'Escaneie o código QR com seu app autenticador',enter2FACode:'Digite o código de 6 dígitos',verify2FA:'Verificar',secretKey:'Chave Secreta',twoFARequired:'Código 2FA obrigatório',invalid2FACode:'Código 2FA inválido',force2FA:'Forçar 2FA',force2FADesc:'Exigir que todos os usuários configurem a Autenticação de Dois Fatores antes de poderem usar o PegaProx.',force2FAHint:'Usuários OIDC/Entra estão isentos (eles usam o MFA do Provedor de Identidade). Usuários sem 2FA verão um diálogo de configuração ao fazer login.',force2FAExcludeAdmins:'Excluir contas de administrador',force2FAExcludeAdminsDesc:'Administradores podem usar o PegaProx sem 2FA',force2FASetupTitle:'Configuração de 2FA Obrigatória',force2FASetupDesc:'Seu administrador tornou a Autenticação de Dois Fatores obrigatória para todos os usuários. Por favor, configure o 2FA para continuar.',resetPassword:'Redefinir Senha',passwordManagedExternally:'Sua senha é gerenciada externamente.',passwordManagedExternallyHint:'Altere sua senha diretamente no seu serviço de diretório.',passwordTooShort:'A senha deve ter pelo menos 4 caracteres',passwordResetSuccess:'Senha alterada com sucesso',myProfile:'Meu Perfil',security:'Segurança',snapshotNotSupported:'Snapshots não suportados',snapshotWarnings:'Avisos de snapshot',filterByUser:'Filtrar por usuário',filterByAction:'Filtrar por ação',allUsers:'Todos os Usuários',allActions:'Todas as Ações',exportAuditLog:'Exportar',refreshAuditLog:'Atualizar',// Header addCluster:'Adicionar Cluster',addXcpngPool:'Adicionar Pool XCP-ng (Tech Preview)',xcpngConnectHint:'Conecte-se ao host mestre do pool. A porta XAPI 443 é usada por padrão.',xcpngTechPreviewNote:'Alguns recursos podem ser limitados ou estar sujeitos a alterações.',pveClusterDesc:'Máquinas virtuais e containers',pbsDesc:'Gerenciamento de backup',vmwareDesc:'Infraestrutura ESXi',xcpngDesc:'XCP-ng / Xen Hypervisor (Tech Preview)',addConnection:'Adicionar Conexão',connectionType:'Tipo de Conexão',clusterManagement:'Gerenciamento de Cluster PegaProx para Proxmox VE',renameCluster:"RenombrarCluster",renameHint:"Dejar vacío para volver al nombre original",// Tabs overview:'Visão Geral',resources:'Recursos',datacenter:'Datacenter',settings:'Configurações',showing:'Mostrando',perPage:'Por página',loadingDatacenter:'Carregando dados do datacenter...',// Cluster -clusters:'Clusters',noClusterSelected:'Nenhum Cluster Selecionado',allClustersOverview:'Visão Geral de Todos os Clusters',multiClusterSummary:'Resumo de todos os clusters gerenciados',clustersConnected:'Clusters Conectados',clusterOverview:'Visão Geral do Cluster',vmsRunning:'VMs em Execução',vmsStopped:'VMs Paradas',noClustersConfigured:'Nenhum cluster configurado',addClusterToStart:'Adicione um cluster para começar',clickClusterTip:'Clique em uma linha de cluster ou selecione na barra lateral para ver informações detalhadas e gerenciar recursos.',health:'Saúde',average:'Média',noDataAvailable:'Nenhum dado disponível',clickToManage:'Clique para gerenciar',sortBy:'Ordenar por',alerts:'Alertas',updated:'Atualizado',justNow:'agora mesmo',topResources:'Principais Recursos',highestCpuUsage:'Maior uso de CPU e RAM em todos os clusters',clickToOpenVm:'Clique para abrir a VM',selectCluster:'Selecione um cluster da lista',addFirstCluster:'Adicionar Primeiro Cluster',connectCluster:'Conectar um cluster Proxmox ao PegaProx',clusterName:'Nome do Cluster',host:'Host',username:'Usuário',password:'Senha',passwordOrToken:'Senha / Token',apiTokenHint:'Para tokens de API: usuario@realm!tokenid',sslVerification:'Verificação SSL',connecting:'Conectando...',addNewCluster:'Adicionar Novo Cluster',testConnection:'Testar Conexão',deleteCluster:'Excluir Cluster',deleteClusterConfirm:'Realmente excluir cluster?',reconfigureCluster:'Reconfigurar Cluster',reconfigureHint:'Digite sua senha do PegaProx para verificar sua identidade.',clusterReconfigured:'Cluster reconfigurado com sucesso',reconfigure:'Reconfigurar',clusterHealth:'Saúde do Cluster',excellent:'Excelente',good:'Bom',warning:'Aviso',critical:'Crítico',nodesOnline:'Nós Online',nodeJoinHint:'To add a new node, run on the new node:',// Mantido comando original -avgScore:'Pontuação Média',avgCpu:'CPU Média',avgRam:'RAM Média',// Nodes +clusters:'Clusters',noClusterSelected:'Nenhum Cluster Selecionado',allClustersOverview:'Visão Geral de Todos os Clusters',multiClusterSummary:'Resumo de todos os clusters gerenciados',clustersConnected:'Clusters Conectados',clusterOverview:'Visão Geral do Cluster',vmsRunning:'VMs em Execução',vmsStopped:'VMs Paradas',noClustersConfigured:'Nenhum cluster configurado',addClusterToStart:'Adicione um cluster para começar',clickClusterTip:'Clique em uma linha de cluster ou selecione na barra lateral para ver informações detalhadas e gerenciar recursos.',health:'Saúde',average:'Média',noDataAvailable:'Nenhum dado disponível',clickToManage:'Clique para gerenciar',sortBy:'Ordenar por',alerts:'Alertas',updated:'Atualizado',justNow:'agora mesmo',topResources:'Principais Recursos',highestCpuUsage:'Maior uso de CPU e RAM em todos os clusters',clickToOpenVm:'Clique para abrir a VM',selectCluster:'Selecione um cluster da lista',addFirstCluster:'Adicionar Primeiro Cluster',connectCluster:'Conectar um cluster Proxmox ao PegaProx',clusterName:'Nome do Cluster',host:'Host',username:'Usuário',password:'Senha',passwordOrToken:'Senha / Token',apiTokenHint:'Para tokens de API: usuario@realm!tokenid',sslVerification:'Verificação SSL',connecting:'Conectando...',addNewCluster:'Adicionar Novo Cluster',testConnection:'Testar Conexão',deleteCluster:'Excluir Cluster',deleteClusterConfirm:'Realmente excluir cluster?',reconfigureCluster:'Reconfigurar Cluster',reconfigureHint:'Digite sua senha do PegaProx para verificar sua identidade.',clusterReconfigured:'Cluster reconfigurado com sucesso',reconfigure:'Reconfigurar',clusterHealth:'Saúde do Cluster',clusterHealthTooltip:'Saúde = 100 − (CPU×30% + RAM×30% + Armazenamento×20% + Nós offline×20%)\n\n80+: Excelente\n60–79: Bom\n40–59: Aviso\n< 40: Crítico',nodeScoreTooltip:'Pontuação nó = CPU% + RAM% (menor é melhor)\n\n< 100: Bom (verde)\n100–150: Elevado (amarelo)\n> 150: Crítico (vermelho)',excellent:'Excelente',good:'Bom',warning:'Aviso',critical:'Crítico',nodesOnline:'Nós Online',nodeJoinHint:'To add a new node, run on the new node:',// Mantido comando original +avgScore:'Pontuação Média',avgStorage:'Armaz. Médio',avgCpu:'CPU Média',avgRam:'RAM Média',// Nodes nodes:'Nós',loadingMetrics:'Carregando métricas...',connectionError:'Erro de Conexão',retry:'Repetir',checkConnectionAndRetry:'Por favor, verifique sua conexão e tente novamente.',connectionTimeout:'Tempo esgotado - Proxmox inacessível',maintenance:'Manutenção',enterMaintenance:'Ativar Modo de Manutenção',exitMaintenance:'Sair do Modo de Manutenção',maintenanceMode:'Modo de Manutenção',update:'Atualizar',startUpdate:'Iniciar Atualização',nodeConfig:'Configuração do Nó',cpuHistory:'Histórico de CPU',ramHistory:'Histórico de RAM',diskUsage:'Uso de Disco',allocated:'alocado',showMore:'Mostrar mais',showLess:'Mostrar menos',// VMs & Resources virtualMachines:'Máquinas Virtuais',containers:'Containers',vm:'VM',lxc:'LXC',lxcContainer:'Container LXC',container:'Container',guests:'Convidados',start:'Iniciar',stop:'Parar',shutdown:'Desligar',reboot:'Reiniciar',forceStop:'Forçar Parada',forceReset:'Forçar Reset',forceStopConfirm:'Deseja realmente forçar a parada? Isso pode causar perda de dados!',migrate:'Migrar',migrateVm:'Migrar VM',crossClusterMigration:'Migração entre Clusters',crossClusterMigrateDesc:'Migrar VM para outro cluster Proxmox',crossClusterMigrate:'Migrar entre Clusters',crossClusterStarted:'Migração entre clusters iniciada',crossClusterFailed:'Migração entre clusters falhou',crossClusterLB:'Balanceamento de Carga entre Clusters',crossClusterLBEnabled:'LB entre clusters habilitado',crossClusterLBDisabled:'LB entre clusters desabilitado',crossClusterLBThreshold:'Limiar de Pontuação',crossClusterLBInterval:'Intervalo de Verificação',crossClusterLBDryRun:'Simulação (Dry Run)',crossClusterLBLastRun:'Última Execução',crossClusterReplication:'Replicação entre Clusters',crossClusterReplicationDesc:'Replicar snapshots de VM para outro cluster (DR)',groupOverview:'Visão Geral do Grupo',groupSettings:'Configurações do Grupo',groupSettingsSaved:'Configurações do grupo salvas',replicationSchedule:'Agendamento',replicationRetention:'Retenção',maxMigrations:'Máx. Migrações por Ciclo',lbHistory:'Histórico de LB',clusterScore:'Pontuação do Cluster',noReplicationJobs:'Nenhum job de replicação entre clusters',createReplicationJob:'Criar Job de DR',replicationStarted:'Replicação iniciada',replicationJobCreated:'Job de replicação criado',replicationJobDeleted:'Job de replicação excluído',recentLbActions:'Ações recentes de LB entre clusters',noLbEvents:'Nenhum evento de LB entre clusters ainda',enableCrossClusterLB:'Habilitar Balanceamento de Carga entre Clusters',lbDescription:'Migrar automaticamente VMs entre clusters quando os limites de recursos forem excedidos',dryRunMode:'Modo Dry Run / Simulação',cpuThreshold:'Limiar de CPU (%)',lbStatus:'Status do Balanceamento de Carga',lbExplanation:'O Balanceamento de Carga entre Clusters monitora o uso de CPU e RAM em todos os clusters deste grupo. Quando um cluster excede o limiar configurado, as VMs são migradas automaticamente para um cluster menos carregado.',lbDryRunExplanation:'Habilite o modo Dry Run primeiro para revisar quais ações seriam tomadas antes de habilitar as migrações reais.',neverRun:'Nunca executado',newCrossClusterReplication:'Nova Replicação entre Clusters',confirmDeleteXRepl:'Excluir este job de replicação entre clusters?',addDrJob:'Adicionar Job de DR',scheduleCron:'Agendamento (cron)',targetStorageHint:'Nome do armazenamento no cluster de destino',maxMigrationsHint:'Limite de quantas VMs são movidas a cada ciclo de verificação (1-5)',proxlbCredit:'ProxLB por gyptazy',proxlbCreditDesc:'Nossa funcionalidade de balanceamento de carga é baseada no excelente trabalho do ProxLB. Agradecimentos especiais ao gyptazy por criar e abrir o código desta ferramenta incrível!',xReplCreated:'Replicação entre clusters criada',xReplDeleted:'Replicação entre clusters excluída',xReplStarted:'Replicação entre clusters iniciada',xReplCreateFailed:'Falha ao criar job',xReplDeleteFailed:'Falha ao excluir',xReplStartFailed:'Falha ao iniciar',lastRunPrefix:'Última',runNow:'Executar agora',loadingCrossClusterResources:'Carregando armazenamento/rede de todos os clusters...',noCommonStorages:'Nenhum armazenamento comum encontrado em todos os clusters',noCommonBridges:'Nenhuma bridge comum encontrada em todos os clusters',selectBridge:'Selecionar bridge...',crossClusterThresholdDesc:'Limiar de CPU para desequilíbrio de cluster (10-80%)',crossClusterIntervalDesc:'Tempo entre ciclos de verificação',crossClusterMaxMigrationsDesc:'Máx. migrações por ciclo de verificação',commonStorageHint:'Apenas armazenamentos disponíveis em todos os clusters deste grupo',commonBridgeHint:'Apenas bridges disponíveis em todos os clusters deste grupo',includeContainers:'Incluir Containers',includeContainersDesc:'Incluir containers (LXC) no balanceamento entre clusters',containerMigrationWarning:'Containers são reiniciados durante a migração (tempo de inatividade)',excludedVMsCrossCluster:'VMs/Containers Excluídos',excludedVMsCrossClusterDesc:'VMs e containers excluídos do balanceamento automático entre clusters',clusterExcludedVMs:'VMs Excluídas',noExcludedVMsInGroup:'Nenhuma VM excluída',selectClusterFirst:'Selecione um cluster de destino primeiro',simulationMode:'Modo de Simulação',sourceVm:'VM de Origem',targetCluster:'Cluster de Destino',targetNode:'Nó de Destino',targetStorage:'Armazenamento de Destino',targetBridge:'Rede de Destino (Bridge)',moveDisk:'Mover Disco',resizeDisk:'Redimensionar Disco',deleteSourceDisk:'Excluir origem após mover',deleteSourceDiskWarning:'O disco original permanecerá no armazenamento de origem. Você pode removê-lo manualmente depois.',move:'Mover',from:'de',noNetworkInterfaces:'Sem interfaces de rede',nameRequired:'Nome obrigatório',newVmid:'Novo VMID (opcional)',sameIdPlaceholder:'Vazio = mesmo ID',liveMigrationOption:'Live Migration (a VM continua rodando)',deleteSourceAfter:'Excluir VM de origem após migração',largeDiskWarning:'Disco Grande Detectado',largeDiskExplanation:'Live migration para discos >100GB pode falhar com "401 Unauthorized" devido ao timeout do ticket WebSocket do Proxmox. O servidor usará automaticamente a migração offline, a menos que seja forçado.',forceOnlineMigration:'Forçar migração online mesmo assim (pode falhar)',autoTokenInfo:'O PegaProx cria automaticamente tokens de API temporários para migração e os exclui após a conclusão.',clusterReachableInfo:'Os clusters devem ser capazes de se alcançar pela rede. O usuário configurado deve ter permissões para criar tokens de API.',selectNode:'Selecionar Nó',selectStorage:'Selecionar armazenamento...',loadingNodes:'Carregando nós de destino...',loadingStorageNetwork:'Carregando armazenamento/rede...',liveMigration:'Live Migration (sem tempo de inatividade)',on:'em',sameAsSource:'Mesmo que a origem',loadingStorages:'Carregando armazenamentos',free:'livre',withLocalDisks:'Com Discos Locais',withLocalDisksDesc:'Migrar discos locais para o armazenamento de destino',localDisksDetected:'Esta VM possui discos locais. A migração requer a cópia dos dados do disco.',requiredForThisVm:'Obrigatório para esta VM',cdDvdMounted:'Drive de CD/DVD Montado',cdDvdMigrationWarning:'Live migration não é possível com um CD/DVD montado. Por favor, ejete o CD/DVD primeiro ou use a migração offline.',isoMounted:'ISO/CD-ROM Montado',isoEjected:'CD-ROM Ejetado',isoMigrationWarning:'A migração pode falhar se a ISO não estiver disponível no nó de destino. Ejete o CD/DVD ou certifique-se de que a ISO existe em um armazenamento compartilhado.',localStorage:'Local',bootOrderIssue:'Problema na Ordem de Boot',bootOrderWarning:'A ordem de boot faz referência a discos inexistentes',bootOrder:'Ordem de Boot',dragToReorder:'Clique para alternar, use as setas para reordenar',noBootDevices:'Nenhum dispositivo de boot encontrado',resizeDiskHint:'Aumentar em (ex: +10G) ou novo tamanho',// SMBIOS Settings smbiosSettings:'Configurações de SMBIOS',smbiosHint:'System Management BIOS - útil para licenciamento Windows e identificação de VM',applySmbiosFromClusterConfig:'Aplicar configurações de SMBIOS da configuração do cluster',requiresRestart:'REINICIAR',readOnly:'somente leitura',autoGenerated:'gerado automaticamente',smbiosFormatHint:'Apenas letras e números permitidos (A-Za-z0-9)',preview:'Visualização',currentValue:'Valor Atual',managedByProxmox:'gerenciado pelo Proxmox',willBeAutoGenerated:'Será gerado automaticamente ao salvar',forceConntrack:'Forçar (Estado Conntrack)',forceConntrackDesc:'Forçar migração mesmo se houver entradas de conntrack',containerNoLiveMigration:'Containers não suportam live migration real',startingMigration:'Iniciando migração de',migrationStarted:'Migração iniciada:',migrationFailed:'Migração falhou',clone:'Clonar',openConsole:'Abrir Console',metrics:'Métricas',config:'Configuração',configuration:'Configuração',createVm:'Criar nova VM',createContainer:'Criar novo Container',newVm:'Nova VM',newContainer:'Novo Container',power:'Energia',snapshot:'Snapshot',editSettings:'Configurações',refreshData:'Atualizar',sshConsole:'Console SSH',noNodesAvailable:'Nenhum nó disponível. Por favor, aguarde o carregamento dos dados do cluster.',loadingStorage:'Carregando lista de armazenamento...',noIsoAvailable:'Nenhuma imagem ISO encontrada',noTemplateAvailable:'Nenhum template encontrado',noStorageAvailable:'Nenhum armazenamento disponível',noIsoStorage:'Nenhum armazenamento com conteúdo ISO encontrado',ram:'RAM',cpu:'CPU',disk:'Disco',// VM Creation Wizard @@ -3079,11 +3079,11 @@ if(isOffline){return/*#__PURE__*/React.createElement("div",{className:"relative card-hover bg-proxmox-card border-2 border-red-500/50 rounded-xl p-5 animate-slide-up",style:{animationDelay:`${index*100}ms`}},/*#__PURE__*/React.createElement("div",{className:"absolute top-2 right-2"},/*#__PURE__*/React.createElement("span",{className:"px-2 py-1 bg-red-500 text-white text-xs font-bold rounded animate-pulse"},"OFFLINE")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 mb-4"},/*#__PURE__*/React.createElement("div",{className:"p-2 bg-red-500/20 rounded-lg"},/*#__PURE__*/React.createElement(Icons.Server,{className:"text-red-400"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white"},name),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mt-1"},/*#__PURE__*/React.createElement("span",{className:"w-2 h-2 rounded-full bg-red-500 animate-pulse"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-red-400"},"offline")))),/*#__PURE__*/React.createElement("div",{className:"space-y-3 opacity-50"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"CPU"),/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"--")),/*#__PURE__*/React.createElement("div",{className:"h-2 bg-proxmox-dark rounded-full"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"RAM"),/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"--")),/*#__PURE__*/React.createElement("div",{className:"h-2 bg-proxmox-dark rounded-full"}))),metrics.last_seen&&/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-3 border-t border-red-500/30 text-xs text-red-400"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"inline w-3 h-3 mr-1"}),t('lastSeen')||'Last seen',": ",new Date(metrics.last_seen).toLocaleString()));}return/*#__PURE__*/React.createElement("div",{className:`card-hover bg-proxmox-card border rounded-xl p-5 animate-slide-up ${isUpdating?'border-blue-500/50 bg-blue-500/5':isInMaintenance?'border-yellow-500/50 bg-yellow-500/5':'border-proxmox-border'}`,style:{animationDelay:`${index*100}ms`}},isUpdating&&updateTask&&/*#__PURE__*/React.createElement("div",{className:"mb-4 -mt-1 -mx-1"},/*#__PURE__*/React.createElement("div",{className:`${updateTask.status==='failed'?'bg-red-500/10 border-red-500/30':updateTask.status==='completed'?'bg-green-500/10 border-green-500/30':'bg-blue-500/10 border-blue-500/30'} border rounded-lg p-3`},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between mb-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},updateTask.status==='completed'?/*#__PURE__*/React.createElement(Icons.CheckCircle,{className:"text-green-400"}):updateTask.status==='failed'?/*#__PURE__*/React.createElement(Icons.XCircle,{className:"text-red-400"}):/*#__PURE__*/React.createElement(Icons.RotateCw,{className:"animate-spin"}),/*#__PURE__*/React.createElement("span",{className:`${updateTask.status==='failed'?'text-red-400':updateTask.status==='completed'?'text-green-400':'text-blue-400'} font-semibold text-sm`},updateTask.status==='failed'?t('updateFailed'):updateTask.status==='completed'?t('updateCompleted'):t('updateRunning'))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:`text-xs px-2 py-0.5 rounded ${updateTask.status==='completed'?'bg-green-500/20 text-green-400':updateTask.status==='failed'?'bg-red-500/20 text-red-400':'bg-blue-500/20 text-blue-400'}`},updateTask.phase==='apt_update'?'apt update':updateTask.phase==='apt_upgrade'?'apt upgrade':updateTask.phase==='reboot'?'Reboot':updateTask.phase==='wait_online'?t('waitingForNode'):updateTask.phase==='done'?t('done'):updateTask.status),(updateTask.status==='completed'||updateTask.status==='failed')&&/*#__PURE__*/React.createElement("button",{onClick:async e=>{e.stopPropagation();try{await fetch(`${API_URL}/clusters/${clusterId}/nodes/${name}/update`,{method:'DELETE',credentials:'include',headers:getAuthHeaders()});}catch(e){}},className:"text-xs text-gray-300 hover:text-white px-2 py-1 bg-proxmox-hover hover:bg-proxmox-border rounded transition-colors",title:t('dismissUpdate')||'Dismiss'},"\u2715 ",t('dismiss')||'Dismiss'))),/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-darker rounded p-2 font-mono text-xs max-h-32 overflow-y-auto cursor-pointer",onClick:()=>setShowUpdateLog(true)},updateTask.output_lines?.slice(-5).map((line,idx)=>/*#__PURE__*/React.createElement("div",{key:idx,className:"text-gray-400 truncate"},line.text))),updateTask.status==='completed'&&/*#__PURE__*/React.createElement("div",{className:"mt-2 text-xs text-green-400"},"\u2705 ",t('updateCompleted')," ",updateTask.packages_upgraded," ",t('packagesUpdated')),updateTask.status==='failed'&&/*#__PURE__*/React.createElement("div",{className:"mt-2 space-y-2"},/*#__PURE__*/React.createElement("div",{className:"text-xs text-red-400"},"\u274C ",t('error'),": ",updateTask.error),/*#__PURE__*/React.createElement("button",{onClick:async()=>{// First clear the update status try{await fetch(`${API_URL}/clusters/${clusterId}/nodes/${name}/update`,{method:'DELETE',credentials:'include',headers:getAuthHeaders()});}catch(e){}// Then exit maintenance mode if(onMaintenanceToggle)onMaintenanceToggle(name,false);},className:"w-full px-3 py-1.5 bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 rounded-lg text-red-400 text-xs font-medium transition-colors"},t('cancelAndExitMaintenance'))))),isInMaintenance&&!isUpdating&&/*#__PURE__*/React.createElement("div",{className:"mb-4 -mt-1 -mx-1"},/*#__PURE__*/React.createElement("div",{className:"bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-3"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between mb-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Wrench,null),/*#__PURE__*/React.createElement("span",{className:"text-yellow-400 font-semibold text-sm"},t('maintenanceMode'))),/*#__PURE__*/React.createElement("span",{className:`text-xs px-2 py-0.5 rounded ${maintenanceTask?.status==='completed'?'bg-green-500/20 text-green-400':maintenanceTask?.status==='completed_with_errors'?'bg-orange-500/20 text-orange-400':maintenanceTask?.status==='evacuating'?'bg-blue-500/20 text-blue-400':maintenanceTask?.status==='failed'?'bg-red-500/20 text-red-400':'bg-yellow-500/20 text-yellow-400'}`},maintenanceTask?.status==='completed'?t('ready'):maintenanceTask?.status==='completed_with_errors'?t('completedWithErrors'):maintenanceTask?.status==='evacuating'?t('evacuating'):maintenanceTask?.status==='failed'?t('failed'):t('starting'))),maintenanceTask&&maintenanceTask.status==='evacuating'&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"mb-2"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs text-gray-400 mb-1"},/*#__PURE__*/React.createElement("span",null,t('progress')),/*#__PURE__*/React.createElement("span",null,maintenanceTask.migrated_vms," / ",maintenanceTask.total_vms," VMs")),/*#__PURE__*/React.createElement("div",{className:"h-2 bg-proxmox-dark rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"h-full bg-gradient-to-r from-yellow-500 to-yellow-400 transition-all duration-500",style:{width:`${maintenanceTask.progress_percent}%`}}))),maintenanceTask.current_vm&&/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-400"},t('migrating'),": ",/*#__PURE__*/React.createElement("span",{className:"text-white font-mono"},maintenanceTask.current_vm.name))),maintenanceTask?.status==='completed_with_errors'&&!metrics.maintenance_acknowledged&&/*#__PURE__*/React.createElement("div",{className:"space-y-3 mt-2"},/*#__PURE__*/React.createElement("div",{className:"p-3 bg-orange-500/10 border border-orange-500/30 rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-start gap-2 text-orange-400 text-xs"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-4 h-4 mt-0.5 flex-shrink-0"}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("p",{className:"font-medium"},t('migrationIncomplete')||'Migration Incomplete'),/*#__PURE__*/React.createElement("p",{className:"text-orange-300/80 mt-1"},t('someVmsOnLocalStorage')||'Some VMs could not be migrated (likely local storage). They will be stopped during reboot.')))),maintenanceTask?.failed_vms?.length>0&&/*#__PURE__*/React.createElement("div",{className:"p-2 bg-proxmox-dark rounded-lg"},/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-400 mb-1"},t('failedToMigrate')||'Failed to migrate',":"),/*#__PURE__*/React.createElement("div",{className:"flex flex-wrap gap-1"},maintenanceTask.failed_vms.slice(0,5).map((vm,idx)=>/*#__PURE__*/React.createElement("span",{key:idx,className:"px-2 py-0.5 bg-red-500/20 text-red-400 text-xs rounded font-mono"},vm.name||vm.vmid||`VM ${idx+1}`)),maintenanceTask.failed_vms.length>5&&/*#__PURE__*/React.createElement("span",{className:"px-2 py-0.5 bg-gray-500/20 text-gray-400 text-xs rounded"},"+",maintenanceTask.failed_vms.length-5," ",t('more')||'more'))),/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},/*#__PURE__*/React.createElement("button",{onClick:async()=>{if(confirm(t('forceMaintenanceWarning')||'⚠️ WARNING: Proceeding will allow actions that may stop the remaining VMs. Continue?')){// Acknowledge the warning - unlock full menu -try{await fetch(`${API_URL}/clusters/${clusterId}/nodes/${name}/maintenance/acknowledge`,{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'}});}catch(e){console.error(e);}}},className:"flex-1 flex items-center justify-center gap-2 px-3 py-1.5 bg-orange-500/20 hover:bg-orange-500/30 border border-orange-500/30 rounded-lg text-orange-400 text-xs font-medium transition-colors"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,null),t('proceedAnyway')||'Proceed Anyway'),/*#__PURE__*/React.createElement("button",{onClick:()=>onMaintenanceToggle(name,false),className:"flex-1 px-3 py-1.5 bg-gray-500/20 hover:bg-gray-500/30 border border-gray-500/30 rounded-lg text-gray-400 text-xs font-medium transition-colors"},t('exitMaintenance')))),(maintenanceTask?.status==='completed'||maintenanceTask?.status==='completed_with_errors'&&metrics.maintenance_acknowledged)&&/*#__PURE__*/React.createElement("div",{className:"space-y-2 mt-2"},maintenanceTask?.status==='completed_with_errors'&&maintenanceTask?.failed_vms?.length>0&&/*#__PURE__*/React.createElement("div",{className:"p-2 bg-orange-500/10 border border-orange-500/20 rounded-lg text-xs text-orange-400 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3"}),maintenanceTask.failed_vms.length," ",t('vmsWillBeStopped')||'VM(s) will be stopped during reboot'),/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowUpdateConfirm(true),className:"flex-1 flex items-center justify-center gap-2 px-3 py-1.5 bg-blue-500/20 hover:bg-blue-500/30 border border-blue-500/30 rounded-lg text-blue-400 text-xs font-medium transition-colors"},/*#__PURE__*/React.createElement(Icons.Download,null),t('updateAndReboot')),/*#__PURE__*/React.createElement("button",{onClick:()=>onMaintenanceToggle(name,false),className:"flex-1 px-3 py-1.5 bg-green-500/20 hover:bg-green-500/30 border border-green-500/30 rounded-lg text-green-400 text-xs font-medium transition-colors"},t('exitMaintenance'))),/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowRebootConfirm(true),disabled:actionLoading,className:"flex-1 flex items-center justify-center gap-2 px-3 py-1.5 bg-orange-500/20 hover:bg-orange-500/30 border border-orange-500/30 rounded-lg text-orange-400 text-xs font-medium transition-colors disabled:opacity-50"},/*#__PURE__*/React.createElement(Icons.RefreshCw,null),t('rebootNode')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowShutdownConfirm(true),disabled:actionLoading,className:"flex-1 flex items-center justify-center gap-2 px-3 py-1.5 bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 rounded-lg text-red-400 text-xs font-medium transition-colors disabled:opacity-50"},/*#__PURE__*/React.createElement(Icons.Power,null),t('shutdownNode'))),/*#__PURE__*/React.createElement("div",{className:"mt-3 pt-3 border-t border-proxmox-border/50 flex gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>onMoveNode&&onMoveNode(name),className:"flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/20 rounded-lg text-blue-400 text-xs font-medium transition-colors"},/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3 h-3"}),t('moveNodeToCluster')||'Move to Cluster'),/*#__PURE__*/React.createElement("button",{onClick:()=>onRemoveNode&&onRemoveNode(name),className:"flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 bg-red-500/10 hover:bg-red-500/20 border border-red-500/20 rounded-lg text-red-400 text-xs font-medium transition-colors"},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-3 h-3"}),t('removeNodeFromCluster')||'Remove'))),maintenanceTask?.failed_vms?.length>0&&!['completed','completed_with_errors'].includes(maintenanceTask?.status)&&/*#__PURE__*/React.createElement("div",{className:"mt-2 text-xs text-red-400"},"\u26A0\uFE0F ",maintenanceTask.failed_vms.length," ",t('vmsCouldNotMigrate')))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between mb-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:`p-2 rounded-lg ${isUpdating?'bg-blue-500/10':isInMaintenance?'bg-yellow-500/10':'bg-proxmox-orange/10'}`},isUpdating?/*#__PURE__*/React.createElement(Icons.RotateCw,null):isInMaintenance?/*#__PURE__*/React.createElement(Icons.Wrench,null):/*#__PURE__*/React.createElement(Icons.Server,null)),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white"},name),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mt-0.5"},/*#__PURE__*/React.createElement("span",{className:`w-2 h-2 rounded-full ${isUpdating?'bg-blue-500 animate-pulse':isInMaintenance?'bg-yellow-500':metrics.status==='online'?'bg-green-500 status-online':'bg-gray-500'}`}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-400"},isUpdating?t('updating'):isInMaintenance?t('maintenance'):metrics.status)))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},!isInMaintenance&&!isUpdating&&/*#__PURE__*/React.createElement("button",{onClick:()=>setShowMaintenanceConfirm(true),className:"p-2 rounded-lg bg-proxmox-dark hover:bg-yellow-500/20 text-gray-400 hover:text-yellow-400 transition-all",title:t('enterMaintenance')},/*#__PURE__*/React.createElement(Icons.Wrench,null)),/*#__PURE__*/React.createElement("button",{onClick:()=>onOpenNodeConfig&&onOpenNodeConfig(name),className:"p-2 rounded-lg bg-proxmox-dark hover:bg-proxmox-orange/20 text-gray-400 hover:text-proxmox-orange transition-all",title:t('nodeConfiguration')},/*#__PURE__*/React.createElement(Icons.Cog,null)),/*#__PURE__*/React.createElement("div",{className:"text-right"},/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},"Score"),/*#__PURE__*/React.createElement("div",{className:`font-mono font-bold text-lg ${metrics.score<100?'text-green-400':metrics.score<150?'text-yellow-400':'text-red-400'}`},metrics.score.toFixed(0))))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4 mb-4"},/*#__PURE__*/React.createElement(Gauge,{value:metrics.cpu_percent,label:"CPU"}),/*#__PURE__*/React.createElement(Gauge,{value:metrics.mem_percent,label:"RAM"})),/*#__PURE__*/React.createElement("div",{className:"space-y-3 pt-3 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Cpu,null)," ",t('cpuHistory')),/*#__PURE__*/React.createElement(Sparkline,{data:history.cpu,width:80,height:24})),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Memory,null)," ",t('ramHistory')),/*#__PURE__*/React.createElement(Sparkline,{data:history.mem,width:80,height:24})),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},t('ramUsage')),/*#__PURE__*/React.createElement("span",{className:"text-gray-300 font-mono"},formatBytes(metrics.mem_used)," / ",formatBytes(metrics.mem_total))),/*#__PURE__*/React.createElement("button",{onClick:()=>setExpanded(!expanded),className:"w-full flex items-center justify-center gap-1 text-xs text-gray-500 hover:text-gray-300 transition-colors pt-2"},expanded?t('showLess'):t('showMore'),/*#__PURE__*/React.createElement(Icons.ChevronDown,{className:`w-3 h-3 transition-transform ${expanded?'rotate-180':''}`})),expanded&&/*#__PURE__*/React.createElement("div",{className:"space-y-3 pt-2 border-t border-gray-700/50 animate-fade-in"},metrics.disk_percent!=null&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.HardDrive,null)," ",t('disk')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-16 h-1.5 bg-proxmox-dark rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:`h-full rounded-full ${metrics.disk_percent>90?'bg-red-500':metrics.disk_percent>75?'bg-yellow-500':'bg-green-500'}`,style:{width:`${metrics.disk_percent||0}%`}})),/*#__PURE__*/React.createElement("span",{className:"text-gray-300 font-mono w-12 text-right"},(metrics.disk_percent||0).toFixed(1),"%"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},t('diskUsage')),/*#__PURE__*/React.createElement("span",{className:"text-gray-300 font-mono"},formatBytes(metrics.disk_used||0)," / ",formatBytes(metrics.disk_total||0)))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Network,null)," Network In"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Sparkline,{data:history.netin,width:50,height:16,color:"#22c55e"}),/*#__PURE__*/React.createElement("span",{className:"text-green-400 font-mono w-16 text-right"},history.netin[history.netin.length-1]?.toFixed(1)||'0.0'," MB/s"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Network,null)," Network Out"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Sparkline,{data:history.netout,width:50,height:16,color:"#f97316"}),/*#__PURE__*/React.createElement("span",{className:"text-orange-400 font-mono w-16 text-right"},history.netout[history.netout.length-1]?.toFixed(1)||'0.0'," MB/s"))),metrics.loadavg&&/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Activity,null)," Load Avg"),/*#__PURE__*/React.createElement("span",{className:"text-gray-300 font-mono"},Array.isArray(metrics.loadavg)?metrics.loadavg.map(l=>typeof l==='number'?l.toFixed(2):l).join(' / '):typeof metrics.loadavg==='number'?metrics.loadavg.toFixed(2):'-')),metrics.cpuinfo&&/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},t('cores')),/*#__PURE__*/React.createElement("span",{className:"text-gray-300"},metrics.cpuinfo.cores||metrics.cpuinfo.cpus||'-'," \xD7 ",metrics.cpuinfo.sockets||1," Socket")),metrics.kversion&&/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"Kernel"),/*#__PURE__*/React.createElement("span",{className:"text-gray-400 font-mono text-[10px] truncate max-w-32"},metrics.kversion.split(' ')[0]||metrics.kversion)),metrics.pveversion&&/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},metrics.pveversion.startsWith('XCP')?'XCP-ng':'PVE'),/*#__PURE__*/React.createElement("span",{className:"text-gray-400 font-mono text-[10px]"},metrics.pveversion))),metrics.uptime>0&&/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500 flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.Clock,null)," ",t('uptime')),/*#__PURE__*/React.createElement("span",{className:"text-gray-300 font-mono"},formatUptime(metrics.uptime)))),showMaintenanceConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60",onClick:()=>setShowMaintenanceConfirm(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-md bg-proxmox-card border border-proxmox-border rounded-2xl shadow-2xl overflow-hidden",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"p-6 border-b border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"p-3 bg-yellow-500/10 rounded-xl"},/*#__PURE__*/React.createElement(Icons.Wrench,null)),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-bold text-white"},t('maintenanceModeTitle')),/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400"},name)))),/*#__PURE__*/React.createElement("div",{className:"p-6"},/*#__PURE__*/React.createElement("div",{className:"bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-4 mb-4"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-yellow-200"},/*#__PURE__*/React.createElement("strong",null,t('warning'),":")," ",t('maintenanceWarning').replace('Warning: ',''))),/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400 mb-6"},t('maintenanceDesc')),/*#__PURE__*/React.createElement("div",{className:"flex gap-3"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowMaintenanceConfirm(false),className:"flex-1 px-4 py-2.5 bg-proxmox-dark border border-proxmox-border rounded-lg text-gray-300 font-medium hover:bg-proxmox-hover transition-colors"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:()=>{setShowMaintenanceConfirm(false);onMaintenanceToggle(name,true);},className:"flex-1 px-4 py-2.5 bg-yellow-500 hover:bg-yellow-600 rounded-lg text-black font-medium transition-colors"},t('startMaintenance')))))),showUpdateConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60",onClick:()=>setShowUpdateConfirm(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-md bg-proxmox-card border border-proxmox-border rounded-2xl shadow-2xl overflow-hidden",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"p-6 border-b border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"p-3 bg-blue-500/10 rounded-xl"},/*#__PURE__*/React.createElement(Icons.Download,null)),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-bold text-white"},t('updateNode')),/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400"},name)))),/*#__PURE__*/React.createElement("div",{className:"p-6"},/*#__PURE__*/React.createElement("div",{className:"bg-blue-500/10 border border-blue-500/30 rounded-lg p-4 mb-4"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-blue-200"},t('updateCommand'))),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-3 mb-6 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:updateWithReboot,onChange:e=>setUpdateWithReboot(e.target.checked),className:"w-5 h-5 rounded border-proxmox-border bg-proxmox-dark text-blue-500 focus:ring-blue-500"}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"text-white font-medium"},t('rebootAfterUpdate')),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},t('recommendedForKernel')))),/*#__PURE__*/React.createElement("div",{className:"flex gap-3"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowUpdateConfirm(false),className:"flex-1 px-4 py-2.5 bg-proxmox-dark border border-proxmox-border rounded-lg text-gray-300 font-medium hover:bg-proxmox-hover transition-colors"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:()=>{setShowUpdateConfirm(false);onStartUpdate(name,updateWithReboot);},className:"flex-1 px-4 py-2.5 bg-blue-500 hover:bg-blue-600 rounded-lg text-white font-medium transition-colors"},t('startUpdate')))))),showUpdateLog&&updateTask&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center p-4 modal-backdrop bg-black/60",onClick:()=>setShowUpdateLog(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-2xl bg-proxmox-card border border-proxmox-border rounded-2xl shadow-2xl animate-scale-in overflow-hidden",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"p-4 border-b border-proxmox-border flex items-center justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement(Icons.Terminal,null),/*#__PURE__*/React.createElement("h2",{className:"font-bold text-white"},"Update Log - ",name)),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowUpdateLog(false),className:"p-2 hover:bg-proxmox-hover rounded-lg transition-colors"},/*#__PURE__*/React.createElement(Icons.X,null))),/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-darker font-mono text-xs max-h-96 overflow-y-auto"},updateTask.output_lines?.map((line,idx)=>/*#__PURE__*/React.createElement("div",{key:idx,className:"py-0.5 text-gray-300 hover:bg-proxmox-card/50"},/*#__PURE__*/React.createElement("span",{className:"text-gray-600 mr-2"},new Date(line.timestamp).toLocaleTimeString('de-DE')),line.text))))),showRebootConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center p-4 modal-backdrop bg-black/60",onClick:()=>setShowRebootConfirm(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-md bg-proxmox-card border border-orange-500/30 rounded-2xl shadow-2xl animate-scale-in overflow-hidden",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"p-6 border-b border-orange-500/30 bg-orange-500/10"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"p-3 bg-orange-500/20 rounded-xl"},/*#__PURE__*/React.createElement(Icons.RefreshCw,null)),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-bold text-white"},t('rebootNode')),/*#__PURE__*/React.createElement("p",{className:"text-sm text-orange-400"},name)))),/*#__PURE__*/React.createElement("div",{className:"p-6"},/*#__PURE__*/React.createElement("div",{className:"bg-orange-500/10 border border-orange-500/30 rounded-lg p-4 mb-4"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-orange-200"},t('rebootNodeWarning'))),/*#__PURE__*/React.createElement("div",{className:"flex gap-3"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowRebootConfirm(false),className:"flex-1 px-4 py-2.5 bg-proxmox-dark border border-proxmox-border rounded-lg text-gray-300 font-medium hover:bg-proxmox-hover transition-colors"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:async()=>{setActionLoading('reboot');setShowRebootConfirm(false);if(onNodeAction)await onNodeAction(name,'reboot');setActionLoading(null);},disabled:actionLoading,className:"flex-1 flex items-center justify-center gap-2 px-4 py-2.5 bg-orange-500 hover:bg-orange-600 rounded-lg text-white font-medium transition-colors disabled:opacity-50"},actionLoading==='reboot'?/*#__PURE__*/React.createElement(Icons.RotateCw,null):/*#__PURE__*/React.createElement(Icons.RefreshCw,null),t('rebootNow')))))),showShutdownConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center p-4 modal-backdrop bg-black/60",onClick:()=>setShowShutdownConfirm(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-md bg-proxmox-card border border-red-500/30 rounded-2xl shadow-2xl animate-scale-in overflow-hidden",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"p-6 border-b border-red-500/30 bg-red-500/10"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"p-3 bg-red-500/20 rounded-xl"},/*#__PURE__*/React.createElement(Icons.Power,null)),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-bold text-white"},t('shutdownNode')),/*#__PURE__*/React.createElement("p",{className:"text-sm text-red-400"},name)))),/*#__PURE__*/React.createElement("div",{className:"p-6"},/*#__PURE__*/React.createElement("div",{className:"bg-red-500/10 border border-red-500/30 rounded-lg p-4 mb-4"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-red-200"},t('shutdownNodeWarning'))),/*#__PURE__*/React.createElement("div",{className:"flex gap-3"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowShutdownConfirm(false),className:"flex-1 px-4 py-2.5 bg-proxmox-dark border border-proxmox-border rounded-lg text-gray-300 font-medium hover:bg-proxmox-hover transition-colors"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:async()=>{setActionLoading('shutdown');setShowShutdownConfirm(false);if(onNodeAction)await onNodeAction(name,'shutdown');setActionLoading(null);},disabled:actionLoading,className:"flex-1 flex items-center justify-center gap-2 px-4 py-2.5 bg-red-500 hover:bg-red-600 rounded-lg text-white font-medium transition-colors disabled:opacity-50"},actionLoading==='shutdown'?/*#__PURE__*/React.createElement(Icons.RotateCw,null):/*#__PURE__*/React.createElement(Icons.Power,null),t('shutdownNow')))))));}// LW: Feb 2026 - compact node row for corporate overview +try{await fetch(`${API_URL}/clusters/${clusterId}/nodes/${name}/maintenance/acknowledge`,{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'}});}catch(e){console.error(e);}}},className:"flex-1 flex items-center justify-center gap-2 px-3 py-1.5 bg-orange-500/20 hover:bg-orange-500/30 border border-orange-500/30 rounded-lg text-orange-400 text-xs font-medium transition-colors"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,null),t('proceedAnyway')||'Proceed Anyway'),/*#__PURE__*/React.createElement("button",{onClick:()=>onMaintenanceToggle(name,false),className:"flex-1 px-3 py-1.5 bg-gray-500/20 hover:bg-gray-500/30 border border-gray-500/30 rounded-lg text-gray-400 text-xs font-medium transition-colors"},t('exitMaintenance')))),(maintenanceTask?.status==='completed'||maintenanceTask?.status==='completed_with_errors'&&metrics.maintenance_acknowledged)&&/*#__PURE__*/React.createElement("div",{className:"space-y-2 mt-2"},maintenanceTask?.status==='completed_with_errors'&&maintenanceTask?.failed_vms?.length>0&&/*#__PURE__*/React.createElement("div",{className:"p-2 bg-orange-500/10 border border-orange-500/20 rounded-lg text-xs text-orange-400 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3"}),maintenanceTask.failed_vms.length," ",t('vmsWillBeStopped')||'VM(s) will be stopped during reboot'),/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowUpdateConfirm(true),className:"flex-1 flex items-center justify-center gap-2 px-3 py-1.5 bg-blue-500/20 hover:bg-blue-500/30 border border-blue-500/30 rounded-lg text-blue-400 text-xs font-medium transition-colors"},/*#__PURE__*/React.createElement(Icons.Download,null),t('updateAndReboot')),/*#__PURE__*/React.createElement("button",{onClick:()=>onMaintenanceToggle(name,false),className:"flex-1 px-3 py-1.5 bg-green-500/20 hover:bg-green-500/30 border border-green-500/30 rounded-lg text-green-400 text-xs font-medium transition-colors"},t('exitMaintenance'))),/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowRebootConfirm(true),disabled:actionLoading,className:"flex-1 flex items-center justify-center gap-2 px-3 py-1.5 bg-orange-500/20 hover:bg-orange-500/30 border border-orange-500/30 rounded-lg text-orange-400 text-xs font-medium transition-colors disabled:opacity-50"},/*#__PURE__*/React.createElement(Icons.RefreshCw,null),t('rebootNode')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowShutdownConfirm(true),disabled:actionLoading,className:"flex-1 flex items-center justify-center gap-2 px-3 py-1.5 bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 rounded-lg text-red-400 text-xs font-medium transition-colors disabled:opacity-50"},/*#__PURE__*/React.createElement(Icons.Power,null),t('shutdownNode'))),/*#__PURE__*/React.createElement("div",{className:"mt-3 pt-3 border-t border-proxmox-border/50 flex gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>onMoveNode&&onMoveNode(name),className:"flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/20 rounded-lg text-blue-400 text-xs font-medium transition-colors"},/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3 h-3"}),t('moveNodeToCluster')||'Move to Cluster'),/*#__PURE__*/React.createElement("button",{onClick:()=>onRemoveNode&&onRemoveNode(name),className:"flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 bg-red-500/10 hover:bg-red-500/20 border border-red-500/20 rounded-lg text-red-400 text-xs font-medium transition-colors"},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-3 h-3"}),t('removeNodeFromCluster')||'Remove'))),maintenanceTask?.failed_vms?.length>0&&!['completed','completed_with_errors'].includes(maintenanceTask?.status)&&/*#__PURE__*/React.createElement("div",{className:"mt-2 text-xs text-red-400"},"\u26A0\uFE0F ",maintenanceTask.failed_vms.length," ",t('vmsCouldNotMigrate')))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between mb-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:`p-2 rounded-lg ${isUpdating?'bg-blue-500/10':isInMaintenance?'bg-yellow-500/10':'bg-proxmox-orange/10'}`},isUpdating?/*#__PURE__*/React.createElement(Icons.RotateCw,null):isInMaintenance?/*#__PURE__*/React.createElement(Icons.Wrench,null):/*#__PURE__*/React.createElement(Icons.Server,null)),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white"},name),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mt-0.5"},/*#__PURE__*/React.createElement("span",{className:`w-2 h-2 rounded-full ${isUpdating?'bg-blue-500 animate-pulse':isInMaintenance?'bg-yellow-500':metrics.status==='online'?'bg-green-500 status-online':'bg-gray-500'}`}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-400"},isUpdating?t('updating'):isInMaintenance?t('maintenance'):metrics.status)))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},!isInMaintenance&&!isUpdating&&/*#__PURE__*/React.createElement("button",{onClick:()=>setShowMaintenanceConfirm(true),className:"p-2 rounded-lg bg-proxmox-dark hover:bg-yellow-500/20 text-gray-400 hover:text-yellow-400 transition-all",title:t('enterMaintenance')},/*#__PURE__*/React.createElement(Icons.Wrench,null)),/*#__PURE__*/React.createElement("button",{onClick:()=>onOpenNodeConfig&&onOpenNodeConfig(name),className:"p-2 rounded-lg bg-proxmox-dark hover:bg-proxmox-orange/20 text-gray-400 hover:text-proxmox-orange transition-all",title:t('nodeConfiguration')},/*#__PURE__*/React.createElement(Icons.Cog,null)),/*#__PURE__*/React.createElement("div",{className:"text-right",title:t('nodeScoreTooltip')},/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},"Score"),/*#__PURE__*/React.createElement("div",{className:`font-mono font-bold text-lg ${metrics.score<100?'text-green-400':metrics.score<150?'text-yellow-400':'text-red-400'}`},metrics.score.toFixed(0))))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4 mb-4"},/*#__PURE__*/React.createElement(Gauge,{value:metrics.cpu_percent,label:"CPU"}),/*#__PURE__*/React.createElement(Gauge,{value:metrics.mem_percent,label:"RAM"})),/*#__PURE__*/React.createElement("div",{className:"space-y-3 pt-3 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Cpu,null)," ",t('cpuHistory')),/*#__PURE__*/React.createElement(Sparkline,{data:history.cpu,width:80,height:24})),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Memory,null)," ",t('ramHistory')),/*#__PURE__*/React.createElement(Sparkline,{data:history.mem,width:80,height:24})),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},t('ramUsage')),/*#__PURE__*/React.createElement("span",{className:"text-gray-300 font-mono"},formatBytes(metrics.mem_used)," / ",formatBytes(metrics.mem_total))),/*#__PURE__*/React.createElement("button",{onClick:()=>setExpanded(!expanded),className:"w-full flex items-center justify-center gap-1 text-xs text-gray-500 hover:text-gray-300 transition-colors pt-2"},expanded?t('showLess'):t('showMore'),/*#__PURE__*/React.createElement(Icons.ChevronDown,{className:`w-3 h-3 transition-transform ${expanded?'rotate-180':''}`})),expanded&&/*#__PURE__*/React.createElement("div",{className:"space-y-3 pt-2 border-t border-gray-700/50 animate-fade-in"},metrics.disk_percent!=null&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.HardDrive,null)," ",t('disk')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-16 h-1.5 bg-proxmox-dark rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:`h-full rounded-full ${metrics.disk_percent>90?'bg-red-500':metrics.disk_percent>75?'bg-yellow-500':'bg-green-500'}`,style:{width:`${metrics.disk_percent||0}%`}})),/*#__PURE__*/React.createElement("span",{className:"text-gray-300 font-mono w-12 text-right"},(metrics.disk_percent||0).toFixed(1),"%"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},t('diskUsage')),/*#__PURE__*/React.createElement("span",{className:"text-gray-300 font-mono"},formatBytes(metrics.disk_used||0)," / ",formatBytes(metrics.disk_total||0)))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Network,null)," Network In"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Sparkline,{data:history.netin,width:50,height:16,color:"#22c55e"}),/*#__PURE__*/React.createElement("span",{className:"text-green-400 font-mono w-16 text-right"},history.netin[history.netin.length-1]?.toFixed(1)||'0.0'," MB/s"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Network,null)," Network Out"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Sparkline,{data:history.netout,width:50,height:16,color:"#f97316"}),/*#__PURE__*/React.createElement("span",{className:"text-orange-400 font-mono w-16 text-right"},history.netout[history.netout.length-1]?.toFixed(1)||'0.0'," MB/s"))),metrics.loadavg&&/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500 flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Activity,null)," Load Avg"),/*#__PURE__*/React.createElement("span",{className:"text-gray-300 font-mono"},Array.isArray(metrics.loadavg)?metrics.loadavg.map(l=>typeof l==='number'?l.toFixed(2):l).join(' / '):typeof metrics.loadavg==='number'?metrics.loadavg.toFixed(2):'-')),metrics.cpuinfo&&/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},t('cores')),/*#__PURE__*/React.createElement("span",{className:"text-gray-300"},metrics.cpuinfo.cores||metrics.cpuinfo.cpus||'-'," \xD7 ",metrics.cpuinfo.sockets||1," Socket")),metrics.kversion&&/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"Kernel"),/*#__PURE__*/React.createElement("span",{className:"text-gray-400 font-mono text-[10px] truncate max-w-32"},metrics.kversion.split(' ')[0]||metrics.kversion)),metrics.pveversion&&/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},metrics.pveversion.startsWith('XCP')?'XCP-ng':'PVE'),/*#__PURE__*/React.createElement("span",{className:"text-gray-400 font-mono text-[10px]"},metrics.pveversion))),metrics.uptime>0&&/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500 flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.Clock,null)," ",t('uptime')),/*#__PURE__*/React.createElement("span",{className:"text-gray-300 font-mono"},formatUptime(metrics.uptime)))),showMaintenanceConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60",onClick:()=>setShowMaintenanceConfirm(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-md bg-proxmox-card border border-proxmox-border rounded-2xl shadow-2xl overflow-hidden",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"p-6 border-b border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"p-3 bg-yellow-500/10 rounded-xl"},/*#__PURE__*/React.createElement(Icons.Wrench,null)),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-bold text-white"},t('maintenanceModeTitle')),/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400"},name)))),/*#__PURE__*/React.createElement("div",{className:"p-6"},/*#__PURE__*/React.createElement("div",{className:"bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-4 mb-4"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-yellow-200"},/*#__PURE__*/React.createElement("strong",null,t('warning'),":")," ",t('maintenanceWarning').replace('Warning: ',''))),/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400 mb-6"},t('maintenanceDesc')),/*#__PURE__*/React.createElement("div",{className:"flex gap-3"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowMaintenanceConfirm(false),className:"flex-1 px-4 py-2.5 bg-proxmox-dark border border-proxmox-border rounded-lg text-gray-300 font-medium hover:bg-proxmox-hover transition-colors"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:()=>{setShowMaintenanceConfirm(false);onMaintenanceToggle(name,true);},className:"flex-1 px-4 py-2.5 bg-yellow-500 hover:bg-yellow-600 rounded-lg text-black font-medium transition-colors"},t('startMaintenance')))))),showUpdateConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60",onClick:()=>setShowUpdateConfirm(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-md bg-proxmox-card border border-proxmox-border rounded-2xl shadow-2xl overflow-hidden",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"p-6 border-b border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"p-3 bg-blue-500/10 rounded-xl"},/*#__PURE__*/React.createElement(Icons.Download,null)),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-bold text-white"},t('updateNode')),/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400"},name)))),/*#__PURE__*/React.createElement("div",{className:"p-6"},/*#__PURE__*/React.createElement("div",{className:"bg-blue-500/10 border border-blue-500/30 rounded-lg p-4 mb-4"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-blue-200"},t('updateCommand'))),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-3 mb-6 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:updateWithReboot,onChange:e=>setUpdateWithReboot(e.target.checked),className:"w-5 h-5 rounded border-proxmox-border bg-proxmox-dark text-blue-500 focus:ring-blue-500"}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"text-white font-medium"},t('rebootAfterUpdate')),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},t('recommendedForKernel')))),/*#__PURE__*/React.createElement("div",{className:"flex gap-3"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowUpdateConfirm(false),className:"flex-1 px-4 py-2.5 bg-proxmox-dark border border-proxmox-border rounded-lg text-gray-300 font-medium hover:bg-proxmox-hover transition-colors"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:()=>{setShowUpdateConfirm(false);onStartUpdate(name,updateWithReboot);},className:"flex-1 px-4 py-2.5 bg-blue-500 hover:bg-blue-600 rounded-lg text-white font-medium transition-colors"},t('startUpdate')))))),showUpdateLog&&updateTask&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center p-4 modal-backdrop bg-black/60",onClick:()=>setShowUpdateLog(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-2xl bg-proxmox-card border border-proxmox-border rounded-2xl shadow-2xl animate-scale-in overflow-hidden",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"p-4 border-b border-proxmox-border flex items-center justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement(Icons.Terminal,null),/*#__PURE__*/React.createElement("h2",{className:"font-bold text-white"},"Update Log - ",name)),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowUpdateLog(false),className:"p-2 hover:bg-proxmox-hover rounded-lg transition-colors"},/*#__PURE__*/React.createElement(Icons.X,null))),/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-darker font-mono text-xs max-h-96 overflow-y-auto"},updateTask.output_lines?.map((line,idx)=>/*#__PURE__*/React.createElement("div",{key:idx,className:"py-0.5 text-gray-300 hover:bg-proxmox-card/50"},/*#__PURE__*/React.createElement("span",{className:"text-gray-600 mr-2"},new Date(line.timestamp).toLocaleTimeString('de-DE')),line.text))))),showRebootConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center p-4 modal-backdrop bg-black/60",onClick:()=>setShowRebootConfirm(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-md bg-proxmox-card border border-orange-500/30 rounded-2xl shadow-2xl animate-scale-in overflow-hidden",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"p-6 border-b border-orange-500/30 bg-orange-500/10"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"p-3 bg-orange-500/20 rounded-xl"},/*#__PURE__*/React.createElement(Icons.RefreshCw,null)),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-bold text-white"},t('rebootNode')),/*#__PURE__*/React.createElement("p",{className:"text-sm text-orange-400"},name)))),/*#__PURE__*/React.createElement("div",{className:"p-6"},/*#__PURE__*/React.createElement("div",{className:"bg-orange-500/10 border border-orange-500/30 rounded-lg p-4 mb-4"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-orange-200"},t('rebootNodeWarning'))),/*#__PURE__*/React.createElement("div",{className:"flex gap-3"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowRebootConfirm(false),className:"flex-1 px-4 py-2.5 bg-proxmox-dark border border-proxmox-border rounded-lg text-gray-300 font-medium hover:bg-proxmox-hover transition-colors"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:async()=>{setActionLoading('reboot');setShowRebootConfirm(false);if(onNodeAction)await onNodeAction(name,'reboot');setActionLoading(null);},disabled:actionLoading,className:"flex-1 flex items-center justify-center gap-2 px-4 py-2.5 bg-orange-500 hover:bg-orange-600 rounded-lg text-white font-medium transition-colors disabled:opacity-50"},actionLoading==='reboot'?/*#__PURE__*/React.createElement(Icons.RotateCw,null):/*#__PURE__*/React.createElement(Icons.RefreshCw,null),t('rebootNow')))))),showShutdownConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center p-4 modal-backdrop bg-black/60",onClick:()=>setShowShutdownConfirm(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-md bg-proxmox-card border border-red-500/30 rounded-2xl shadow-2xl animate-scale-in overflow-hidden",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"p-6 border-b border-red-500/30 bg-red-500/10"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"p-3 bg-red-500/20 rounded-xl"},/*#__PURE__*/React.createElement(Icons.Power,null)),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-bold text-white"},t('shutdownNode')),/*#__PURE__*/React.createElement("p",{className:"text-sm text-red-400"},name)))),/*#__PURE__*/React.createElement("div",{className:"p-6"},/*#__PURE__*/React.createElement("div",{className:"bg-red-500/10 border border-red-500/30 rounded-lg p-4 mb-4"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-red-200"},t('shutdownNodeWarning'))),/*#__PURE__*/React.createElement("div",{className:"flex gap-3"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowShutdownConfirm(false),className:"flex-1 px-4 py-2.5 bg-proxmox-dark border border-proxmox-border rounded-lg text-gray-300 font-medium hover:bg-proxmox-hover transition-colors"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:async()=>{setActionLoading('shutdown');setShowShutdownConfirm(false);if(onNodeAction)await onNodeAction(name,'shutdown');setActionLoading(null);},disabled:actionLoading,className:"flex-1 flex items-center justify-center gap-2 px-4 py-2.5 bg-red-500 hover:bg-red-600 rounded-lg text-white font-medium transition-colors disabled:opacity-50"},actionLoading==='shutdown'?/*#__PURE__*/React.createElement(Icons.RotateCw,null):/*#__PURE__*/React.createElement(Icons.Power,null),t('shutdownNow')))))));}// LW: Feb 2026 - compact node row for corporate overview // NS: Mar 2026 - added sparkline history + detail cards function NodeCompactRow({name,metrics,clusterId,onOpenNodeConfig,onMaintenanceToggle,onStartUpdate,onNodeAction,onRemoveNode,onMoveNode}){const{t}=useTranslation();const{getAuthHeaders}=useAuth();const[expanded,setExpanded]=useState(false);const[showMaintenanceConfirm,setShowMaintenanceConfirm]=useState(false);const[showRebootConfirm,setShowRebootConfirm]=useState(false);const[showShutdownConfirm,setShowShutdownConfirm]=useState(false);const[showUpdateConfirm,setShowUpdateConfirm]=useState(false);const[updateWithReboot,setUpdateWithReboot]=useState(true);const[actionLoading,setActionLoading]=useState(null);// sparkline history buffer - 20 pts like NodeCard const histRef=useRef({cpu:Array(20).fill(0),mem:Array(20).fill(0)});const lastValRef=useRef(null);const[,bump]=useState(0);useEffect(()=>{if(metrics&&metrics!==lastValRef.current){lastValRef.current=metrics;histRef.current={cpu:[...histRef.current.cpu.slice(1),metrics.cpu_percent||0],mem:[...histRef.current.mem.slice(1),metrics.mem_percent||(metrics.memory?metrics.memory.used/metrics.memory.total*100:0)]};bump(n=>n+1);}},[metrics?.cpu_percent,metrics?.mem_percent]);const isOffline=!metrics||metrics.status==='offline';const cpuPercent=metrics?.cpu_percent?.toFixed(1)||(metrics?.cpu?(metrics.cpu*100).toFixed(1):'0');const ramPercent=metrics?.mem_percent?.toFixed(1)||(metrics?.memory?(metrics.memory.used/metrics.memory.total*100).toFixed(1):'0');const isInMaintenance=metrics?.maintenance_mode;const maintenanceTask=metrics?.maintenance_task;const isUpdating=metrics?.is_updating;const updateTask=metrics?.update_task;const canUpdate=isInMaintenance&&maintenanceTask?.status&&(['completed','completed_with_errors'].includes(maintenanceTask.status)||metrics?.maintenance_acknowledged)&&!isUpdating;const formatBytes=bytes=>{if(!bytes)return'0 B';const k=1024,s=['B','KB','MB','GB','TB','PB'],i=Math.floor(Math.log(bytes)/Math.log(k));return(bytes/Math.pow(k,i)).toFixed(1)+' '+s[i];};const formatUptime=uptime=>{if(!uptime)return'-';const days=Math.floor(uptime/86400);const hours=Math.floor(uptime%86400/3600);const mins=Math.floor(uptime%3600/60);if(days>0)return`${days}d ${hours}h`;if(hours>0)return`${hours}h ${mins}m`;return`${mins}m`;};// Status colors - Clarity dark theme -const statusDotStyle=isOffline?{background:'#f54f47'}:isInMaintenance?{background:'#efc006'}:isUpdating?{background:'#49afd9'}:{background:'#60b515'};const statusLabel=isOffline?'':isInMaintenance?t('maintenance'):isUpdating?updateTask?.phase||t('updating'):'';const nodeStatusCls=isOffline?'node-offline':isInMaintenance?'node-maintenance':'node-online';return/*#__PURE__*/React.createElement("div",{className:`corp-node-row ${nodeStatusCls}`},/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-3 px-3 py-2 text-[13px] cursor-pointer ${isOffline?'opacity-50':''}`,onClick:()=>!isOffline&&setExpanded(!expanded)},!isOffline&&/*#__PURE__*/React.createElement(Icons.ChevronRight,{className:"w-3 h-3 flex-shrink-0 transition-transform",style:{color:'#728b9a',transform:expanded?'rotate(90deg)':'none'}}),/*#__PURE__*/React.createElement("span",{className:"w-2 h-2 rounded-full flex-shrink-0",style:statusDotStyle}),/*#__PURE__*/React.createElement("span",{className:"font-medium w-32 truncate",style:{color:'#e9ecef'}},name),!isOffline?/*#__PURE__*/React.createElement(React.Fragment,null,statusLabel&&/*#__PURE__*/React.createElement("span",{className:`corp-badge ${isInMaintenance?'corp-badge-locked':'corp-badge-ha'}`},statusLabel),isUpdating&&updateTask&&!isInMaintenance&&/*#__PURE__*/React.createElement("span",{className:"corp-badge",style:{background:updateTask.status==='failed'?'rgba(245,79,71,0.15)':updateTask.status==='completed'?'rgba(96,181,21,0.15)':'rgba(73,175,217,0.15)',color:updateTask.status==='failed'?'#f54f47':updateTask.status==='completed'?'#60b515':'#49afd9',border:`1px solid ${updateTask.status==='failed'?'rgba(245,79,71,0.3)':updateTask.status==='completed'?'rgba(96,181,21,0.3)':'rgba(73,175,217,0.3)'}`}},updateTask.status==='completed'?'✓ ':updateTask.status==='failed'?'✕ ':'⟳ ',updateTask.status==='failed'?t('updateFailed'):updateTask.status==='completed'?t('updateCompleted'):`${t('updating')||'Updating'}: ${updateTask.phase||'...'}`),/*#__PURE__*/React.createElement("span",{className:"w-8",style:{color:'#728b9a'}},"CPU"),/*#__PURE__*/React.createElement("div",{className:"w-20 h-1.5 flex-shrink-0 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${Math.min(cpuPercent,100)}%`,background:'#49afd9',borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{className:"w-12 text-right",style:{color:'#adbbc4'}},cpuPercent,"%"),(()=>{const d=histRef.current.cpu;const mx=Math.max(...d,1);const pts=d.map((v,i)=>`${i/19*40},${12-v/mx*12}`).join(' ');return/*#__PURE__*/React.createElement("svg",{width:"40",height:"12",className:"corp-sparkline-inline"},/*#__PURE__*/React.createElement("polyline",{fill:"none",stroke:"#49afd9",strokeWidth:"1",points:pts}),/*#__PURE__*/React.createElement("circle",{cx:"40",cy:12-d[19]/mx*12,r:"1.5",fill:"#49afd9"}));})(),/*#__PURE__*/React.createElement("span",{className:"w-8 ml-2",style:{color:'#728b9a'}},"RAM"),/*#__PURE__*/React.createElement("div",{className:"w-20 h-1.5 flex-shrink-0 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${Math.min(ramPercent,100)}%`,background:'#9b59b6',borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{className:"w-12 text-right",style:{color:'#adbbc4'}},ramPercent,"%"),(()=>{const d=histRef.current.mem;const mx=Math.max(...d,1);const pts=d.map((v,i)=>`${i/19*40},${12-v/mx*12}`).join(' ');return/*#__PURE__*/React.createElement("svg",{width:"40",height:"12",className:"corp-sparkline-inline"},/*#__PURE__*/React.createElement("polyline",{fill:"none",stroke:"#9b59b6",strokeWidth:"1",points:pts}),/*#__PURE__*/React.createElement("circle",{cx:"40",cy:12-d[19]/mx*12,r:"1.5",fill:"#9b59b6"}));})(),metrics.score!=null&&/*#__PURE__*/React.createElement("span",{className:"ml-3 w-16",style:{color:'#728b9a'}},t('score'),": ",/*#__PURE__*/React.createElement("span",{style:{color:'#adbbc4'}},Number(metrics.score).toFixed(1))),/*#__PURE__*/React.createElement("span",{className:"ml-3",style:{color:'#728b9a'}},formatUptime(metrics.uptime)),/*#__PURE__*/React.createElement("span",{className:"flex-1"}),isInMaintenance&&maintenanceTask&&maintenanceTask.status==='running'&&/*#__PURE__*/React.createElement("span",{className:"text-[11px] mr-2",style:{color:'#efc006'}},maintenanceTask.migrated_count||0,"/",maintenanceTask.total_vms||'?'," VMs"),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();onOpenNodeConfig&&onOpenNodeConfig(name);},className:"p-0.5 hover:text-white",style:{color:'#728b9a'},title:t('settings')||'Settings'},/*#__PURE__*/React.createElement(Icons.Settings,{className:"w-3.5 h-3.5"}))):/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'#f54f47'}},t('nodeUnreachable')||'Node unreachable')),expanded&&!isOffline&&metrics&&/*#__PURE__*/React.createElement("div",{className:"px-3 pb-3 pt-1 ml-5",style:{borderTop:'1px solid var(--corp-divider)'}},isUpdating&&updateTask&&/*#__PURE__*/React.createElement("div",{className:"mb-2 p-2 text-[12px]",style:{background:updateTask.status==='failed'?'rgba(245,79,71,0.08)':updateTask.status==='completed'?'rgba(96,181,21,0.08)':'rgba(73,175,217,0.08)',border:`1px solid ${updateTask.status==='failed'?'rgba(245,79,71,0.2)':updateTask.status==='completed'?'rgba(96,181,21,0.2)':'rgba(73,175,217,0.2)'}`}},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2",style:{color:updateTask.status==='failed'?'#f54f47':updateTask.status==='completed'?'#60b515':'#49afd9'}},updateTask.status==='completed'?/*#__PURE__*/React.createElement(Icons.CheckCircle,{className:"w-3 h-3"}):updateTask.status==='failed'?/*#__PURE__*/React.createElement(Icons.XCircle,{className:"w-3 h-3"}):/*#__PURE__*/React.createElement(Icons.Download,{className:"w-3 h-3"}),/*#__PURE__*/React.createElement("span",{className:"font-medium"},updateTask.status==='failed'?t('updateFailed'):updateTask.status==='completed'?t('updateCompleted'):t('updateRunning'),": ",updateTask.phase||'...')),(updateTask.status==='completed'||updateTask.status==='failed')&&/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();fetch(`${API_URL}/clusters/${clusterId}/nodes/${name}/update`,{method:'DELETE',credentials:'include',headers:getAuthHeaders()}).catch(()=>{});},className:"text-[10px] px-1.5 py-0.5 hover:text-white",style:{color:'#728b9a'}},"\u2715")),updateTask.output&&updateTask.output.length>0&&/*#__PURE__*/React.createElement("pre",{className:"mt-1 text-[10px] font-mono max-h-16 overflow-y-auto",style:{color:'#728b9a'}},(updateTask.output_lines||updateTask.output).slice(-3).map(l=>typeof l==='object'?l.text:l).join('\n')),updateTask.status==='completed'&&updateTask.packages_upgraded&&/*#__PURE__*/React.createElement("div",{className:"mt-1",style:{color:'#60b515'}},t('updateCompleted')," - ",updateTask.packages_upgraded," ",t('packagesUpdated')),updateTask.status==='failed'&&updateTask.error&&/*#__PURE__*/React.createElement("div",{className:"mt-1",style:{color:'#f54f47'}},updateTask.error)),isInMaintenance&&maintenanceTask&&/*#__PURE__*/React.createElement("div",{className:"mb-2 p-2 text-[12px]",style:{background:'rgba(239, 192, 6, 0.08)',border:'1px solid rgba(239, 192, 6, 0.2)'}},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2",style:{color:'#efc006'}},/*#__PURE__*/React.createElement(Icons.Wrench,{className:"w-3 h-3"}),/*#__PURE__*/React.createElement("span",{className:"font-medium"},t('maintenance')),/*#__PURE__*/React.createElement("span",{style:{color:'#728b9a'}},"- ",maintenanceTask.status==='completed'?t('ready'):maintenanceTask.status==='completed_with_errors'?t('completedWithErrors'):maintenanceTask.status==='evacuating'?t('evacuating'):maintenanceTask.status==='failed'?t('failed'):maintenanceTask.status==='running'?t('running')||'Running':maintenanceTask.status)),(maintenanceTask.status==='running'||maintenanceTask.status==='evacuating')&&maintenanceTask.total_vms>0&&/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-16 h-1 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${(maintenanceTask.migrated_count||maintenanceTask.migrated_vms||0)/maintenanceTask.total_vms*100}%`,background:'#efc006',borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{style:{color:'#efc006'}},maintenanceTask.migrated_count||maintenanceTask.migrated_vms||0,"/",maintenanceTask.total_vms))),maintenanceTask.failed_vms&&maintenanceTask.failed_vms.length>0&&/*#__PURE__*/React.createElement("div",{className:"mt-1",style:{color:'#f54f47'}},t('failedToMigrate'),": ",maintenanceTask.failed_vms.map(v=>v.name||v.vmid).join(', ')),!metrics.maintenance_acknowledged&&(maintenanceTask.status==='completed_with_errors'||maintenanceTask.failed_vms&&maintenanceTask.failed_vms.length>0)&&/*#__PURE__*/React.createElement("div",{className:"mt-1.5 flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();if(confirm(t('forceMaintenanceWarning')||'Proceeding may stop remaining VMs. Continue?')){fetch(`${API_URL}/clusters/${clusterId}/nodes/${name}/maintenance/acknowledge`,{method:'POST',credentials:'include',headers:{...getAuthHeaders(),'Content-Type':'application/json'}}).catch(()=>{});}},className:"px-2 py-0.5 text-[11px] font-medium",style:{background:'rgba(239,192,6,0.15)',color:'#efc006',border:'1px solid rgba(239,192,6,0.3)'}},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-2.5 h-2.5 inline mr-1"}),t('proceedAnyway')),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();onMaintenanceToggle&&onMaintenanceToggle(name,false);},className:"px-2 py-0.5 text-[11px] font-medium",style:{background:'rgba(96,181,21,0.1)',color:'#60b515',border:'1px solid rgba(96,181,21,0.3)'}},t('exitMaintenance'))),metrics.maintenance_acknowledged&&maintenanceTask.failed_vms&&maintenanceTask.failed_vms.length>0&&/*#__PURE__*/React.createElement("div",{className:"mt-1.5 text-[11px]",style:{color:'#efc006'}},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-2.5 h-2.5 inline mr-1"}),maintenanceTask.failed_vms.length," ",t('vmsWillBeStopped')||'VM(s) will be stopped during reboot')),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-grid"},/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-card"},/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-label"},t('disk')),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-value"},metrics.disk_percent?.toFixed(1)||'-',"%"),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-sub"},metrics.disk_total?`${formatBytes(metrics.disk_used||0)} / ${formatBytes(metrics.disk_total)}`:'-')),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-card"},/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-label"},"Load"),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-value"},Array.isArray(metrics.loadavg)?metrics.loadavg.map(l=>typeof l==='number'?l.toFixed(2):l).join(' / '):typeof metrics.loadavg==='number'?metrics.loadavg.toFixed(2):metrics.loadavg||'-'),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-sub"},t('cpuCores'),": ",metrics.cpus||metrics.cpu_count||(metrics.cpuinfo?`${metrics.cpuinfo.cores||metrics.cpuinfo.cpus||'-'} × ${metrics.cpuinfo.sockets||1}`:'-'))),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-card"},/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-label"},t('uptime')),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-value"},formatUptime(metrics.uptime)),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-sub"},metrics.kernel_version||metrics.kversion?metrics.kernel_version||metrics.kversion.split(' ')[0]:'')),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-card"},/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-label"},(metrics.pveversion||'').startsWith('XCP')?'XCP-ng':'PVE'),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-value",style:{fontSize:13}},metrics.pve_version||metrics.pveversion||'-'),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-sub"},metrics.kernel_version||(metrics.kversion?metrics.kversion.split(' ')[0]:'')))),/*#__PURE__*/React.createElement("div",{className:"corp-toolbar flex flex-wrap items-center gap-1 pt-1",style:{borderTop:'1px solid var(--corp-divider)'}},!isInMaintenance?/*#__PURE__*/React.createElement("button",{onClick:()=>setShowMaintenanceConfirm(true)},/*#__PURE__*/React.createElement(Icons.Wrench,{className:"w-3 h-3",style:{color:'#efc006'}})," ",t('enterMaintenance')||t('maintenance')):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("button",{onClick:()=>onMaintenanceToggle&&onMaintenanceToggle(name,false)},/*#__PURE__*/React.createElement(Icons.X,{className:"w-3 h-3",style:{color:'#60b515'}})," ",t('exitMaintenance')),canUpdate&&/*#__PURE__*/React.createElement("button",{onClick:()=>setShowUpdateConfirm(true)},/*#__PURE__*/React.createElement(Icons.Download,{className:"w-3 h-3",style:{color:'#49afd9'}})," ",t('startUpdate')),maintenanceTask?.status&&(['completed','completed_with_errors'].includes(maintenanceTask.status)||metrics?.maintenance_acknowledged)&&/*#__PURE__*/React.createElement(React.Fragment,null,onRemoveNode&&/*#__PURE__*/React.createElement("button",{onClick:()=>onRemoveNode(name)},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-3 h-3",style:{color:'#f54f47'}})," ",t('removeNodeFromCluster')),onMoveNode&&/*#__PURE__*/React.createElement("button",{onClick:()=>onMoveNode(name)},/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3 h-3"})," ",t('moveNodeToCluster')))),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowRebootConfirm(true)},/*#__PURE__*/React.createElement(Icons.RotateCw,{className:"w-3 h-3",style:{color:'#efc006'}})," ",t('reboot')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowShutdownConfirm(true)},/*#__PURE__*/React.createElement(Icons.Power,{className:"w-3 h-3",style:{color:'#f54f47'}})," ",t('shutdown')))),showMaintenanceConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-sm bg-proxmox-card border border-proxmox-border p-5"},/*#__PURE__*/React.createElement("h3",{className:"text-[14px] font-semibold mb-2",style:{color:'#e9ecef'}},t('enterMaintenance')||'Enter Maintenance Mode'),/*#__PURE__*/React.createElement("p",{className:"text-[13px] mb-4",style:{color:'#adbbc4'}},t('maintenanceWarning')||`All VMs on ${name} will be migrated to other nodes.`),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowMaintenanceConfirm(false),className:"px-3 py-1.5 text-[13px] border border-proxmox-border hover:text-white",style:{color:'#adbbc4'}},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:()=>{onMaintenanceToggle&&onMaintenanceToggle(name,true);setShowMaintenanceConfirm(false);},className:"px-3 py-1.5 text-[13px] text-white",style:{background:'#efc006',border:'1px solid #d4a905'}},t('confirm')||'Confirm')))),showUpdateConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-sm bg-proxmox-card border border-proxmox-border p-5"},/*#__PURE__*/React.createElement("h3",{className:"text-[14px] font-semibold mb-2",style:{color:'#e9ecef'}},t('startUpdate')||'Start Update'),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-2 text-[13px] mb-4",style:{color:'#adbbc4'}},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:updateWithReboot,onChange:e=>setUpdateWithReboot(e.target.checked)}),t('rebootAfterUpdate')||'Reboot after update'),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowUpdateConfirm(false),className:"px-3 py-1.5 text-[13px] border border-proxmox-border hover:text-white",style:{color:'#adbbc4'}},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:()=>{onStartUpdate&&onStartUpdate(name,updateWithReboot);setShowUpdateConfirm(false);},className:"px-3 py-1.5 text-[13px] text-white",style:{background:'#49afd9',border:'1px solid #3d9bc2'}},t('startUpdate')||'Start')))),showRebootConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-sm bg-proxmox-card border border-proxmox-border p-5"},/*#__PURE__*/React.createElement("h3",{className:"text-[14px] font-semibold mb-2",style:{color:'#e9ecef'}},t('rebootNode')||`Reboot ${name}`),/*#__PURE__*/React.createElement("p",{className:"text-[13px] mb-4",style:{color:'#adbbc4'}},t('rebootWarning')||'This will reboot the node. All VMs will be affected.'),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowRebootConfirm(false),className:"px-3 py-1.5 text-[13px] border border-proxmox-border hover:text-white",style:{color:'#adbbc4'}},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:()=>{onNodeAction&&onNodeAction(name,'reboot');setShowRebootConfirm(false);},className:"px-3 py-1.5 text-[13px] text-white",style:{background:'#efc006',border:'1px solid #d4a905'}},t('reboot'))))),showShutdownConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-sm bg-proxmox-card border border-proxmox-border p-5"},/*#__PURE__*/React.createElement("h3",{className:"text-[14px] font-semibold mb-2",style:{color:'#e9ecef'}},t('shutdownNode')||`Shutdown ${name}`),/*#__PURE__*/React.createElement("p",{className:"text-[13px] mb-4",style:{color:'#adbbc4'}},t('shutdownWarning')||'This will shut down the node.'),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowShutdownConfirm(false),className:"px-3 py-1.5 text-[13px] border border-proxmox-border hover:text-white",style:{color:'#adbbc4'}},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:()=>{onNodeAction&&onNodeAction(name,'shutdown');setShowShutdownConfirm(false);},className:"px-3 py-1.5 text-[13px] text-white",style:{background:'#f54f47',border:'1px solid #d4433d'}},t('shutdown'))))));}// Resource Table Component +const statusDotStyle=isOffline?{background:'#f54f47'}:isInMaintenance?{background:'#efc006'}:isUpdating?{background:'#49afd9'}:{background:'#60b515'};const statusLabel=isOffline?'':isInMaintenance?t('maintenance'):isUpdating?updateTask?.phase||t('updating'):'';const nodeStatusCls=isOffline?'node-offline':isInMaintenance?'node-maintenance':'node-online';return/*#__PURE__*/React.createElement("div",{className:`corp-node-row ${nodeStatusCls}`},/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-3 px-3 py-2 text-[13px] cursor-pointer ${isOffline?'opacity-50':''}`,onClick:()=>!isOffline&&setExpanded(!expanded)},!isOffline&&/*#__PURE__*/React.createElement(Icons.ChevronRight,{className:"w-3 h-3 flex-shrink-0 transition-transform",style:{color:'#728b9a',transform:expanded?'rotate(90deg)':'none'}}),/*#__PURE__*/React.createElement("span",{className:"w-2 h-2 rounded-full flex-shrink-0",style:statusDotStyle}),/*#__PURE__*/React.createElement("span",{className:"font-medium w-32 truncate",style:{color:'#e9ecef'}},name),!isOffline?/*#__PURE__*/React.createElement(React.Fragment,null,statusLabel&&/*#__PURE__*/React.createElement("span",{className:`corp-badge ${isInMaintenance?'corp-badge-locked':'corp-badge-ha'}`},statusLabel),isUpdating&&updateTask&&!isInMaintenance&&/*#__PURE__*/React.createElement("span",{className:"corp-badge",style:{background:updateTask.status==='failed'?'rgba(245,79,71,0.15)':updateTask.status==='completed'?'rgba(96,181,21,0.15)':'rgba(73,175,217,0.15)',color:updateTask.status==='failed'?'#f54f47':updateTask.status==='completed'?'#60b515':'#49afd9',border:`1px solid ${updateTask.status==='failed'?'rgba(245,79,71,0.3)':updateTask.status==='completed'?'rgba(96,181,21,0.3)':'rgba(73,175,217,0.3)'}`}},updateTask.status==='completed'?'✓ ':updateTask.status==='failed'?'✕ ':'⟳ ',updateTask.status==='failed'?t('updateFailed'):updateTask.status==='completed'?t('updateCompleted'):`${t('updating')||'Updating'}: ${updateTask.phase||'...'}`),/*#__PURE__*/React.createElement("span",{className:"w-8",style:{color:'#728b9a'}},"CPU"),/*#__PURE__*/React.createElement("div",{className:"w-20 h-1.5 flex-shrink-0 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${Math.min(cpuPercent,100)}%`,background:'#49afd9',borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{className:"w-12 text-right",style:{color:'#adbbc4'}},cpuPercent,"%"),(()=>{const d=histRef.current.cpu;const mx=Math.max(...d,1);const pts=d.map((v,i)=>`${i/19*40},${12-v/mx*12}`).join(' ');return/*#__PURE__*/React.createElement("svg",{width:"40",height:"12",className:"corp-sparkline-inline"},/*#__PURE__*/React.createElement("polyline",{fill:"none",stroke:"#49afd9",strokeWidth:"1",points:pts}),/*#__PURE__*/React.createElement("circle",{cx:"40",cy:12-d[19]/mx*12,r:"1.5",fill:"#49afd9"}));})(),/*#__PURE__*/React.createElement("span",{className:"w-8 ml-2",style:{color:'#728b9a'}},"RAM"),/*#__PURE__*/React.createElement("div",{className:"w-20 h-1.5 flex-shrink-0 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${Math.min(ramPercent,100)}%`,background:'#9b59b6',borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{className:"w-12 text-right",style:{color:'#adbbc4'}},ramPercent,"%"),(()=>{const d=histRef.current.mem;const mx=Math.max(...d,1);const pts=d.map((v,i)=>`${i/19*40},${12-v/mx*12}`).join(' ');return/*#__PURE__*/React.createElement("svg",{width:"40",height:"12",className:"corp-sparkline-inline"},/*#__PURE__*/React.createElement("polyline",{fill:"none",stroke:"#9b59b6",strokeWidth:"1",points:pts}),/*#__PURE__*/React.createElement("circle",{cx:"40",cy:12-d[19]/mx*12,r:"1.5",fill:"#9b59b6"}));})(),metrics.score!=null&&/*#__PURE__*/React.createElement("span",{className:"ml-3 w-16",style:{color:'#728b9a'},title:t('nodeScoreTooltip')},t('score'),": ",/*#__PURE__*/React.createElement("span",{style:{color:'#adbbc4'}},Number(metrics.score).toFixed(1))),/*#__PURE__*/React.createElement("span",{className:"ml-3",style:{color:'#728b9a'}},formatUptime(metrics.uptime)),/*#__PURE__*/React.createElement("span",{className:"flex-1"}),isInMaintenance&&maintenanceTask&&maintenanceTask.status==='running'&&/*#__PURE__*/React.createElement("span",{className:"text-[11px] mr-2",style:{color:'#efc006'}},maintenanceTask.migrated_count||0,"/",maintenanceTask.total_vms||'?'," VMs"),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();onOpenNodeConfig&&onOpenNodeConfig(name);},className:"p-0.5 hover:text-white",style:{color:'#728b9a'},title:t('settings')||'Settings'},/*#__PURE__*/React.createElement(Icons.Settings,{className:"w-3.5 h-3.5"}))):/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'#f54f47'}},t('nodeUnreachable')||'Node unreachable')),expanded&&!isOffline&&metrics&&/*#__PURE__*/React.createElement("div",{className:"px-3 pb-3 pt-1 ml-5",style:{borderTop:'1px solid var(--corp-divider)'}},isUpdating&&updateTask&&/*#__PURE__*/React.createElement("div",{className:"mb-2 p-2 text-[12px]",style:{background:updateTask.status==='failed'?'rgba(245,79,71,0.08)':updateTask.status==='completed'?'rgba(96,181,21,0.08)':'rgba(73,175,217,0.08)',border:`1px solid ${updateTask.status==='failed'?'rgba(245,79,71,0.2)':updateTask.status==='completed'?'rgba(96,181,21,0.2)':'rgba(73,175,217,0.2)'}`}},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2",style:{color:updateTask.status==='failed'?'#f54f47':updateTask.status==='completed'?'#60b515':'#49afd9'}},updateTask.status==='completed'?/*#__PURE__*/React.createElement(Icons.CheckCircle,{className:"w-3 h-3"}):updateTask.status==='failed'?/*#__PURE__*/React.createElement(Icons.XCircle,{className:"w-3 h-3"}):/*#__PURE__*/React.createElement(Icons.Download,{className:"w-3 h-3"}),/*#__PURE__*/React.createElement("span",{className:"font-medium"},updateTask.status==='failed'?t('updateFailed'):updateTask.status==='completed'?t('updateCompleted'):t('updateRunning'),": ",updateTask.phase||'...')),(updateTask.status==='completed'||updateTask.status==='failed')&&/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();fetch(`${API_URL}/clusters/${clusterId}/nodes/${name}/update`,{method:'DELETE',credentials:'include',headers:getAuthHeaders()}).catch(()=>{});},className:"text-[10px] px-1.5 py-0.5 hover:text-white",style:{color:'#728b9a'}},"\u2715")),updateTask.output&&updateTask.output.length>0&&/*#__PURE__*/React.createElement("pre",{className:"mt-1 text-[10px] font-mono max-h-16 overflow-y-auto",style:{color:'#728b9a'}},(updateTask.output_lines||updateTask.output).slice(-3).map(l=>typeof l==='object'?l.text:l).join('\n')),updateTask.status==='completed'&&updateTask.packages_upgraded&&/*#__PURE__*/React.createElement("div",{className:"mt-1",style:{color:'#60b515'}},t('updateCompleted')," - ",updateTask.packages_upgraded," ",t('packagesUpdated')),updateTask.status==='failed'&&updateTask.error&&/*#__PURE__*/React.createElement("div",{className:"mt-1",style:{color:'#f54f47'}},updateTask.error)),isInMaintenance&&maintenanceTask&&/*#__PURE__*/React.createElement("div",{className:"mb-2 p-2 text-[12px]",style:{background:'rgba(239, 192, 6, 0.08)',border:'1px solid rgba(239, 192, 6, 0.2)'}},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2",style:{color:'#efc006'}},/*#__PURE__*/React.createElement(Icons.Wrench,{className:"w-3 h-3"}),/*#__PURE__*/React.createElement("span",{className:"font-medium"},t('maintenance')),/*#__PURE__*/React.createElement("span",{style:{color:'#728b9a'}},"- ",maintenanceTask.status==='completed'?t('ready'):maintenanceTask.status==='completed_with_errors'?t('completedWithErrors'):maintenanceTask.status==='evacuating'?t('evacuating'):maintenanceTask.status==='failed'?t('failed'):maintenanceTask.status==='running'?t('running')||'Running':maintenanceTask.status)),(maintenanceTask.status==='running'||maintenanceTask.status==='evacuating')&&maintenanceTask.total_vms>0&&/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-16 h-1 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${(maintenanceTask.migrated_count||maintenanceTask.migrated_vms||0)/maintenanceTask.total_vms*100}%`,background:'#efc006',borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{style:{color:'#efc006'}},maintenanceTask.migrated_count||maintenanceTask.migrated_vms||0,"/",maintenanceTask.total_vms))),maintenanceTask.failed_vms&&maintenanceTask.failed_vms.length>0&&/*#__PURE__*/React.createElement("div",{className:"mt-1",style:{color:'#f54f47'}},t('failedToMigrate'),": ",maintenanceTask.failed_vms.map(v=>v.name||v.vmid).join(', ')),!metrics.maintenance_acknowledged&&(maintenanceTask.status==='completed_with_errors'||maintenanceTask.failed_vms&&maintenanceTask.failed_vms.length>0)&&/*#__PURE__*/React.createElement("div",{className:"mt-1.5 flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();if(confirm(t('forceMaintenanceWarning')||'Proceeding may stop remaining VMs. Continue?')){fetch(`${API_URL}/clusters/${clusterId}/nodes/${name}/maintenance/acknowledge`,{method:'POST',credentials:'include',headers:{...getAuthHeaders(),'Content-Type':'application/json'}}).catch(()=>{});}},className:"px-2 py-0.5 text-[11px] font-medium",style:{background:'rgba(239,192,6,0.15)',color:'#efc006',border:'1px solid rgba(239,192,6,0.3)'}},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-2.5 h-2.5 inline mr-1"}),t('proceedAnyway')),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();onMaintenanceToggle&&onMaintenanceToggle(name,false);},className:"px-2 py-0.5 text-[11px] font-medium",style:{background:'rgba(96,181,21,0.1)',color:'#60b515',border:'1px solid rgba(96,181,21,0.3)'}},t('exitMaintenance'))),metrics.maintenance_acknowledged&&maintenanceTask.failed_vms&&maintenanceTask.failed_vms.length>0&&/*#__PURE__*/React.createElement("div",{className:"mt-1.5 text-[11px]",style:{color:'#efc006'}},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-2.5 h-2.5 inline mr-1"}),maintenanceTask.failed_vms.length," ",t('vmsWillBeStopped')||'VM(s) will be stopped during reboot')),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-grid"},/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-card"},/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-label"},t('disk')),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-value"},metrics.disk_percent?.toFixed(1)||'-',"%"),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-sub"},metrics.disk_total?`${formatBytes(metrics.disk_used||0)} / ${formatBytes(metrics.disk_total)}`:'-')),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-card"},/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-label"},"Load"),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-value"},Array.isArray(metrics.loadavg)?metrics.loadavg.map(l=>typeof l==='number'?l.toFixed(2):l).join(' / '):typeof metrics.loadavg==='number'?metrics.loadavg.toFixed(2):metrics.loadavg||'-'),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-sub"},t('cpuCores'),": ",metrics.cpus||metrics.cpu_count||(metrics.cpuinfo?`${metrics.cpuinfo.cores||metrics.cpuinfo.cpus||'-'} × ${metrics.cpuinfo.sockets||1}`:'-'))),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-card"},/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-label"},t('uptime')),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-value"},formatUptime(metrics.uptime)),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-sub"},metrics.kernel_version||metrics.kversion?metrics.kernel_version||metrics.kversion.split(' ')[0]:'')),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-card"},/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-label"},(metrics.pveversion||'').startsWith('XCP')?'XCP-ng':'PVE'),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-value",style:{fontSize:13}},metrics.pve_version||metrics.pveversion||'-'),/*#__PURE__*/React.createElement("div",{className:"corp-node-detail-sub"},metrics.kernel_version||(metrics.kversion?metrics.kversion.split(' ')[0]:'')))),/*#__PURE__*/React.createElement("div",{className:"corp-toolbar flex flex-wrap items-center gap-1 pt-1",style:{borderTop:'1px solid var(--corp-divider)'}},!isInMaintenance?/*#__PURE__*/React.createElement("button",{onClick:()=>setShowMaintenanceConfirm(true)},/*#__PURE__*/React.createElement(Icons.Wrench,{className:"w-3 h-3",style:{color:'#efc006'}})," ",t('enterMaintenance')||t('maintenance')):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("button",{onClick:()=>onMaintenanceToggle&&onMaintenanceToggle(name,false)},/*#__PURE__*/React.createElement(Icons.X,{className:"w-3 h-3",style:{color:'#60b515'}})," ",t('exitMaintenance')),canUpdate&&/*#__PURE__*/React.createElement("button",{onClick:()=>setShowUpdateConfirm(true)},/*#__PURE__*/React.createElement(Icons.Download,{className:"w-3 h-3",style:{color:'#49afd9'}})," ",t('startUpdate')),maintenanceTask?.status&&(['completed','completed_with_errors'].includes(maintenanceTask.status)||metrics?.maintenance_acknowledged)&&/*#__PURE__*/React.createElement(React.Fragment,null,onRemoveNode&&/*#__PURE__*/React.createElement("button",{onClick:()=>onRemoveNode(name)},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-3 h-3",style:{color:'#f54f47'}})," ",t('removeNodeFromCluster')),onMoveNode&&/*#__PURE__*/React.createElement("button",{onClick:()=>onMoveNode(name)},/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3 h-3"})," ",t('moveNodeToCluster')))),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowRebootConfirm(true)},/*#__PURE__*/React.createElement(Icons.RotateCw,{className:"w-3 h-3",style:{color:'#efc006'}})," ",t('reboot')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowShutdownConfirm(true)},/*#__PURE__*/React.createElement(Icons.Power,{className:"w-3 h-3",style:{color:'#f54f47'}})," ",t('shutdown')))),showMaintenanceConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-sm bg-proxmox-card border border-proxmox-border p-5"},/*#__PURE__*/React.createElement("h3",{className:"text-[14px] font-semibold mb-2",style:{color:'#e9ecef'}},t('enterMaintenance')||'Enter Maintenance Mode'),/*#__PURE__*/React.createElement("p",{className:"text-[13px] mb-4",style:{color:'#adbbc4'}},t('maintenanceWarning')||`All VMs on ${name} will be migrated to other nodes.`),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowMaintenanceConfirm(false),className:"px-3 py-1.5 text-[13px] border border-proxmox-border hover:text-white",style:{color:'#adbbc4'}},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:()=>{onMaintenanceToggle&&onMaintenanceToggle(name,true);setShowMaintenanceConfirm(false);},className:"px-3 py-1.5 text-[13px] text-white",style:{background:'#efc006',border:'1px solid #d4a905'}},t('confirm')||'Confirm')))),showUpdateConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-sm bg-proxmox-card border border-proxmox-border p-5"},/*#__PURE__*/React.createElement("h3",{className:"text-[14px] font-semibold mb-2",style:{color:'#e9ecef'}},t('startUpdate')||'Start Update'),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-2 text-[13px] mb-4",style:{color:'#adbbc4'}},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:updateWithReboot,onChange:e=>setUpdateWithReboot(e.target.checked)}),t('rebootAfterUpdate')||'Reboot after update'),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowUpdateConfirm(false),className:"px-3 py-1.5 text-[13px] border border-proxmox-border hover:text-white",style:{color:'#adbbc4'}},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:()=>{onStartUpdate&&onStartUpdate(name,updateWithReboot);setShowUpdateConfirm(false);},className:"px-3 py-1.5 text-[13px] text-white",style:{background:'#49afd9',border:'1px solid #3d9bc2'}},t('startUpdate')||'Start')))),showRebootConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-sm bg-proxmox-card border border-proxmox-border p-5"},/*#__PURE__*/React.createElement("h3",{className:"text-[14px] font-semibold mb-2",style:{color:'#e9ecef'}},t('rebootNode')||`Reboot ${name}`),/*#__PURE__*/React.createElement("p",{className:"text-[13px] mb-4",style:{color:'#adbbc4'}},t('rebootWarning')||'This will reboot the node. All VMs will be affected.'),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowRebootConfirm(false),className:"px-3 py-1.5 text-[13px] border border-proxmox-border hover:text-white",style:{color:'#adbbc4'}},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:()=>{onNodeAction&&onNodeAction(name,'reboot');setShowRebootConfirm(false);},className:"px-3 py-1.5 text-[13px] text-white",style:{background:'#efc006',border:'1px solid #d4a905'}},t('reboot'))))),showShutdownConfirm&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-sm bg-proxmox-card border border-proxmox-border p-5"},/*#__PURE__*/React.createElement("h3",{className:"text-[14px] font-semibold mb-2",style:{color:'#e9ecef'}},t('shutdownNode')||`Shutdown ${name}`),/*#__PURE__*/React.createElement("p",{className:"text-[13px] mb-4",style:{color:'#adbbc4'}},t('shutdownWarning')||'This will shut down the node.'),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowShutdownConfirm(false),className:"px-3 py-1.5 text-[13px] border border-proxmox-border hover:text-white",style:{color:'#adbbc4'}},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:()=>{onNodeAction&&onNodeAction(name,'shutdown');setShowShutdownConfirm(false);},className:"px-3 py-1.5 text-[13px] text-white",style:{background:'#f54f47',border:'1px solid #d4433d'}},t('shutdown'))))));}// Resource Table Component // LW: The main VM/CT list - supports cards, table, and detail view // NS: Added bulk select for mass operations (migration, etc.) // This component does a lot... might need to split it up eventually @@ -3240,7 +3240,7 @@ const withMetrics=clusterStats.filter(c=>c.hasMetrics);const totals={clusters:groupClusters.length,connectedClusters:groupClusters.filter(c=>c.connected).length,totalNodes:clusterStats.reduce((acc,c)=>acc+c.nodeCount,0),onlineNodes:clusterStats.reduce((acc,c)=>acc+c.onlineNodes,0),totalVms:clusterStats.reduce((acc,c)=>acc+c.totalVms,0),runningVms:clusterStats.reduce((acc,c)=>acc+c.runningVms,0),avgCpu:withMetrics.length>0?clusterStats.reduce((acc,c)=>acc+c.avgCpu,0)/withMetrics.length:0,avgMem:withMetrics.length>0?clusterStats.reduce((acc,c)=>acc+c.avgMem,0)/withMetrics.length:0,avgStorage:withMetrics.length>0?clusterStats.reduce((acc,c)=>acc+c.avgStorage,0)/withMetrics.length:0,totalAlerts:clusterStats.reduce((acc,c)=>acc+c.alerts.length,0)};const lbEnabled=!!group.cross_cluster_lb_enabled;// NS: sparkline - same as AllClustersOverview const Sparkline=({data,color,height=20,width=60})=>{if(!data||data.length<2)return/*#__PURE__*/React.createElement("div",{style:{width,height},className:"bg-proxmox-dark/50 rounded"});const max=Math.max(...data,1);const min=Math.min(...data,0);const range=max-min||1;const points=data.map((v,i)=>`${i/(data.length-1)*width},${height-(v-min)/range*height}`).join(' ');return/*#__PURE__*/React.createElement("svg",{width:width,height:height,className:"overflow-visible"},/*#__PURE__*/React.createElement("polyline",{fill:"none",stroke:color,strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",points:points}),/*#__PURE__*/React.createElement("circle",{cx:width,cy:height-(data[data.length-1]-min)/range*height,r:"2",fill:color}));};// circular progress gauge const CircularProgress=({value,size=60,strokeWidth=6,color})=>{const radius=(size-strokeWidth)/2;const circumference=radius*2*Math.PI;const offset=circumference-value/100*circumference;return/*#__PURE__*/React.createElement("div",{className:"relative flex-shrink-0",style:{width:size,height:size}},/*#__PURE__*/React.createElement("svg",{className:"transform -rotate-90 overflow-visible",width:size,height:size},/*#__PURE__*/React.createElement("circle",{cx:size/2,cy:size/2,r:radius,fill:"none",stroke:"currentColor",strokeWidth:strokeWidth,className:"text-gray-700"}),/*#__PURE__*/React.createElement("circle",{cx:size/2,cy:size/2,r:radius,fill:"none",stroke:color,strokeWidth:strokeWidth,strokeLinecap:"round",strokeDasharray:circumference,strokeDashoffset:offset,className:"transition-all duration-500"})),/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 flex items-center justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-white"},value.toFixed(0),"%")));};const getHealthColor=score=>{if(score>=80)return{bg:'bg-green-500/20',text:'text-green-400',border:'border-green-500/30',color:'#22c55e'};if(score>=60)return{bg:'bg-yellow-500/20',text:'text-yellow-400',border:'border-yellow-500/30',color:'#eab308'};if(score>=40)return{bg:'bg-orange-500/20',text:'text-orange-400',border:'border-orange-500/30',color:'#f97316'};return{bg:'bg-red-500/20',text:'text-red-400',border:'border-red-500/30',color:'#ef4444'};};const formatLastUpdate=date=>{if(!date)return'-';const now=new Date();const diff=Math.floor((now-new Date(date))/1000);if(diff<10)return t('justNow')||'just now';if(diff<60)return`${diff}s ago`;if(diff<3600)return`${Math.floor(diff/60)}m ago`;return new Date(date).toLocaleTimeString();};const SortButton=({field,label})=>/*#__PURE__*/React.createElement("button",{onClick:()=>{setSortBy(field);setSortDir(sortBy===field&&sortDir==='asc'?'desc':'asc');},className:`px-3 py-1.5 text-sm rounded-lg transition-colors ${sortBy===field?'bg-proxmox-orange/20 text-proxmox-orange font-medium':'text-gray-400 hover:text-white hover:bg-proxmox-dark'}`},label," ",sortBy===field&&(sortDir==='asc'?'↑':'↓'));// MK: cluster card - clickable, same layout as the all-clusters one -const ClusterCard=({cluster})=>{const healthColors=getHealthColor(cluster.healthScore);const hasAlerts=cluster.alerts.length>0;const errorCount=cluster.alerts.filter(a=>a.type==='error').length;return/*#__PURE__*/React.createElement("div",{onClick:()=>onSelectCluster(cluster),className:"group relative bg-proxmox-card border border-proxmox-border rounded-xl overflow-hidden cursor-pointer hover:border-proxmox-orange/50 transition-all duration-300 hover:shadow-lg hover:shadow-proxmox-orange/10"},/*#__PURE__*/React.createElement("div",{className:`h-1.5 ${cluster.connected?'bg-gradient-to-r from-green-500 via-emerald-500 to-green-500':'bg-gradient-to-r from-red-500 via-red-600 to-red-500'}`}),/*#__PURE__*/React.createElement("div",{className:"p-5"},/*#__PURE__*/React.createElement("div",{className:"flex items-start justify-between mb-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:`relative w-12 h-12 rounded-xl ${cluster.connected?'bg-gradient-to-br from-green-500/20 to-emerald-500/20':'bg-gradient-to-br from-red-500/20 to-red-600/20'} flex items-center justify-center`},/*#__PURE__*/React.createElement(Icons.Server,{className:`w-6 h-6 ${cluster.connected?'text-green-400':'text-red-400'}`}),cluster.connected&&/*#__PURE__*/React.createElement("div",{className:"absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-proxmox-card animate-pulse"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white text-base group-hover:text-proxmox-orange transition-colors"},cluster.display_name||cluster.name),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},cluster.host))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},hasAlerts&&/*#__PURE__*/React.createElement("div",{className:`px-2.5 py-1 rounded-lg flex items-center gap-1.5 ${errorCount>0?'bg-red-500/20 border border-red-500/30':'bg-yellow-500/20 border border-yellow-500/30'}`},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:`w-3.5 h-3.5 ${errorCount>0?'text-red-400':'text-yellow-400'}`}),/*#__PURE__*/React.createElement("span",{className:`text-xs font-medium ${errorCount>0?'text-red-400':'text-yellow-400'}`},cluster.alerts.length)),cluster.connected?/*#__PURE__*/React.createElement("div",{className:`px-2.5 py-1 rounded-lg ${healthColors.bg} ${healthColors.border} border`},/*#__PURE__*/React.createElement("span",{className:`text-xs font-semibold ${healthColors.text}`},cluster.healthScore,"%")):/*#__PURE__*/React.createElement("div",{className:"px-2.5 py-1 rounded-lg bg-red-500/20 border border-red-500/30"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-semibold text-red-400"},"Offline")))),cluster.hasMetrics?/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-5 gap-3"},/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:`text-xl font-bold ${cluster.onlineNodes===cluster.nodeCount?'text-green-400':'text-yellow-400'}`},cluster.onlineNodes,"/",cluster.nodeCount),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},t('nodes'))),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center gap-1"},/*#__PURE__*/React.createElement(CircularProgress,{value:cluster.avgCpu,size:44,strokeWidth:4,color:cluster.avgCpu>80?'#ef4444':cluster.avgCpu>60?'#eab308':'#22c55e'}),/*#__PURE__*/React.createElement(Sparkline,{data:cluster.cpuHistory,color:cluster.avgCpu>80?'#ef4444':'#22c55e',height:14,width:40})),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},"CPU")),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center gap-1"},/*#__PURE__*/React.createElement(CircularProgress,{value:cluster.avgMem,size:44,strokeWidth:4,color:cluster.avgMem>80?'#ef4444':cluster.avgMem>60?'#eab308':'#3b82f6'}),/*#__PURE__*/React.createElement(Sparkline,{data:cluster.ramHistory,color:cluster.avgMem>80?'#ef4444':'#3b82f6',height:14,width:40})),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},"RAM")),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"flex justify-center"},/*#__PURE__*/React.createElement(CircularProgress,{value:cluster.avgStorage,size:44,strokeWidth:4,color:cluster.avgStorage>80?'#ef4444':cluster.avgStorage>60?'#eab308':'#8b5cf6'})),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},t('storage')||'Disk')),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"text-xl font-bold"},/*#__PURE__*/React.createElement("span",{className:"text-green-400"},cluster.runningVms),/*#__PURE__*/React.createElement("span",{className:"text-gray-500 text-sm"},"/",cluster.totalVms)),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},t('vms')))):/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center h-20 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-4 h-4 mr-2"}),t('noDataAvailable')||'No data available'),/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-3 border-t border-proxmox-border/50 flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},t('updated')||'Updated',": ",formatLastUpdate(cluster.lastUpdate)),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-4 h-4 text-gray-500 group-hover:text-proxmox-orange transition-colors"}))));};// LW: Feb 2026 - Corporate variant for GroupOverview +const ClusterCard=({cluster})=>{const healthColors=getHealthColor(cluster.healthScore);const hasAlerts=cluster.alerts.length>0;const errorCount=cluster.alerts.filter(a=>a.type==='error').length;return/*#__PURE__*/React.createElement("div",{onClick:()=>onSelectCluster(cluster),className:"group relative bg-proxmox-card border border-proxmox-border rounded-xl overflow-hidden cursor-pointer hover:border-proxmox-orange/50 transition-all duration-300 hover:shadow-lg hover:shadow-proxmox-orange/10"},/*#__PURE__*/React.createElement("div",{className:`h-1.5 ${cluster.connected?'bg-gradient-to-r from-green-500 via-emerald-500 to-green-500':'bg-gradient-to-r from-red-500 via-red-600 to-red-500'}`}),/*#__PURE__*/React.createElement("div",{className:"p-5"},/*#__PURE__*/React.createElement("div",{className:"flex items-start justify-between mb-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:`relative w-12 h-12 rounded-xl ${cluster.connected?'bg-gradient-to-br from-green-500/20 to-emerald-500/20':'bg-gradient-to-br from-red-500/20 to-red-600/20'} flex items-center justify-center`},/*#__PURE__*/React.createElement(Icons.Server,{className:`w-6 h-6 ${cluster.connected?'text-green-400':'text-red-400'}`}),cluster.connected&&/*#__PURE__*/React.createElement("div",{className:"absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-proxmox-card animate-pulse"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white text-base group-hover:text-proxmox-orange transition-colors"},cluster.display_name||cluster.name),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},cluster.host))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},hasAlerts&&/*#__PURE__*/React.createElement("div",{className:`px-2.5 py-1 rounded-lg flex items-center gap-1.5 ${errorCount>0?'bg-red-500/20 border border-red-500/30':'bg-yellow-500/20 border border-yellow-500/30'}`},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:`w-3.5 h-3.5 ${errorCount>0?'text-red-400':'text-yellow-400'}`}),/*#__PURE__*/React.createElement("span",{className:`text-xs font-medium ${errorCount>0?'text-red-400':'text-yellow-400'}`},cluster.alerts.length)),cluster.connected?/*#__PURE__*/React.createElement("div",{className:`px-2.5 py-1 rounded-lg ${healthColors.bg} ${healthColors.border} border`,title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("span",{className:`text-xs font-semibold ${healthColors.text}`},cluster.healthScore,"%")):/*#__PURE__*/React.createElement("div",{className:"px-2.5 py-1 rounded-lg bg-red-500/20 border border-red-500/30"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-semibold text-red-400"},"Offline")))),cluster.hasMetrics?/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-5 gap-3"},/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:`text-xl font-bold ${cluster.onlineNodes===cluster.nodeCount?'text-green-400':'text-yellow-400'}`},cluster.onlineNodes,"/",cluster.nodeCount),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},t('nodes'))),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center gap-1"},/*#__PURE__*/React.createElement(CircularProgress,{value:cluster.avgCpu,size:44,strokeWidth:4,color:cluster.avgCpu>80?'#ef4444':cluster.avgCpu>60?'#eab308':'#22c55e'}),/*#__PURE__*/React.createElement(Sparkline,{data:cluster.cpuHistory,color:cluster.avgCpu>80?'#ef4444':'#22c55e',height:14,width:40})),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},"CPU")),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center gap-1"},/*#__PURE__*/React.createElement(CircularProgress,{value:cluster.avgMem,size:44,strokeWidth:4,color:cluster.avgMem>80?'#ef4444':cluster.avgMem>60?'#eab308':'#3b82f6'}),/*#__PURE__*/React.createElement(Sparkline,{data:cluster.ramHistory,color:cluster.avgMem>80?'#ef4444':'#3b82f6',height:14,width:40})),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},"RAM")),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"flex justify-center"},/*#__PURE__*/React.createElement(CircularProgress,{value:cluster.avgStorage,size:44,strokeWidth:4,color:cluster.avgStorage>80?'#ef4444':cluster.avgStorage>60?'#eab308':'#8b5cf6'})),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},t('storage')||'Disk')),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"text-xl font-bold"},/*#__PURE__*/React.createElement("span",{className:"text-green-400"},cluster.runningVms),/*#__PURE__*/React.createElement("span",{className:"text-gray-500 text-sm"},"/",cluster.totalVms)),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},t('vms')))):/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center h-20 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-4 h-4 mr-2"}),t('noDataAvailable')||'No data available'),/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-3 border-t border-proxmox-border/50 flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},t('updated')||'Updated',": ",formatLastUpdate(cluster.lastUpdate)),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-4 h-4 text-gray-500 group-hover:text-proxmox-orange transition-colors"}))));};// LW: Feb 2026 - Corporate variant for GroupOverview if(isCorporate){const corpBarColor=val=>val>80?'#f54f47':val>60?'#efc006':'#60b515';const groupClusterIds=groupClusters.map(c=>c.id);const groupTopGuests=topGuests.filter(g=>groupClusterIds.includes(g.cluster_id));return/*#__PURE__*/React.createElement("div",{className:"space-y-3"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between pb-2",style:{borderBottom:'1px solid var(--corp-border-medium)'}},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Folder,{className:"w-4 h-4",style:{color:group.color||'#E86F2D'}}),/*#__PURE__*/React.createElement("h2",{className:"text-[15px] font-semibold",style:{color:'var(--color-text)'}},group.name),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-muted)'}},groupClusters.length," ",t('clusters'))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},totals.totalAlerts>0&&/*#__PURE__*/React.createElement("span",{className:"text-[12px] flex items-center gap-1",style:{color:'var(--color-error)'}},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3.5 h-3.5"})," ",totals.totalAlerts),/*#__PURE__*/React.createElement("button",{onClick:onOpenSettings,className:"p-1",style:{color:'var(--corp-text-muted)'},title:t('settings')},/*#__PURE__*/React.createElement(Icons.Settings,{className:"w-4 h-4"})))),lbEnabled&&/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 px-2 py-1.5 text-[12px]",style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)'}},/*#__PURE__*/React.createElement(Icons.Scale,{className:"w-3.5 h-3.5",style:{color:'var(--color-success)'}}),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},t('crossClusterLB')||'Cross-Cluster LB',": ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-success)'}},t('enabled')||'Enabled')),group.cross_cluster_dry_run&&/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-warning)'}},"(",t('simulationMode')||'Simulation',")"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-muted)'}},"Threshold: ",group.cross_cluster_threshold||30,"%")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-0 flex-wrap text-[13px] px-2 py-2",style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)'}},/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},t('clusters'),": ",/*#__PURE__*/React.createElement("b",{style:{color:'var(--color-text)'}},totals.connectedClusters,"/",totals.clusters)),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)',margin:'0 8px'}},"|"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},t('nodes'),": ",/*#__PURE__*/React.createElement("b",{style:{color:'var(--color-text)'}},totals.onlineNodes,"/",totals.totalNodes)),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)',margin:'0 8px'}},"|"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},"VMs: ",/*#__PURE__*/React.createElement("b",{style:{color:'var(--color-success)'}},totals.runningVms)," / ",/*#__PURE__*/React.createElement("b",{style:{color:'var(--corp-text-muted)'}},totals.totalVms-totals.runningVms)),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)',margin:'0 8px'}},"|"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},"CPU: ",/*#__PURE__*/React.createElement("b",{style:{color:corpBarColor(totals.avgCpu)}},totals.avgCpu.toFixed(0),"%")),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)',margin:'0 8px'}},"|"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},"RAM: ",/*#__PURE__*/React.createElement("b",{style:{color:corpBarColor(totals.avgMem)}},totals.avgMem.toFixed(0),"%")),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)',margin:'0 8px'}},"|"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},t('storage')||'Storage',": ",/*#__PURE__*/React.createElement("b",{style:{color:corpBarColor(totals.avgStorage)}},totals.avgStorage.toFixed(0),"%"))),/*#__PURE__*/React.createElement("table",{className:"corp-datagrid"},/*#__PURE__*/React.createElement("thead",null,/*#__PURE__*/React.createElement("tr",null,[{field:'name',label:t('name')||'Name'},{field:null,label:t('status')||'Status'},{field:'nodes',label:t('nodes')||'Nodes'},{field:'vms',label:'VMs'},{field:'cpu',label:'CPU'},{field:'ram',label:'RAM'},{field:null,label:t('storage')||'Storage'},{field:'health',label:t('health')||'Health'}].map((col,i)=>/*#__PURE__*/React.createElement("th",{key:i,className:col.field?'cursor-pointer hover:text-white':'',onClick:col.field?()=>{setSortBy(col.field);setSortDir(sortBy===col.field&&sortDir==='asc'?'desc':'asc');}:undefined,style:{textAlign:'left'}},col.label," ",sortBy===col.field&&/*#__PURE__*/React.createElement("span",{className:"sort-indicator"},sortDir==='asc'?'▲':'▼'))))),/*#__PURE__*/React.createElement("tbody",null,sortedStats.map(cluster=>{const hc=getHealthColor(cluster.healthScore);return/*#__PURE__*/React.createElement("tr",{key:cluster.id,className:"table-row-hover cursor-pointer",onClick:()=>onSelectCluster(cluster)},/*#__PURE__*/React.createElement("td",{style:{fontWeight:500}},cluster.display_name||cluster.name),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"w-1.5 h-1.5 rounded-full inline-block",style:{background:cluster.connected?'#60b515':'#f54f47'}}),/*#__PURE__*/React.createElement("span",{style:{color:cluster.connected?'#60b515':'#f54f47',fontSize:'12px'}},cluster.connected?'online':'offline'))),/*#__PURE__*/React.createElement("td",null,cluster.onlineNodes,"/",cluster.nodeCount),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{style:{color:'#60b515'}},cluster.runningVms)," / ",/*#__PURE__*/React.createElement("span",{style:{color:'#728b9a'}},cluster.totalVms-cluster.runningVms)),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{style:{color:corpBarColor(cluster.avgCpu),minWidth:'28px'}},cluster.avgCpu.toFixed(0),"%"),/*#__PURE__*/React.createElement("span",{className:"inline-block",style:{width:'40px',height:'3px',background:'var(--corp-divider)',position:'relative'}},/*#__PURE__*/React.createElement("span",{style:{position:'absolute',left:0,top:0,height:'3px',width:`${Math.min(cluster.avgCpu,100)}%`,background:corpBarColor(cluster.avgCpu)}})))),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{style:{color:corpBarColor(cluster.avgMem),minWidth:'28px'}},cluster.avgMem.toFixed(0),"%"),/*#__PURE__*/React.createElement("span",{className:"inline-block",style:{width:'40px',height:'3px',background:'var(--corp-divider)',position:'relative'}},/*#__PURE__*/React.createElement("span",{style:{position:'absolute',left:0,top:0,height:'3px',width:`${Math.min(cluster.avgMem,100)}%`,background:corpBarColor(cluster.avgMem)}})))),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{style:{color:corpBarColor(cluster.avgStorage),minWidth:'28px'}},cluster.avgStorage.toFixed(0),"%"),/*#__PURE__*/React.createElement("span",{className:"inline-block",style:{width:'40px',height:'3px',background:'var(--corp-divider)',position:'relative'}},/*#__PURE__*/React.createElement("span",{style:{position:'absolute',left:0,top:0,height:'3px',width:`${Math.min(cluster.avgStorage,100)}%`,background:corpBarColor(cluster.avgStorage)}})))),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{style:{color:hc.color,fontWeight:500}},cluster.healthScore,"%")));}))),groupTopGuests.length>0&&/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 py-1.5",style:{borderBottom:'1px solid #485764'}},/*#__PURE__*/React.createElement(Icons.Activity,{className:"w-3.5 h-3.5",style:{color:'#49afd9'}}),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-semibold",style:{color:'#adbbc4'}},t('topResources')||'Top Resources')),/*#__PURE__*/React.createElement("table",{className:"corp-datagrid"},/*#__PURE__*/React.createElement("thead",null,/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("th",{style:{width:'24px'}}),/*#__PURE__*/React.createElement("th",null,t('name')),/*#__PURE__*/React.createElement("th",null,t('cluster')),/*#__PURE__*/React.createElement("th",null,t('node')),/*#__PURE__*/React.createElement("th",null,"CPU"),/*#__PURE__*/React.createElement("th",null,"RAM"),/*#__PURE__*/React.createElement("th",null,t('status')))),/*#__PURE__*/React.createElement("tbody",null,groupTopGuests.slice(0,10).map(guest=>{const cpuP=((guest.cpu||0)*100).toFixed(1);const memP=guest.maxmem>0?(guest.mem/guest.maxmem*100).toFixed(1):0;const gc=clusters.find(c=>c.id===guest.cluster_id);return/*#__PURE__*/React.createElement("tr",{key:`${guest.cluster_id}-${guest.vmid}`,className:"table-row-hover cursor-pointer",onClick:()=>{if(gc&&onSelectVm)onSelectVm(gc,guest.vmid,guest.node,guest);}},/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement(Icons.Monitor,{className:"w-3.5 h-3.5",style:{color:guest.type==='qemu'?'#49afd9':'#a178d9'}})),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{style:{fontWeight:500}},guest.name||`VM ${guest.vmid}`)," ",/*#__PURE__*/React.createElement("span",{style:{color:'#728b9a',fontSize:'11px'}},"#",guest.vmid)),/*#__PURE__*/React.createElement("td",null,guest.cluster_name),/*#__PURE__*/React.createElement("td",{style:{color:'#adbbc4'}},guest.node),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{style:{color:corpBarColor(cpuP)}},cpuP,"%")),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{style:{color:corpBarColor(memP)}},memP,"%")),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"w-1.5 h-1.5 rounded-full inline-block",style:{background:guest.status==='running'?'#60b515':'#728b9a'}}),/*#__PURE__*/React.createElement("span",{style:{color:guest.status==='running'?'#60b515':'#728b9a',fontSize:'12px'}},guest.status==='running'?t('running'):t('stopped')))));})))),lbEnabled&&lbHistory.length>0&&/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 py-1.5",style:{borderBottom:'1px solid #485764'}},/*#__PURE__*/React.createElement(Icons.Activity,{className:"w-3.5 h-3.5",style:{color:'#a178d9'}}),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-semibold",style:{color:'#adbbc4'}},t('lbHistory')||'LB History')),/*#__PURE__*/React.createElement("table",{className:"corp-datagrid"},/*#__PURE__*/React.createElement("thead",null,/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("th",null,t('time')),/*#__PURE__*/React.createElement("th",null,t('action')),/*#__PURE__*/React.createElement("th",null,t('details')))),/*#__PURE__*/React.createElement("tbody",null,lbHistory.slice(0,10).map((entry,i)=>/*#__PURE__*/React.createElement("tr",{key:i,className:"table-row-hover"},/*#__PURE__*/React.createElement("td",{style:{color:'#728b9a'}},new Date(entry.timestamp).toLocaleString()),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{style:{color:entry.action==='migrate'?'#49afd9':'#a178d9'}},entry.action)),/*#__PURE__*/React.createElement("td",{style:{color:'#adbbc4'}},entry.details||'-')))))),groupClusters.length===0&&/*#__PURE__*/React.createElement("div",{className:"py-8 text-center text-[13px]",style:{color:'#728b9a'}},t('noClustersInGroup')||'No clusters in this group'));}// Modern layout (original) return/*#__PURE__*/React.createElement("div",{className:"space-y-6"},/*#__PURE__*/React.createElement("div",{className:"relative overflow-hidden bg-gradient-to-r from-proxmox-card via-proxmox-dark to-proxmox-card border border-proxmox-border rounded-2xl p-6"},/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 bg-gradient-to-r from-proxmox-orange/5 via-transparent to-blue-500/5"}),/*#__PURE__*/React.createElement("div",{className:"relative flex items-center justify-between flex-wrap gap-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4"},/*#__PURE__*/React.createElement("div",{className:"w-14 h-14 rounded-xl flex items-center justify-center shadow-lg",style:{backgroundColor:(group.color||'#E86F2D')+'33'}},/*#__PURE__*/React.createElement(Icons.Folder,{className:"w-7 h-7",style:{color:group.color||'#E86F2D'}})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h1",{className:"text-2xl font-bold text-white"},group.name),group.description&&/*#__PURE__*/React.createElement("p",{className:"text-gray-400 text-sm"},group.description),/*#__PURE__*/React.createElement("p",{className:"text-gray-500 text-xs mt-0.5"},groupClusters.length," ",t('clusters')))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},totals.totalAlerts>0&&/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 px-3 py-1.5 bg-red-500/10 border border-red-500/30 rounded-lg"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-4 h-4 text-red-400"}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-red-400 font-medium"},totals.totalAlerts," ",t('alerts')||'Alerts')),/*#__PURE__*/React.createElement("button",{onClick:onOpenSettings,className:"p-2 rounded-lg text-gray-400 hover:text-white hover:bg-proxmox-dark transition-colors",title:t('settings')||'Settings'},/*#__PURE__*/React.createElement(Icons.Settings,{className:"w-5 h-5"})),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 px-3 py-1.5 bg-green-500/10 border border-green-500/30 rounded-full"},/*#__PURE__*/React.createElement("div",{className:"w-2 h-2 rounded-full bg-green-500 animate-pulse"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-green-400 font-medium"},"Live"))))),lbEnabled&&/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl p-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"w-10 h-10 rounded-lg bg-green-500/20 flex items-center justify-center"},/*#__PURE__*/React.createElement(Icons.Scale,{className:"w-5 h-5 text-green-400"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold flex items-center gap-2"},t('crossClusterLB')||'Cross-Cluster Load Balancing',/*#__PURE__*/React.createElement("span",{className:"bg-green-500/10 text-green-400 px-2 py-0.5 rounded-full text-xs"},t('enabled')||'Enabled'),group.cross_cluster_dry_run&&/*#__PURE__*/React.createElement("span",{className:"bg-yellow-500/10 text-yellow-400 px-2 py-0.5 rounded-full text-xs"},t('simulationMode')||'Simulation Mode')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4 text-xs text-gray-500 mt-1"},/*#__PURE__*/React.createElement("span",null,t('threshold')||'Threshold',": ",group.cross_cluster_threshold||30,"%"),/*#__PURE__*/React.createElement("span",null,t('interval')||'Interval',": ",group.cross_cluster_interval||600,"s"),groupStatus?.cross_cluster_lb?.last_run&&/*#__PURE__*/React.createElement("span",null,t('lastRun')||'Last run',": ",formatLastUpdate(groupStatus.cross_cluster_lb.last_run))))))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-4"},[{icon:Icons.Server,value:`${totals.connectedClusters}/${totals.clusters}`,label:t('clusters'),color:'proxmox-orange',hoverColor:'proxmox-orange'},{icon:Icons.Cpu,value:`${totals.onlineNodes}/${totals.totalNodes}`,label:t('nodesOnline')||'Nodes',color:'blue-400',hoverColor:'blue-500'},{icon:Icons.Play,value:totals.runningVms,label:t('vmsRunning')||'Running',color:'green-400',hoverColor:'green-500',valueColor:'text-green-400'},{icon:Icons.Square,value:totals.totalVms-totals.runningVms,label:t('vmsStopped')||'Stopped',color:'gray-400',hoverColor:'gray-500'}].map((stat,i)=>/*#__PURE__*/React.createElement("div",{key:i,className:`bg-gradient-to-br from-proxmox-card to-proxmox-dark border border-proxmox-border rounded-xl p-4 hover:border-${stat.hoverColor}/30 transition-all group`},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:`w-10 h-10 rounded-lg bg-${stat.color}/20 flex items-center justify-center group-hover:scale-110 transition-transform`},/*#__PURE__*/React.createElement(stat.icon,{className:`w-5 h-5 text-${stat.color}`})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:`text-xl font-bold ${stat.valueColor||'text-white'}`},stat.value),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},stat.label))))),[{value:totals.avgCpu,label:'CPU',color:totals.avgCpu>80?'#ef4444':totals.avgCpu>60?'#eab308':'#22c55e'},{value:totals.avgMem,label:'RAM',color:totals.avgMem>80?'#ef4444':totals.avgMem>60?'#eab308':'#3b82f6'},{value:totals.avgStorage,label:t('storage')||'Disk',color:totals.avgStorage>80?'#ef4444':totals.avgStorage>60?'#eab308':'#8b5cf6'}].map((stat,i)=>/*#__PURE__*/React.createElement("div",{key:i,className:"bg-gradient-to-br from-proxmox-card to-proxmox-dark border border-proxmox-border rounded-xl p-4 hover:border-proxmox-border transition-all"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement(CircularProgress,{value:stat.value||0,size:44,strokeWidth:4,color:stat.color}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-sm font-bold text-white"},stat.label),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('average')||'Avg')))))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-wrap"},/*#__PURE__*/React.createElement("span",{className:"text-sm text-gray-500 mr-1"},t('sortBy')||'Sort by',":"),/*#__PURE__*/React.createElement(SortButton,{field:"name",label:t('name')||'Name'}),/*#__PURE__*/React.createElement(SortButton,{field:"health",label:t('health')||'Health'}),/*#__PURE__*/React.createElement(SortButton,{field:"nodes",label:t('nodes')||'Nodes'}),/*#__PURE__*/React.createElement(SortButton,{field:"vms",label:"VMs"}),/*#__PURE__*/React.createElement(SortButton,{field:"cpu",label:"CPU"}),/*#__PURE__*/React.createElement(SortButton,{field:"ram",label:"RAM"}))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-4"},sortedStats.map(cluster=>/*#__PURE__*/React.createElement(ClusterCard,{key:cluster.id,cluster:cluster}))),(()=>{const groupClusterIds2=groupClusters.map(c=>c.id);const groupTopGuests2=topGuests.filter(g=>groupClusterIds2.includes(g.cluster_id));if(groupTopGuests2.length===0)return null;return/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"p-4 border-b border-proxmox-border flex items-center justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"w-10 h-10 rounded-lg bg-cyan-500/20 flex items-center justify-center"},/*#__PURE__*/React.createElement(Icons.Activity,{className:"w-5 h-5 text-cyan-400"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white"},t('topResources')||'Top Resources'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},t('highestCpuUsage')||'Highest CPU and RAM usage across all clusters'))),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500 bg-proxmox-dark px-2 py-1 rounded"},"Top 10")),/*#__PURE__*/React.createElement("div",{className:"overflow-x-auto"},/*#__PURE__*/React.createElement("table",{className:"w-full"},/*#__PURE__*/React.createElement("thead",{className:"bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("tr",{className:"text-left text-xs text-gray-400"},/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 font-medium"},t('type')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 font-medium"},t('name')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 font-medium"},t('cluster')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 font-medium"},t('node')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 font-medium"},"CPU"),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 font-medium"},"RAM"),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 font-medium"},t('status')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 w-10"}))),/*#__PURE__*/React.createElement("tbody",{className:"divide-y divide-proxmox-border/50"},groupTopGuests2.slice(0,10).map((guest,idx)=>{const cpuPercent=((guest.cpu||0)*100).toFixed(1);const memPercent=guest.maxmem>0?(guest.mem/guest.maxmem*100).toFixed(1):0;const isVM=guest.type==='qemu';const guestCluster=clusters.find(c=>c.id===guest.cluster_id);return/*#__PURE__*/React.createElement("tr",{key:`${guest.cluster_id}-${guest.vmid}`,className:"hover:bg-proxmox-hover/50 transition-colors cursor-pointer group",onClick:()=>{if(guestCluster&&onSelectVm)onSelectVm(guestCluster,guest.vmid,guest.node,guest);},title:t('clickToOpenVm')||'Click to open VM'},/*#__PURE__*/React.createElement("td",{className:"px-4 py-3"},/*#__PURE__*/React.createElement("div",{className:`w-8 h-8 rounded-lg flex items-center justify-center ${isVM?'bg-blue-500/20':'bg-purple-500/20'}`},isVM?/*#__PURE__*/React.createElement(Icons.Monitor,{className:"w-4 h-4 text-blue-400"}):/*#__PURE__*/React.createElement(Icons.Layers,{className:"w-4 h-4 text-purple-400"}))),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3"},/*#__PURE__*/React.createElement("div",{className:"font-medium text-white group-hover:text-proxmox-orange transition-colors"},guest.name||`VM ${guest.vmid}`),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},"ID: ",guest.vmid)),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3"},/*#__PURE__*/React.createElement("span",{className:"text-sm text-gray-300"},guest.cluster_name)),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3"},/*#__PURE__*/React.createElement("span",{className:"text-sm text-gray-400"},guest.node)),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-16 h-2 bg-proxmox-dark rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:`h-full rounded-full ${cpuPercent>80?'bg-red-500':cpuPercent>60?'bg-yellow-500':'bg-green-500'}`,style:{width:`${Math.min(cpuPercent,100)}%`}})),/*#__PURE__*/React.createElement("span",{className:`text-xs font-medium ${cpuPercent>80?'text-red-400':cpuPercent>60?'text-yellow-400':'text-green-400'}`},cpuPercent,"%"))),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-16 h-2 bg-proxmox-dark rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:`h-full rounded-full ${memPercent>80?'bg-red-500':memPercent>60?'bg-yellow-500':'bg-blue-500'}`,style:{width:`${Math.min(memPercent,100)}%`}})),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-400"},memPercent,"%"))),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3"},/*#__PURE__*/React.createElement("span",{className:`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium ${guest.status==='running'?'bg-green-500/20 text-green-400':'bg-gray-500/20 text-gray-400'}`},/*#__PURE__*/React.createElement("div",{className:`w-1.5 h-1.5 rounded-full ${guest.status==='running'?'bg-green-400':'bg-gray-400'}`}),guest.status==='running'?t('running'):t('stopped'))),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3"},/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-4 h-4 text-gray-500 group-hover:text-proxmox-orange transition-colors"})));})))));})(),lbEnabled&&/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"p-4 border-b border-proxmox-border flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"w-10 h-10 rounded-lg bg-purple-500/20 flex items-center justify-center"},/*#__PURE__*/React.createElement(Icons.Activity,{className:"w-5 h-5 text-purple-400"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white"},t('lbHistory')||'Load Balancing History'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},t('recentLbActions')||'Recent cross-cluster LB actions'))),lbHistory.length>0?/*#__PURE__*/React.createElement("div",{className:"overflow-x-auto"},/*#__PURE__*/React.createElement("table",{className:"w-full"},/*#__PURE__*/React.createElement("thead",{className:"bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("tr",{className:"text-left text-xs text-gray-400"},/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 font-medium"},t('time')||'Time'),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 font-medium"},t('action')||'Action'),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 font-medium"},t('details')||'Details'))),/*#__PURE__*/React.createElement("tbody",{className:"divide-y divide-proxmox-border/50"},lbHistory.slice(0,20).map((entry,idx)=>/*#__PURE__*/React.createElement("tr",{key:idx,className:"hover:bg-proxmox-hover/50 transition-colors"},/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-sm text-gray-400"},new Date(entry.timestamp).toLocaleString()),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3"},/*#__PURE__*/React.createElement("span",{className:`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium ${entry.action==='migrate'?'bg-blue-500/20 text-blue-400':entry.action==='rebalance'?'bg-purple-500/20 text-purple-400':'bg-gray-500/20 text-gray-400'}`},entry.action)),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-sm text-gray-300"},entry.details||'-')))))):/*#__PURE__*/React.createElement("div",{className:"p-8 text-center text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.Clock,{className:"w-8 h-8 mx-auto mb-2 opacity-50"}),t('noLbEvents')||'No cross-cluster LB events yet')),groupClusters.length===0&&/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl p-12 text-center"},/*#__PURE__*/React.createElement("div",{className:"w-16 h-16 mx-auto mb-4 rounded-2xl bg-gradient-to-br from-proxmox-orange/20 to-orange-600/20 flex items-center justify-center"},/*#__PURE__*/React.createElement(Icons.Server,{className:"w-8 h-8 text-proxmox-orange"})),/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold text-white mb-2"},t('noClustersInGroup')||'No clusters in this group'),/*#__PURE__*/React.createElement("p",{className:"text-gray-500 text-sm"},t('addClustersToGroup')||'Assign clusters to this group to see them here')));}// LW: All Clusters Overview - GitHub Feature Request #16 // added a bunch of stuff here - storage, sparklines, sorting etc @@ -3254,7 +3254,7 @@ const totals={clusters:clusters.length,connectedClusters:clusters.filter(c=>c.connected).length,totalNodes:clusterStats.reduce((acc,c)=>acc+c.nodeCount,0),onlineNodes:clusterStats.reduce((acc,c)=>acc+c.onlineNodes,0),totalVms:clusterStats.reduce((acc,c)=>acc+c.totalVms,0),runningVms:clusterStats.reduce((acc,c)=>acc+c.runningVms,0),avgCpu:clusterStats.filter(c=>c.hasMetrics).length>0?clusterStats.reduce((acc,c)=>acc+c.avgCpu,0)/clusterStats.filter(c=>c.hasMetrics).length:0,avgMem:clusterStats.filter(c=>c.hasMetrics).length>0?clusterStats.reduce((acc,c)=>acc+c.avgMem,0)/clusterStats.filter(c=>c.hasMetrics).length:0,avgStorage:clusterStats.filter(c=>c.hasMetrics).length>0?clusterStats.reduce((acc,c)=>acc+c.avgStorage,0)/clusterStats.filter(c=>c.hasMetrics).length:0,totalAlerts:clusterStats.reduce((acc,c)=>acc+c.alerts.length,0)};// NS: sparkline svg - kept it simple const Sparkline=({data,color,height=20,width=60})=>{if(!data||data.length<2)return/*#__PURE__*/React.createElement("div",{style:{width,height},className:"bg-proxmox-dark/50 rounded"});const max=Math.max(...data,1);const min=Math.min(...data,0);const range=max-min||1;const points=data.map((v,i)=>`${i/(data.length-1)*width},${height-(v-min)/range*height}`).join(' ');return/*#__PURE__*/React.createElement("svg",{width:width,height:height,className:"overflow-visible"},/*#__PURE__*/React.createElement("polyline",{fill:"none",stroke:color,strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",points:points}),/*#__PURE__*/React.createElement("circle",{cx:width,cy:height-(data[data.length-1]-min)/range*height,r:"2",fill:color}));};// circular progress - reusable const CircularProgress=({value,size=60,strokeWidth=6,color})=>{const radius=(size-strokeWidth)/2;const circumference=radius*2*Math.PI;const offset=circumference-value/100*circumference;return/*#__PURE__*/React.createElement("div",{className:"relative flex-shrink-0",style:{width:size,height:size}},/*#__PURE__*/React.createElement("svg",{className:"transform -rotate-90 overflow-visible",width:size,height:size},/*#__PURE__*/React.createElement("circle",{cx:size/2,cy:size/2,r:radius,fill:"none",stroke:"currentColor",strokeWidth:strokeWidth,className:"text-gray-700"}),/*#__PURE__*/React.createElement("circle",{cx:size/2,cy:size/2,r:radius,fill:"none",stroke:color,strokeWidth:strokeWidth,strokeLinecap:"round",strokeDasharray:circumference,strokeDashoffset:offset,className:"transition-all duration-500"})),/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 flex items-center justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-white"},value.toFixed(0),"%")));};const getHealthColor=score=>{if(score>=80)return{bg:'bg-green-500/20',text:'text-green-400',border:'border-green-500/30',color:'#22c55e'};if(score>=60)return{bg:'bg-yellow-500/20',text:'text-yellow-400',border:'border-yellow-500/30',color:'#eab308'};if(score>=40)return{bg:'bg-orange-500/20',text:'text-orange-400',border:'border-orange-500/30',color:'#f97316'};return{bg:'bg-red-500/20',text:'text-red-400',border:'border-red-500/30',color:'#ef4444'};};const formatLastUpdate=date=>{if(!date)return'-';const now=new Date();const diff=Math.floor((now-new Date(date))/1000);if(diff<10)return t('justNow')||'just now';if(diff<60)return`${diff}s ago`;if(diff<3600)return`${Math.floor(diff/60)}m ago`;return new Date(date).toLocaleTimeString();};const SortButton=({field,label})=>/*#__PURE__*/React.createElement("button",{onClick:()=>{setSortBy(field);setSortDir(sortBy===field&&sortDir==='asc'?'desc':'asc');},className:`px-3 py-1.5 text-sm rounded-lg transition-colors ${sortBy===field?'bg-proxmox-orange/20 text-proxmox-orange font-medium':'text-gray-400 hover:text-white hover:bg-proxmox-dark'}`},label," ",sortBy===field&&(sortDir==='asc'?'↑':'↓'));// cluster card component -const ClusterCard=({cluster})=>{const healthColors=getHealthColor(cluster.healthScore);const hasAlerts=cluster.alerts.length>0;const errorCount=cluster.alerts.filter(a=>a.type==='error').length;return/*#__PURE__*/React.createElement("div",{onClick:()=>onSelectCluster(cluster),className:"group relative bg-proxmox-card border border-proxmox-border rounded-xl overflow-hidden cursor-pointer hover:border-proxmox-orange/50 transition-all duration-300 hover:shadow-lg hover:shadow-proxmox-orange/10"},/*#__PURE__*/React.createElement("div",{className:`h-1.5 ${cluster.connected?'bg-gradient-to-r from-green-500 via-emerald-500 to-green-500':'bg-gradient-to-r from-red-500 via-red-600 to-red-500'}`}),/*#__PURE__*/React.createElement("div",{className:"p-5"},/*#__PURE__*/React.createElement("div",{className:"flex items-start justify-between mb-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:`relative w-12 h-12 rounded-xl ${cluster.connected?'bg-gradient-to-br from-green-500/20 to-emerald-500/20':'bg-gradient-to-br from-red-500/20 to-red-600/20'} flex items-center justify-center`},/*#__PURE__*/React.createElement(Icons.Server,{className:`w-6 h-6 ${cluster.connected?'text-green-400':'text-red-400'}`}),cluster.connected&&/*#__PURE__*/React.createElement("div",{className:"absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-proxmox-card animate-pulse"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white text-base group-hover:text-proxmox-orange transition-colors"},cluster.display_name||cluster.name),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},cluster.host))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},hasAlerts&&/*#__PURE__*/React.createElement("div",{className:"relative group/alerts"},/*#__PURE__*/React.createElement("div",{className:`px-2.5 py-1 rounded-lg flex items-center gap-1.5 ${errorCount>0?'bg-red-500/20 border border-red-500/30':'bg-yellow-500/20 border border-yellow-500/30'}`},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:`w-3.5 h-3.5 ${errorCount>0?'text-red-400':'text-yellow-400'}`}),/*#__PURE__*/React.createElement("span",{className:`text-xs font-medium ${errorCount>0?'text-red-400':'text-yellow-400'}`},cluster.alerts.length)),/*#__PURE__*/React.createElement("div",{className:"absolute right-0 top-full mt-1 z-50 hidden group-hover/alerts:block"},/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark border border-proxmox-border rounded-lg p-2 shadow-xl min-w-[160px]"},cluster.alerts.map((alert,i)=>/*#__PURE__*/React.createElement("div",{key:i,className:`text-xs py-0.5 ${alert.type==='error'?'text-red-400':'text-yellow-400'}`},"\u2022 ",alert.msg))))),cluster.connected&&/*#__PURE__*/React.createElement("div",{className:`px-2.5 py-1 rounded-lg ${healthColors.bg} ${healthColors.border} border`},/*#__PURE__*/React.createElement("span",{className:`text-xs font-semibold ${healthColors.text}`},cluster.healthScore,"%")),!cluster.connected&&/*#__PURE__*/React.createElement("div",{className:"px-2.5 py-1 rounded-lg bg-red-500/20 border border-red-500/30"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-semibold text-red-400"},"Offline")))),cluster.hasMetrics?/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-5 gap-3"},/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:`text-xl font-bold ${cluster.onlineNodes===cluster.nodeCount?'text-green-400':'text-yellow-400'}`},cluster.onlineNodes,"/",cluster.nodeCount),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},t('nodes'))),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center gap-1"},/*#__PURE__*/React.createElement(CircularProgress,{value:cluster.avgCpu,size:44,strokeWidth:4,color:cluster.avgCpu>80?'#ef4444':cluster.avgCpu>60?'#eab308':'#22c55e'}),/*#__PURE__*/React.createElement(Sparkline,{data:cluster.cpuHistory,color:cluster.avgCpu>80?'#ef4444':'#22c55e',height:14,width:40})),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},"CPU")),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center gap-1"},/*#__PURE__*/React.createElement(CircularProgress,{value:cluster.avgMem,size:44,strokeWidth:4,color:cluster.avgMem>80?'#ef4444':cluster.avgMem>60?'#eab308':'#3b82f6'}),/*#__PURE__*/React.createElement(Sparkline,{data:cluster.ramHistory,color:cluster.avgMem>80?'#ef4444':'#3b82f6',height:14,width:40})),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},"RAM")),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"flex justify-center"},/*#__PURE__*/React.createElement(CircularProgress,{value:cluster.avgStorage,size:44,strokeWidth:4,color:cluster.avgStorage>80?'#ef4444':cluster.avgStorage>60?'#eab308':'#8b5cf6'})),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},t('storage')||'Disk')),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"text-xl font-bold"},/*#__PURE__*/React.createElement("span",{className:"text-green-400"},cluster.runningVms),/*#__PURE__*/React.createElement("span",{className:"text-gray-500 text-sm"},"/",cluster.totalVms)),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},t('vms')))):/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center h-20 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-4 h-4 mr-2"}),t('noDataAvailable')||'No data available'),/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-3 border-t border-proxmox-border/50 flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},t('updated')||'Updated',": ",formatLastUpdate(cluster.lastUpdate)),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-4 h-4 text-gray-500 group-hover:text-proxmox-orange transition-colors"}))));};// LW: Feb 2026 - Corporate variant for AllClustersOverview +const ClusterCard=({cluster})=>{const healthColors=getHealthColor(cluster.healthScore);const hasAlerts=cluster.alerts.length>0;const errorCount=cluster.alerts.filter(a=>a.type==='error').length;return/*#__PURE__*/React.createElement("div",{onClick:()=>onSelectCluster(cluster),className:"group relative bg-proxmox-card border border-proxmox-border rounded-xl overflow-hidden cursor-pointer hover:border-proxmox-orange/50 transition-all duration-300 hover:shadow-lg hover:shadow-proxmox-orange/10"},/*#__PURE__*/React.createElement("div",{className:`h-1.5 ${cluster.connected?'bg-gradient-to-r from-green-500 via-emerald-500 to-green-500':'bg-gradient-to-r from-red-500 via-red-600 to-red-500'}`}),/*#__PURE__*/React.createElement("div",{className:"p-5"},/*#__PURE__*/React.createElement("div",{className:"flex items-start justify-between mb-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:`relative w-12 h-12 rounded-xl ${cluster.connected?'bg-gradient-to-br from-green-500/20 to-emerald-500/20':'bg-gradient-to-br from-red-500/20 to-red-600/20'} flex items-center justify-center`},/*#__PURE__*/React.createElement(Icons.Server,{className:`w-6 h-6 ${cluster.connected?'text-green-400':'text-red-400'}`}),cluster.connected&&/*#__PURE__*/React.createElement("div",{className:"absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-proxmox-card animate-pulse"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white text-base group-hover:text-proxmox-orange transition-colors"},cluster.display_name||cluster.name),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},cluster.host))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},hasAlerts&&/*#__PURE__*/React.createElement("div",{className:"relative group/alerts"},/*#__PURE__*/React.createElement("div",{className:`px-2.5 py-1 rounded-lg flex items-center gap-1.5 ${errorCount>0?'bg-red-500/20 border border-red-500/30':'bg-yellow-500/20 border border-yellow-500/30'}`},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:`w-3.5 h-3.5 ${errorCount>0?'text-red-400':'text-yellow-400'}`}),/*#__PURE__*/React.createElement("span",{className:`text-xs font-medium ${errorCount>0?'text-red-400':'text-yellow-400'}`},cluster.alerts.length)),/*#__PURE__*/React.createElement("div",{className:"absolute right-0 top-full mt-1 z-50 hidden group-hover/alerts:block"},/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark border border-proxmox-border rounded-lg p-2 shadow-xl min-w-[160px]"},cluster.alerts.map((alert,i)=>/*#__PURE__*/React.createElement("div",{key:i,className:`text-xs py-0.5 ${alert.type==='error'?'text-red-400':'text-yellow-400'}`},"\u2022 ",alert.msg))))),cluster.connected&&/*#__PURE__*/React.createElement("div",{className:`px-2.5 py-1 rounded-lg ${healthColors.bg} ${healthColors.border} border`,title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("span",{className:`text-xs font-semibold ${healthColors.text}`},cluster.healthScore,"%")),!cluster.connected&&/*#__PURE__*/React.createElement("div",{className:"px-2.5 py-1 rounded-lg bg-red-500/20 border border-red-500/30"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-semibold text-red-400"},"Offline")))),cluster.hasMetrics?/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-5 gap-3"},/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:`text-xl font-bold ${cluster.onlineNodes===cluster.nodeCount?'text-green-400':'text-yellow-400'}`},cluster.onlineNodes,"/",cluster.nodeCount),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},t('nodes'))),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center gap-1"},/*#__PURE__*/React.createElement(CircularProgress,{value:cluster.avgCpu,size:44,strokeWidth:4,color:cluster.avgCpu>80?'#ef4444':cluster.avgCpu>60?'#eab308':'#22c55e'}),/*#__PURE__*/React.createElement(Sparkline,{data:cluster.cpuHistory,color:cluster.avgCpu>80?'#ef4444':'#22c55e',height:14,width:40})),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},"CPU")),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center gap-1"},/*#__PURE__*/React.createElement(CircularProgress,{value:cluster.avgMem,size:44,strokeWidth:4,color:cluster.avgMem>80?'#ef4444':cluster.avgMem>60?'#eab308':'#3b82f6'}),/*#__PURE__*/React.createElement(Sparkline,{data:cluster.ramHistory,color:cluster.avgMem>80?'#ef4444':'#3b82f6',height:14,width:40})),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},"RAM")),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"flex justify-center"},/*#__PURE__*/React.createElement(CircularProgress,{value:cluster.avgStorage,size:44,strokeWidth:4,color:cluster.avgStorage>80?'#ef4444':cluster.avgStorage>60?'#eab308':'#8b5cf6'})),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},t('storage')||'Disk')),/*#__PURE__*/React.createElement("div",{className:"text-center p-3 rounded-xl bg-proxmox-dark/50"},/*#__PURE__*/React.createElement("div",{className:"text-xl font-bold"},/*#__PURE__*/React.createElement("span",{className:"text-green-400"},cluster.runningVms),/*#__PURE__*/React.createElement("span",{className:"text-gray-500 text-sm"},"/",cluster.totalVms)),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-gray-500 uppercase mt-1"},t('vms')))):/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center h-20 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-4 h-4 mr-2"}),t('noDataAvailable')||'No data available'),/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-3 border-t border-proxmox-border/50 flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},t('updated')||'Updated',": ",formatLastUpdate(cluster.lastUpdate)),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-4 h-4 text-gray-500 group-hover:text-proxmox-orange transition-colors"}))));};// LW: Feb 2026 - Corporate variant for AllClustersOverview if(isCorporate){const corpBarColor=val=>val>80?'#f54f47':val>60?'#efc006':'#60b515';return/*#__PURE__*/React.createElement("div",{className:"space-y-3"},/*#__PURE__*/React.createElement("div",{className:"corp-content-header"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},topologyOnly?/*#__PURE__*/React.createElement(Icons.Network,{className:"w-4 h-4",style:{color:'var(--corp-accent)'}}):/*#__PURE__*/React.createElement(Icons.Grid,{className:"w-4 h-4",style:{color:'var(--corp-accent)'}}),/*#__PURE__*/React.createElement("span",{className:"corp-header-title"},topologyOnly?t('topologyView')||'Topology':t('inventoryOverview')||t('allClustersOverview')||'Inventory Overview')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},totals.totalAlerts>0&&/*#__PURE__*/React.createElement("span",{className:"text-[12px] flex items-center gap-1",style:{color:'var(--color-error)'}},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3.5 h-3.5"})," ",totals.totalAlerts," ",t('alerts')||'alerts'))),!topologyOnly&&/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-0 flex-wrap text-[13px] px-2 py-2",style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)'}},/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},t('clusters'),": ",/*#__PURE__*/React.createElement("b",{style:{color:'var(--color-text)'}},totals.connectedClusters,"/",totals.clusters)," online"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)',margin:'0 8px'}},"|"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},t('nodes'),": ",/*#__PURE__*/React.createElement("b",{style:{color:'var(--color-text)'}},totals.onlineNodes,"/",totals.totalNodes)),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)',margin:'0 8px'}},"|"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},"VMs: ",/*#__PURE__*/React.createElement("b",{style:{color:'var(--color-success)'}},totals.runningVms)," ",t('running')?.toLowerCase(),", ",/*#__PURE__*/React.createElement("b",{style:{color:'var(--corp-text-muted)'}},totals.totalVms-totals.runningVms)," ",t('stopped')?.toLowerCase()),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)',margin:'0 8px'}},"|"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},"CPU: ",/*#__PURE__*/React.createElement("b",{style:{color:corpBarColor(totals.avgCpu)}},totals.avgCpu.toFixed(0),"%")),/*#__PURE__*/React.createElement("span",{className:"inline-block mx-1",style:{width:'40px',height:'3px',background:'var(--corp-divider)',position:'relative',verticalAlign:'middle'}},/*#__PURE__*/React.createElement("span",{style:{position:'absolute',left:0,top:0,height:'3px',width:`${Math.min(totals.avgCpu,100)}%`,background:corpBarColor(totals.avgCpu)}})),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)',margin:'0 8px'}},"|"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},"RAM: ",/*#__PURE__*/React.createElement("b",{style:{color:corpBarColor(totals.avgMem)}},totals.avgMem.toFixed(0),"%")),/*#__PURE__*/React.createElement("span",{className:"inline-block mx-1",style:{width:'40px',height:'3px',background:'var(--corp-divider)',position:'relative',verticalAlign:'middle'}},/*#__PURE__*/React.createElement("span",{style:{position:'absolute',left:0,top:0,height:'3px',width:`${Math.min(totals.avgMem,100)}%`,background:corpBarColor(totals.avgMem)}})),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)',margin:'0 8px'}},"|"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-secondary)'}},t('storage')||'Storage',": ",/*#__PURE__*/React.createElement("b",{style:{color:corpBarColor(totals.avgStorage)}},totals.avgStorage.toFixed(0),"%")),/*#__PURE__*/React.createElement("span",{className:"inline-block mx-1",style:{width:'40px',height:'3px',background:'var(--corp-divider)',position:'relative',verticalAlign:'middle'}},/*#__PURE__*/React.createElement("span",{style:{position:'absolute',left:0,top:0,height:'3px',width:`${Math.min(totals.avgStorage,100)}%`,background:corpBarColor(totals.avgStorage)}}))),!topologyOnly&&/*#__PURE__*/React.createElement("table",{className:"corp-datagrid corp-datagrid-striped"},/*#__PURE__*/React.createElement("thead",null,/*#__PURE__*/React.createElement("tr",null,[{field:'name',label:t('name')||'Name'},{field:null,label:t('status')||'Status'},{field:'nodes',label:t('nodes')||'Nodes'},{field:'vms',label:'VMs'},{field:'cpu',label:'CPU'},{field:'ram',label:'RAM'},{field:null,label:t('storage')||'Storage'},{field:'health',label:t('health')||'Health'}].map((col,i)=>/*#__PURE__*/React.createElement("th",{key:i,className:col.field?'cursor-pointer hover:text-white':'',onClick:col.field?()=>{setSortBy(col.field);setSortDir(sortBy===col.field&&sortDir==='asc'?'desc':'asc');}:undefined,style:{textAlign:'left'}},col.label," ",sortBy===col.field&&/*#__PURE__*/React.createElement("span",{className:"sort-indicator"},sortDir==='asc'?'▲':'▼'))))),/*#__PURE__*/React.createElement("tbody",null,sortedStats.map(cluster=>{const hc=getHealthColor(cluster.healthScore);return/*#__PURE__*/React.createElement("tr",{key:cluster.id,className:"table-row-hover cursor-pointer",onClick:()=>onSelectCluster(cluster)},/*#__PURE__*/React.createElement("td",{style:{fontWeight:500}},cluster.display_name||cluster.name),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"w-1.5 h-1.5 rounded-full inline-block",style:{background:cluster.connected?'var(--color-success)':'var(--color-error)'}}),/*#__PURE__*/React.createElement("span",{style:{color:cluster.connected?'var(--color-success)':'var(--color-error)',fontSize:'12px'}},cluster.connected?'online':'offline'))),/*#__PURE__*/React.createElement("td",null,cluster.onlineNodes,"/",cluster.nodeCount),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-success)'}},cluster.runningVms),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-muted)'}}," / ",cluster.totalVms-cluster.runningVms)),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{style:{color:corpBarColor(cluster.avgCpu),minWidth:'28px'}},cluster.avgCpu.toFixed(0),"%"),/*#__PURE__*/React.createElement("span",{className:"inline-block",style:{width:'40px',height:'3px',background:'var(--corp-divider)',position:'relative'}},/*#__PURE__*/React.createElement("span",{style:{position:'absolute',left:0,top:0,height:'3px',width:`${Math.min(cluster.avgCpu,100)}%`,background:corpBarColor(cluster.avgCpu)}})))),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{style:{color:corpBarColor(cluster.avgMem),minWidth:'28px'}},cluster.avgMem.toFixed(0),"%"),/*#__PURE__*/React.createElement("span",{className:"inline-block",style:{width:'40px',height:'3px',background:'var(--corp-divider)',position:'relative'}},/*#__PURE__*/React.createElement("span",{style:{position:'absolute',left:0,top:0,height:'3px',width:`${Math.min(cluster.avgMem,100)}%`,background:corpBarColor(cluster.avgMem)}})))),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{style:{color:corpBarColor(cluster.avgStorage),minWidth:'28px'}},cluster.avgStorage.toFixed(0),"%"),/*#__PURE__*/React.createElement("span",{className:"inline-block",style:{width:'40px',height:'3px',background:'var(--corp-divider)',position:'relative'}},/*#__PURE__*/React.createElement("span",{style:{position:'absolute',left:0,top:0,height:'3px',width:`${Math.min(cluster.avgStorage,100)}%`,background:corpBarColor(cluster.avgStorage)}})))),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{style:{color:hc.color,fontWeight:500}},cluster.healthScore,"%")));}))),!topologyOnly&&topGuests.length>0&&/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 py-1.5",style:{borderBottom:'1px solid #485764'}},/*#__PURE__*/React.createElement(Icons.Activity,{className:"w-3.5 h-3.5",style:{color:'#49afd9'}}),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-semibold",style:{color:'#adbbc4'}},t('topResources')||'Top Resources'),/*#__PURE__*/React.createElement("span",{className:"text-[11px]",style:{color:'#728b9a'}},"Top 10")),/*#__PURE__*/React.createElement("table",{className:"corp-datagrid corp-datagrid-striped"},/*#__PURE__*/React.createElement("thead",null,/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("th",{style:{width:'24px'}}),/*#__PURE__*/React.createElement("th",null,t('name')),/*#__PURE__*/React.createElement("th",null,t('cluster')),/*#__PURE__*/React.createElement("th",null,t('node')),/*#__PURE__*/React.createElement("th",null,"CPU"),/*#__PURE__*/React.createElement("th",null,"RAM"),/*#__PURE__*/React.createElement("th",null,t('status')))),/*#__PURE__*/React.createElement("tbody",null,topGuests.map(guest=>{const cpuP=((guest.cpu||0)*100).toFixed(1);const memP=guest.maxmem>0?(guest.mem/guest.maxmem*100).toFixed(1):0;const gc=clusters.find(c=>c.id===guest.cluster_id);return/*#__PURE__*/React.createElement("tr",{key:`${guest.cluster_id}-${guest.vmid}`,className:"table-row-hover cursor-pointer",onClick:()=>{if(gc&&onSelectVm)onSelectVm(gc,guest.vmid,guest.node,guest);}},/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement(Icons.Monitor,{className:"w-3.5 h-3.5",style:{color:guest.type==='qemu'?'#49afd9':'#a178d9'}})),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{style:{fontWeight:500}},guest.name||`VM ${guest.vmid}`),/*#__PURE__*/React.createElement("span",{style:{color:'#728b9a',fontSize:'11px',marginLeft:'6px'}},"#",guest.vmid)),/*#__PURE__*/React.createElement("td",null,guest.cluster_name),/*#__PURE__*/React.createElement("td",{style:{color:'#adbbc4'}},guest.node),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{style:{color:corpBarColor(cpuP),minWidth:'32px'}},cpuP,"%"),/*#__PURE__*/React.createElement("span",{className:"inline-block",style:{width:'40px',height:'2px',background:'var(--corp-divider)',position:'relative'}},/*#__PURE__*/React.createElement("span",{style:{position:'absolute',left:0,top:0,height:'2px',width:`${Math.min(cpuP,100)}%`,background:corpBarColor(cpuP)}})))),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{style:{color:corpBarColor(memP),minWidth:'32px'}},memP,"%"),/*#__PURE__*/React.createElement("span",{className:"inline-block",style:{width:'40px',height:'2px',background:'var(--corp-divider)',position:'relative'}},/*#__PURE__*/React.createElement("span",{style:{position:'absolute',left:0,top:0,height:'2px',width:`${Math.min(memP,100)}%`,background:corpBarColor(memP)}})))),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"w-1.5 h-1.5 rounded-full inline-block",style:{background:guest.status==='running'?'#60b515':'#728b9a'}}),/*#__PURE__*/React.createElement("span",{style:{color:guest.status==='running'?'#60b515':'#728b9a',fontSize:'12px'}},guest.status==='running'?t('running'):t('stopped')))));})))),clusters.filter(c=>c.connected).length>0&&(()=>{const fmtMem=b=>{if(!b)return'0';const gb=b/(1024*1024*1024);return gb>=1?`${gb.toFixed(1)}G`:`${(b/(1024*1024)).toFixed(0)}M`;};const barColor=v=>v>80?'#f54f47':v>60?'#efc006':'#60b515';// MK: mini bar kept for fallback, donuts are primary now const MiniBar=({pct,color})=>/*#__PURE__*/React.createElement("div",{style:{width:'60px',height:'3px',background:'var(--corp-bar-track)',borderRadius:'1.5px',flexShrink:0}},/*#__PURE__*/React.createElement("div",{style:{width:`${Math.min(pct,100)}%`,height:'3px',background:color,borderRadius:'1.5px',transition:'width 0.3s'}}));const MiniDonut=({pct,color,sz})=>{const r=(sz-3)/2,c=r*2*Math.PI;const off=c-Math.min(pct,100)/100*c;return/*#__PURE__*/React.createElement("svg",{width:sz,height:sz,style:{transform:'rotate(-90deg)',flexShrink:0}},/*#__PURE__*/React.createElement("circle",{cx:sz/2,cy:sz/2,r:r,fill:"none",className:"corp-donut-track",strokeWidth:3}),/*#__PURE__*/React.createElement("circle",{cx:sz/2,cy:sz/2,r:r,fill:"none",stroke:color,strokeWidth:3,strokeLinecap:"round",strokeDasharray:c,strokeDashoffset:off,className:"corp-donut-fill"}));};return/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 py-1.5",style:{borderBottom:'1px solid var(--corp-border-medium)'}},/*#__PURE__*/React.createElement(Icons.Network,{className:"w-3.5 h-3.5",style:{color:'var(--corp-accent)'}}),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-semibold",style:{color:'var(--corp-text-secondary)'}},t('topologyView')||'Topology')),/*#__PURE__*/React.createElement("div",{className:"mt-3 flex flex-wrap gap-5"},clusters.filter(c=>c.connected).map(cluster=>{const resources=allClusterGuests[cluster.id]||[];const nodeMap={};resources.forEach(r=>{if(r.type!=='qemu'&&r.type!=='lxc')return;if(!nodeMap[r.node])nodeMap[r.node]={guests:[]};nodeMap[r.node].guests.push(r);});const statusData=allMetrics[cluster.id]?.data||{};const nodeEntries=Object.entries(nodeMap);const clusterPbs=pbsServers.filter(p=>p.linked_clusters?.includes(cluster.id)&&p.status!=='disconnected');// LW: cluster-level avg stats for header donuts const allGuests=Object.values(nodeMap).flatMap(n=>n.guests);const clAvgCpu=allGuests.length>0?allGuests.reduce((s,g)=>s+(g.cpu||0)*100,0)/allGuests.length:0;const clTotalMem=allGuests.reduce((s,g)=>s+(g.mem||0),0);const clTotalMaxMem=allGuests.reduce((s,g)=>s+(g.maxmem||0),0);const clMemPct=clTotalMaxMem>0?clTotalMem/clTotalMaxMem*100:0;return/*#__PURE__*/React.createElement("div",{key:cluster.id,className:"flex-1 min-w-[300px] max-w-[520px]",style:{border:'1px solid var(--corp-border-medium)',borderLeft:'3px solid var(--corp-accent)',background:'var(--corp-surface-0)',boxShadow:'var(--corp-shadow-sm)',transition:'box-shadow 0.15s, transform 0.15s'},onMouseEnter:e=>{e.currentTarget.style.boxShadow='var(--corp-shadow-md)';e.currentTarget.style.transform='translateY(-1px)';},onMouseLeave:e=>{e.currentTarget.style.boxShadow='var(--corp-shadow-sm)';e.currentTarget.style.transform='none';}},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 px-3 py-2.5 cursor-pointer",style:{borderBottom:'1px solid var(--corp-border-medium)',background:'var(--corp-header-bg)'},onClick:()=>onSelectCluster(cluster)},/*#__PURE__*/React.createElement("span",{className:"w-2 h-2 rounded-full topo-status-glow",style:{background:'var(--color-success)'}}),/*#__PURE__*/React.createElement(Icons.Server,{className:"w-3.5 h-3.5",style:{color:'var(--corp-accent)'}}),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-semibold",style:{color:'var(--color-text)'}},cluster.display_name||cluster.name),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 ml-auto"},allGuests.length>0&&/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement(MiniDonut,{pct:clAvgCpu,color:barColor(clAvgCpu),sz:24}),/*#__PURE__*/React.createElement("span",{className:"text-[9px] font-medium",style:{color:barColor(clAvgCpu)}},clAvgCpu.toFixed(0),"%")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement(MiniDonut,{pct:clMemPct,color:barColor(clMemPct),sz:24}),/*#__PURE__*/React.createElement("span",{className:"text-[9px] font-medium",style:{color:barColor(clMemPct)}},clMemPct.toFixed(0),"%"))),/*#__PURE__*/React.createElement("span",{className:"text-[10px]",style:{color:'var(--corp-text-muted)'}},nodeEntries.length,"N \xB7 ",allGuests.length,"G"))),/*#__PURE__*/React.createElement("div",{className:"p-3 space-y-2.5"},nodeEntries.map(([nodeName,nd])=>{const isOnline=true;// connected cluster = reachable @@ -3277,8 +3277,8 @@ useEffect(()=>{if(!xReplForm.target_cluster||!authFetch)return;let cancelled=false;const fetchTargetResources=async()=>{setXReplLoadingResources(true);setXReplTargetStorages([]);setXReplTargetBridges([]);try{const nodesRes=await authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes`);if(!nodesRes||!nodesRes.ok||cancelled)return;const nodesData=await nodesRes.json();const onlineNode=(Array.isArray(nodesData)?nodesData:nodesData.nodes||[]).find(n=>n.status==='online');if(!onlineNode||cancelled)return;const nodeName=onlineNode.node||onlineNode.name;const[storRes,netRes]=await Promise.all([authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes/${nodeName}/storage`),authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes/${nodeName}/networks`)]);if(cancelled)return;if(storRes&&storRes.ok){const storData=await storRes.json();setXReplTargetStorages((Array.isArray(storData)?storData:storData.storages||[]).filter(s=>s.content&&(s.content.includes('images')||s.content.includes('rootdir'))));}if(netRes&&netRes.ok){const netData=await netRes.json();setXReplTargetBridges((Array.isArray(netData)?netData:netData.networks||[]).filter(n=>n.type==='bridge'||n.type==='OVSBridge'||n.source==='sdn'));}}catch(err){console.error('xrepl target resources:',err);}if(!cancelled)setXReplLoadingResources(false);};fetchTargetResources();return()=>{cancelled=true;};},[xReplForm.target_cluster]);// NS: Cross-cluster replication handlers const handleCreateXRepl=async()=>{try{const res=await authFetch(`${API_URL}/cross-cluster-replications`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({source_cluster:xReplForm.source_cluster,target_cluster:xReplForm.target_cluster,vmid:parseInt(xReplForm.vmid),vm_type:xReplForm.vm_type||'qemu',target_storage:xReplForm.target_storage,target_bridge:xReplForm.target_bridge,schedule:xReplForm.schedule,retention:parseInt(xReplForm.retention)||3})});if(res&&res.ok){if(addToast)addToast(t('xReplCreated')||'Replication job created','success');setShowCreateXRepl(false);setXReplForm({source_cluster:'',vmid:'',vm_type:'qemu',target_cluster:'',target_storage:'',target_bridge:'vmbr0',schedule:'0 */6 * * *',retention:3});await fetchXReplJobs();}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplCreateFailed')||'Failed to create replication job','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleDeleteXRepl=async jobId=>{if(!confirm(t('confirmDeleteXRepl')||'Delete this replication job?'))return;try{const res=await authFetch(`${API_URL}/cross-cluster-replications/${jobId}`,{method:'DELETE'});if(res&&res.ok){if(addToast)addToast(t('xReplDeleted')||'Replication job deleted','success');await fetchXReplJobs();}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplDeleteFailed')||'Failed to delete','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleRunXReplNow=async jobId=>{try{const res=await authFetch(`${API_URL}/cross-cluster-replications/${jobId}/run`,{method:'POST'});if(res&&res.ok){if(addToast)addToast(t('xReplStarted')||'Replication started','success');}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplStartFailed')||'Failed to start','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleSave=async()=>{if(!form.name.trim())return;setSaving(true);await onSave(form);setSaving(false);};// MK: tab config const tabs=[{id:'general',label:t('general')||'General',icon:Icons.Settings},{id:'lb',label:t('crossClusterLB')||'Cross-Cluster LB',icon:Icons.Scale},{id:'replication',label:t('crossClusterReplication')||'Replication',icon:Icons.Globe},{id:'info',label:t('info')||'Info',icon:Icons.Info}];return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4"},/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl w-full max-w-2xl max-h-[80vh] overflow-y-auto"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-5 border-b border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"w-10 h-10 rounded-lg flex items-center justify-center",style:{backgroundColor:(form.color||'#E86F2D')+'33'}},/*#__PURE__*/React.createElement(Icons.Folder,{className:"w-5 h-5",style:{color:form.color||'#E86F2D'}})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-semibold text-white"},t('groupSettings')||'Group Settings'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},group.name))),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-gray-400 hover:text-white transition-colors"},/*#__PURE__*/React.createElement(Icons.X,null))),/*#__PURE__*/React.createElement("div",{className:"flex border-b border-proxmox-border"},tabs.map(tab=>/*#__PURE__*/React.createElement("button",{key:tab.id,onClick:()=>setActiveTab(tab.id),className:`flex items-center gap-2 px-4 py-2.5 text-sm font-medium transition-colors whitespace-nowrap ${activeTab===tab.id?'text-proxmox-orange border-b-2 border-proxmox-orange bg-proxmox-dark/50':'text-gray-400 hover:text-white hover:bg-proxmox-dark/30'}`},/*#__PURE__*/React.createElement(tab.icon,{className:"w-4 h-4"}),tab.label))),/*#__PURE__*/React.createElement("div",{className:"p-5"},activeTab==='general'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('name')||'Name'," *"),/*#__PURE__*/React.createElement("input",{value:form.name,onChange:e=>setForm(p=>({...p,name:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange",placeholder:"Production Cluster Group"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('description')||'Description'),/*#__PURE__*/React.createElement("textarea",{value:form.description,onChange:e=>setForm(p=>({...p,description:e.target.value})),rows:3,className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange resize-none",placeholder:"Optional description for this group..."})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('color')||'Color'),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("input",{type:"color",value:form.color,onChange:e=>setForm(p=>({...p,color:e.target.value})),className:"w-10 h-10 rounded cursor-pointer border border-proxmox-border"}),/*#__PURE__*/React.createElement("input",{type:"text",value:form.color,onChange:e=>setForm(p=>({...p,color:e.target.value})),className:"w-28 px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm font-mono focus:outline-none focus:border-proxmox-orange",placeholder:"#E86F2D"}),/*#__PURE__*/React.createElement("div",{className:"flex gap-1.5"},['#E86F2D','#3B82F6','#22C55E','#EAB308','#8B5CF6','#EC4899'].map(c=>/*#__PURE__*/React.createElement("button",{key:c,onClick:()=>setForm(p=>({...p,color:c})),className:`w-6 h-6 rounded-full border-2 transition-all ${form.color===c?'border-white scale-110':'border-transparent hover:border-gray-500'}`,style:{backgroundColor:c}})))))),activeTab==='lb'&&/*#__PURE__*/React.createElement("div",{className:"space-y-5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white"},t('enableCrossClusterLB')||'Enable Cross-Cluster Load Balancing'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('lbDescription')||'Automatically migrate VMs between clusters when resource thresholds are exceeded')),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_lb_enabled,onChange:e=>setForm(p=>({...p,cross_cluster_lb_enabled:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-proxmox-orange rounded-full peer-focus:ring-2 peer-focus:ring-proxmox-orange/50 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),form.cross_cluster_lb_enabled&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex justify-end"},/*#__PURE__*/React.createElement("button",{onClick:handleXclbBalanceNow,disabled:xclbRunning,className:"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg bg-proxmox-orange/20 text-proxmox-orange hover:bg-proxmox-orange/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"},xclbRunning?React.createElement('span',{className:'w-3.5 h-3.5 border-2 border-proxmox-orange/40 border-t-proxmox-orange rounded-full animate-spin'}):React.createElement(Icons.RefreshCw,{className:'w-3.5 h-3.5'}),t('balanceNow')||'Balance Now')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-3 bg-yellow-500/5 border border-yellow-500/20 rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-4 h-4 text-yellow-400"}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"text-sm text-yellow-300"},t('dryRunMode')||'Dry Run / Simulation Mode'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},t('dryRunDesc')||'Log what would happen without actually migrating'))),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_dry_run,onChange:e=>setForm(p=>({...p,cross_cluster_dry_run:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-yellow-500 rounded-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),/*#__PURE__*/React.createElement(Slider,{label:t('cpuThreshold')||'CPU Threshold (%)',description:t('crossClusterThresholdDesc')||'CPU threshold for cluster imbalance (10-80%)',value:form.cross_cluster_threshold,onChange:v=>setForm(p=>({...p,cross_cluster_threshold:v})),min:10,max:80}),/*#__PURE__*/React.createElement(Slider,{label:t('checkInterval')||'Check Interval',description:t('crossClusterIntervalDesc')||'Time between check cycles',value:form.cross_cluster_interval,onChange:v=>setForm(p=>({...p,cross_cluster_interval:v})),min:300,max:3600,step:60,unit:"s"}),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetStorage')||'Target Storage'),loadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loadingCrossClusterResources')||'Loading...'):commonStorages.length>0?/*#__PURE__*/React.createElement("select",{value:form.cross_cluster_target_storage,onChange:e=>setForm(p=>({...p,cross_cluster_target_storage:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange"},/*#__PURE__*/React.createElement("option",{value:""},t('selectStorage')||'Select storage...'),commonStorages.map(s=>/*#__PURE__*/React.createElement("option",{key:s.storage,value:s.storage},s.storage," (",s.type,")"))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-yellow-400 py-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3 inline mr-1"}),t('noCommonStorages')||'No common storage found across all clusters'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-1"},t('commonStorageHint')||'Only storages available on all clusters')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetBridge')||'Target Bridge'),loadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loadingCrossClusterResources')||'Loading...'):commonBridges.length>0?/*#__PURE__*/React.createElement("select",{value:form.cross_cluster_target_bridge,onChange:e=>setForm(p=>({...p,cross_cluster_target_bridge:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange"},commonBridges.filter(b=>b.source!=='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"Local Bridges"},commonBridges.filter(b=>b.source!=='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface,b.comments?` - ${b.comments}`:''))),commonBridges.filter(b=>b.source==='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"SDN VNets"},commonBridges.filter(b=>b.source==='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface," - ",b.zone||'SDN',b.alias?` (${b.alias})`:'')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-yellow-400 py-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3 inline mr-1"}),t('noCommonBridges')||'No common bridge found across all clusters'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-1"},t('commonBridgeHint')||'Only bridges available on all clusters'))),/*#__PURE__*/React.createElement(Slider,{label:t('maxMigrations')||'Max Migrations per Cycle',description:t('crossClusterMaxMigrationsDesc')||'Max migrations per check cycle',value:form.cross_cluster_max_migrations,onChange:v=>setForm(p=>({...p,cross_cluster_max_migrations:v})),min:1,max:5,step:1,unit:""}),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-3 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"text-sm text-white"},t('includeContainers')||'Include Containers'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('includeContainersDesc')||'Include containers (LXC) in cross-cluster balancing'),form.cross_cluster_include_containers&&/*#__PURE__*/React.createElement("p",{className:"text-xs text-yellow-400 mt-1 flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3"}),t('containerMigrationWarning')||'Containers are restarted during migration (downtime)')),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer ml-4"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_include_containers,onChange:e=>setForm(p=>({...p,cross_cluster_include_containers:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-proxmox-orange rounded-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-1"},t('excludedVMsCrossCluster')||'Excluded VMs/Containers'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mb-3"},t('excludedVMsCrossClusterDesc')||'VMs and containers excluded from automatic cross-cluster balancing'),loadingExcludedVMs?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loading'),"..."):groupClusters.filter(c=>c.connected).length===0?/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-600 py-2"},t('noExcludedVMsInGroup')||'No VMs excluded'):/*#__PURE__*/React.createElement("div",{className:"space-y-2"},groupClusters.filter(c=>c.connected).map(cluster=>{const excluded=excludedVMsByCluster[cluster.id]||[];const allVMs=allVMsByCluster[cluster.id]||[];const excludedIds=excluded.map(v=>v.vmid);const available=allVMs.filter(vm=>!excludedIds.includes(vm.vmid));const isExpanded=expandedClusters[cluster.id];return/*#__PURE__*/React.createElement("div",{key:cluster.id,className:"border border-proxmox-border rounded-lg overflow-hidden"},/*#__PURE__*/React.createElement("button",{onClick:()=>setExpandedClusters(prev=>({...prev,[cluster.id]:!prev[cluster.id]})),className:"w-full flex items-center justify-between p-2.5 bg-proxmox-dark/50 hover:bg-proxmox-dark text-left"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Server,{className:"w-3.5 h-3.5 text-proxmox-orange"}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white"},cluster.name),excluded.length>0&&/*#__PURE__*/React.createElement("span",{className:"text-xs bg-red-500/20 text-red-400 px-1.5 py-0.5 rounded"},excluded.length)),/*#__PURE__*/React.createElement(Icons.ChevronDown,{className:`w-4 h-4 text-gray-500 transition-transform ${isExpanded?'rotate-180':''}`})),isExpanded&&/*#__PURE__*/React.createElement("div",{className:"p-2.5 space-y-2 border-t border-proxmox-border"},excluded.length>0?/*#__PURE__*/React.createElement("div",{className:"space-y-1"},excluded.map(vm=>/*#__PURE__*/React.createElement("div",{key:vm.vmid,className:"flex items-center justify-between bg-proxmox-dark rounded px-2.5 py-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Monitor,{className:"w-3.5 h-3.5 text-red-400"}),/*#__PURE__*/React.createElement("span",{className:"text-sm"},vm.name||`VM ${vm.vmid}`),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},"(",vm.vmid,")")),/*#__PURE__*/React.createElement("button",{onClick:()=>includeVM(cluster.id,vm.vmid),className:"text-xs text-green-400 hover:text-green-300"},t('include')||'Include')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-600 p-2"},t('noExcludedVMsInGroup')||'No VMs excluded'),available.length>0&&/*#__PURE__*/React.createElement("div",{className:"flex gap-2 mt-1"},/*#__PURE__*/React.createElement("select",{id:`xclb-exclude-${cluster.id}`,className:"flex-1 bg-proxmox-dark border border-proxmox-border rounded px-2 py-1.5 text-sm",defaultValue:""},/*#__PURE__*/React.createElement("option",{value:"",disabled:true},t('selectVMToExclude')||'Select VM to exclude...'),available.map(vm=>/*#__PURE__*/React.createElement("option",{key:vm.vmid,value:vm.vmid},vm.name||`VM ${vm.vmid}`," (",vm.vmid,") - ",vm.node))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const select=document.getElementById(`xclb-exclude-${cluster.id}`);const vmid=parseInt(select?.value);if(!vmid)return;const vm=available.find(v=>v.vmid===vmid);excludeVM(cluster.id,vmid,vm?.name);select.value='';},className:"px-2.5 py-1.5 bg-red-500/20 text-red-400 rounded hover:bg-red-500/30 text-xs flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.Ban,{className:"w-3.5 h-3.5"}),t('exclude')||'Exclude'))));}))))),activeTab==='replication'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Globe,{className:"w-4 h-4 text-proxmox-orange"}),t('crossClusterReplication')||'Cross-Cluster Replication'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('crossClusterReplicationDesc')||'Replicate VM snapshots to another cluster (DR)')),groupClusters.length>=2&&/*#__PURE__*/React.createElement("button",{onClick:()=>setShowCreateXRepl(true),className:"flex items-center gap-1.5 px-3 py-1.5 bg-proxmox-orange/10 text-proxmox-orange rounded-lg text-xs hover:bg-proxmox-orange/20 transition-colors"},/*#__PURE__*/React.createElement(Icons.Plus,{className:"w-3.5 h-3.5"}),t('addDrJob')||'Add DR Job')),groupClusters.length<2?/*#__PURE__*/React.createElement("div",{className:"text-center py-8 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-8 h-8 mx-auto mb-2 text-gray-600"}),t('needTwoClusters')||'At least 2 clusters needed for cross-cluster replication'):xReplLoading?/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 py-8 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loading'),"..."):xReplJobs.length===0&&!showCreateXRepl?/*#__PURE__*/React.createElement("div",{className:"text-center py-8 text-gray-500 text-sm"},t('noReplicationJobs')||'No replication jobs configured'):/*#__PURE__*/React.createElement("div",{className:"space-y-2"},xReplJobs.map(job=>{const srcCluster=groupClusters.find(c=>c.id===job.source_cluster);const tgtCluster=groupClusters.find(c=>c.id===job.target_cluster);return/*#__PURE__*/React.createElement("div",{key:job.id,className:"bg-proxmox-dark rounded-lg p-3 flex items-center justify-between border border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex-1 min-w-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-wrap"},/*#__PURE__*/React.createElement("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${job.enabled?'bg-green-500':'bg-gray-500'}`}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white truncate"},srcCluster?.name||job.source_cluster),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3.5 h-3.5 text-gray-500 flex-shrink-0"}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white truncate"},tgtCluster?.name||job.target_cluster),/*#__PURE__*/React.createElement("span",{className:"text-xs bg-proxmox-dark px-1.5 py-0.5 rounded text-gray-400 border border-proxmox-border"},job.vm_type==='lxc'?'CT':'VM'," ",job.vmid)),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 mt-1 flex items-center gap-2 flex-wrap"},/*#__PURE__*/React.createElement("span",null,job.schedule),/*#__PURE__*/React.createElement("span",null,"\xB7"),/*#__PURE__*/React.createElement("span",null,job.target_storage||'default'),job.last_run&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("span",null,"\xB7"),/*#__PURE__*/React.createElement("span",null,t('lastRunPrefix')||'Last',": ",new Date(job.last_run).toLocaleString())),job.last_status&&/*#__PURE__*/React.createElement("span",{className:job.last_status==='OK'?'text-green-400':'text-red-400'},job.last_status),job.last_error&&/*#__PURE__*/React.createElement("span",{className:"text-red-400"},job.last_error))),/*#__PURE__*/React.createElement("div",{className:"flex gap-1 flex-shrink-0 ml-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>handleRunXReplNow(job.id),className:"p-1.5 rounded hover:bg-green-500/10 text-gray-400 hover:text-green-400",title:t('runNow')||'Run now'},/*#__PURE__*/React.createElement(Icons.Play,{className:"w-3.5 h-3.5"})),/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteXRepl(job.id),className:"p-1.5 rounded hover:bg-red-500/10 text-gray-400 hover:text-red-400",title:t('delete')||'Delete'},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-3.5 h-3.5"}))));})),showCreateXRepl&&groupClusters.length>=2&&/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark border border-proxmox-border rounded-lg p-4"},/*#__PURE__*/React.createElement("h5",{className:"text-sm font-medium text-white mb-3"},t('newCrossClusterReplication')||'New Cross-Cluster Replication'),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('sourceCluster')||'Source Cluster'),/*#__PURE__*/React.createElement("select",{value:xReplForm.source_cluster,onChange:e=>setXReplForm(f=>({...f,source_cluster:e.target.value,vmid:'',vm_type:'qemu'})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectCluster')||'Select cluster...'),groupClusters.filter(c=>c.connected).map(c=>/*#__PURE__*/React.createElement("option",{key:c.id,value:c.id},c.name)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},"VM"),xReplLoadingVMs?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.source_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.vmid,onChange:e=>{const vmid=e.target.value;const vm=xReplSourceVMs.find(v=>String(v.vmid)===vmid);setXReplForm(f=>({...f,vmid,vm_type:vm?.type||'qemu'}));},className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectVM')||'Select VM...'),xReplSourceVMs.map(vm=>/*#__PURE__*/React.createElement("option",{key:vm.vmid,value:vm.vmid},vm.name||`VM ${vm.vmid}`," (",vm.vmid,") - ",vm.type==='lxc'?'CT':'VM'))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a source cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetCluster')||'Target Cluster'),/*#__PURE__*/React.createElement("select",{value:xReplForm.target_cluster,onChange:e=>setXReplForm(f=>({...f,target_cluster:e.target.value,target_storage:'',target_bridge:'vmbr0'})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectCluster')||'Select cluster...'),groupClusters.filter(c=>c.connected&&c.id!==xReplForm.source_cluster).map(c=>/*#__PURE__*/React.createElement("option",{key:c.id,value:c.id},c.name)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetStorage')||'Target Storage'),xReplLoadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.target_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.target_storage,onChange:e=>setXReplForm(f=>({...f,target_storage:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectStorage')||'Select storage...'),xReplTargetStorages.map(s=>/*#__PURE__*/React.createElement("option",{key:s.storage,value:s.storage},s.storage," (",s.type,")"))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a target cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetBridge')||'Target Bridge'),xReplLoadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.target_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.target_bridge,onChange:e=>setXReplForm(f=>({...f,target_bridge:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},xReplTargetBridges.filter(b=>b.source!=='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"Local Bridges"},xReplTargetBridges.filter(b=>b.source!=='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface,b.comments?` - ${b.comments}`:''))),xReplTargetBridges.filter(b=>b.source==='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"SDN VNets"},xReplTargetBridges.filter(b=>b.source==='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface," - ",b.zone||'SDN',b.alias?` (${b.alias})`:'')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a target cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('scheduleCron')||'Schedule (Cron)'),/*#__PURE__*/React.createElement("input",{type:"text",value:xReplForm.schedule,onChange:e=>setXReplForm(f=>({...f,schedule:e.target.value})),placeholder:"0 */6 * * *",className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('replicationRetention')||'Retention'),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",max:"30",value:xReplForm.retention,onChange:e=>setXReplForm(f=>({...f,retention:parseInt(e.target.value)||1})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"}))),/*#__PURE__*/React.createElement("div",{className:"flex gap-2 mt-3"},/*#__PURE__*/React.createElement("button",{onClick:handleCreateXRepl,disabled:!xReplForm.source_cluster||!xReplForm.vmid||!xReplForm.target_cluster,className:"px-3 py-1.5 bg-proxmox-orange text-white rounded-lg text-sm hover:bg-proxmox-orange/90 transition-colors disabled:opacity-50"},t('create')||'Create'),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowCreateXRepl(false),className:"px-3 py-1.5 bg-proxmox-dark border border-proxmox-border text-gray-300 rounded-lg text-sm hover:bg-proxmox-darker transition-colors"},t('cancel')||'Cancel'))),Object.keys(nativeReplByCluster).length>0&&/*#__PURE__*/React.createElement("div",{className:"mt-6"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mb-3"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 text-purple-400"}),/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white"},t('nativeProxmoxReplication')||'Native Proxmox Replication (ZFS)')),/*#__PURE__*/React.createElement("div",{className:"space-y-3"},Object.entries(nativeReplByCluster).map(([cid,jobs])=>{const cluster=groupClusters.find(c=>c.id===cid);return/*#__PURE__*/React.createElement("div",{key:cid,className:"bg-proxmox-dark rounded-lg border border-proxmox-border overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"px-3 py-2 border-b border-proxmox-border bg-purple-500/5"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-medium text-purple-300"},cluster?.name||cid)),jobs.map((job,idx)=>{const hasErr=job.fail_count>0||job.error;const lastSync=job.last_sync?new Date(job.last_sync*1000).toLocaleString():'-';return/*#__PURE__*/React.createElement("div",{key:job.id||idx,className:"px-3 py-2 flex items-center justify-between border-b border-proxmox-border/50 last:border-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-wrap min-w-0"},/*#__PURE__*/React.createElement("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${job.disable?'bg-gray-500':hasErr?'bg-red-500':'bg-green-500'}`}),/*#__PURE__*/React.createElement("span",{className:"text-xs bg-proxmox-dark px-1.5 py-0.5 rounded text-gray-400 border border-proxmox-border"},"VM ",job.guest),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},job.source||'?'),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3 h-3 text-gray-600 flex-shrink-0"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},job.target),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600 font-mono"},job.schedule)),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-shrink-0 ml-2"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600"},lastSync),job.duration!=null&&/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600 font-mono"},job.duration.toFixed(1),"s")));}));})))),activeTab==='info'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-3"},t('groupInfo')||'Group Information'),/*#__PURE__*/React.createElement("div",{className:"space-y-2 text-sm"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('groupId')||'Group ID'),/*#__PURE__*/React.createElement("span",{className:"text-white font-mono text-xs"},group.id)),/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('created')||'Created'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.created?new Date(group.created).toLocaleString():'-')),group.tenant_id&&/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('tenant')||'Tenant'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.tenant_id)))),group.cross_cluster_lb_enabled&&/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-3"},t('lbStatus')||'Load Balancing Status'),/*#__PURE__*/React.createElement("div",{className:"space-y-2 text-sm"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('lastRun')||'Last LB Run'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.last_lb_run?new Date(group.last_lb_run).toLocaleString():t('neverRun')||'Never run')),/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('mode')||'Mode'),/*#__PURE__*/React.createElement("span",{className:group.cross_cluster_dry_run?'text-yellow-400':'text-green-400'},group.cross_cluster_dry_run?t('simulation')||'Simulation':t('active')||'Active')))),/*#__PURE__*/React.createElement("div",{className:"p-4 bg-blue-500/5 border border-blue-500/20 rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-start gap-3"},/*#__PURE__*/React.createElement(Icons.Info,{className:"w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5"}),/*#__PURE__*/React.createElement("div",{className:"text-sm text-gray-400"},/*#__PURE__*/React.createElement("p",{className:"mb-2"},t('lbExplanation')||'Cross-Cluster Load Balancing monitors CPU and RAM usage across all clusters in this group. When a cluster exceeds the configured threshold, VMs are automatically migrated to a less loaded cluster.'),/*#__PURE__*/React.createElement("p",null,t('lbDryRunExplanation')||'Enable Dry Run mode first to review what actions would be taken before enabling live migrations.')))))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-end gap-3 p-5 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-sm text-gray-400 hover:text-white transition-colors"},t('cancel')||'Cancel'),/*#__PURE__*/React.createElement("button",{onClick:handleSave,disabled:saving||!form.name.trim(),className:"px-5 py-2 bg-proxmox-orange hover:bg-orange-600 disabled:opacity-50 rounded-lg text-sm font-medium text-white transition-colors flex items-center gap-2"},saving?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"})," ",t('saving')||'Saving...'):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement(Icons.Save,{className:"w-4 h-4"})," ",t('save')||'Save')))));}// Cluster Health Widget -function ClusterHealth({metrics,isCorporate}){const{t}=useTranslation();const nodes=Object.entries(metrics);if(nodes.length===0)return null;const avgCpu=nodes.reduce((acc,[,m])=>acc+m.cpu_percent,0)/nodes.length;const avgMem=nodes.reduce((acc,[,m])=>acc+m.mem_percent,0)/nodes.length;const avgScore=nodes.reduce((acc,[,m])=>acc+m.score,0)/nodes.length;const onlineNodes=nodes.filter(([,m])=>m.status==='online'&&!m.maintenance_mode).length;const maintenanceNodes=nodes.filter(([,m])=>m.maintenance_mode).length;const healthScore=Math.max(0,100-avgScore/2);const healthLabel=healthScore>=80?t('excellent'):healthScore>=60?t('good'):healthScore>=40?t('warning'):t('critical');const healthColor=healthScore>=80?'#22c55e':healthScore>=60?'#84cc16':healthScore>=40?'#eab308':'#ef4444';// LW: Feb 2026 - corporate compact variant (Clarity dark theme) -const corpHealthColor=healthScore>=80?'#60b515':healthScore>=60?'#60b515':healthScore>=40?'#efc006':'#f54f47';if(isCorporate){return/*#__PURE__*/React.createElement("div",{className:"p-3",style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)'}},/*#__PURE__*/React.createElement("h3",{className:"text-[11px] font-semibold uppercase tracking-wider mb-2",style:{color:'var(--corp-text-muted)'}},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4 flex-wrap"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-20 h-2 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${healthScore}%`,backgroundColor:corpHealthColor,borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-medium",style:{color:'var(--color-text)'}},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-[11px]",style:{color:'var(--corp-text-muted)'}},healthLabel)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},t('nodesOnline'),": ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},onlineNodes,"/",nodes.length)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"CPU: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},avgCpu.toFixed(1),"%")),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"RAM: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},avgMem.toFixed(1),"%")),maintenanceNodes>0&&/*#__PURE__*/React.createElement("span",{className:"text-[12px] flex items-center gap-1",style:{color:'var(--color-warning)'}},/*#__PURE__*/React.createElement(Icons.Wrench,{className:"w-3 h-3"})," ",maintenanceNodes," ",t('maintenance'))));}return/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl p-5"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4"},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center mb-6"},/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement("svg",{viewBox:"0 0 100 100",className:"w-32 h-32"},/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#30363D",strokeWidth:"8"}),/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:healthColor,strokeWidth:"8",strokeLinecap:"round",strokeDasharray:`${healthScore*2.83} 283`,transform:"rotate(-90 50 50)",className:"transition-all duration-1000"})),/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 flex flex-col items-center justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-2xl font-bold text-white"},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-400"},healthLabel)))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},onlineNodes,"/",nodes.length),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('nodesOnline'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgScore.toFixed(0)),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgScore'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgCpu.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgCpu'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgMem.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgRam')))),maintenanceNodes>0&&/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-4 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 text-yellow-400"},/*#__PURE__*/React.createElement(Icons.Wrench,null),/*#__PURE__*/React.createElement("span",{className:"text-sm font-medium"},maintenanceNodes," Node(s) ",t('maintenance')))));}// Create Snapshot Modal - NS: Feb 2026 enhanced with efficient mode +function ClusterHealth({metrics,isCorporate}){const{t}=useTranslation();const nodes=Object.entries(metrics);if(nodes.length===0)return null;const avgCpu=nodes.reduce((acc,[,m])=>acc+m.cpu_percent,0)/nodes.length;const avgMem=nodes.reduce((acc,[,m])=>acc+m.mem_percent,0)/nodes.length;const avgDisk=nodes.reduce((acc,[,m])=>acc+(m.disk_percent||0),0)/nodes.length;const onlineNodes=nodes.filter(([,m])=>m.status==='online'&&!m.maintenance_mode).length;const maintenanceNodes=nodes.filter(([,m])=>m.maintenance_mode).length;const offlineNodes=nodes.length-onlineNodes-maintenanceNodes;const healthScore=Math.max(0,100-(avgCpu*0.3+avgMem*0.3+avgDisk*0.2+(nodes.length>0?offlineNodes/nodes.length*100*0.2:0)));const healthLabel=healthScore>=80?t('excellent'):healthScore>=60?t('good'):healthScore>=40?t('warning'):t('critical');const healthColor=healthScore>=80?'#22c55e':healthScore>=60?'#84cc16':healthScore>=40?'#eab308':'#ef4444';// LW: Feb 2026 - corporate compact variant (Clarity dark theme) +const corpHealthColor=healthScore>=80?'#60b515':healthScore>=60?'#60b515':healthScore>=40?'#efc006':'#f54f47';if(isCorporate){return/*#__PURE__*/React.createElement("div",{className:"p-3",style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)'},title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-[11px] font-semibold uppercase tracking-wider mb-2",style:{color:'var(--corp-text-muted)'}},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4 flex-wrap"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-20 h-2 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${healthScore}%`,backgroundColor:corpHealthColor,borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-medium",style:{color:'var(--color-text)'}},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-[11px]",style:{color:'var(--corp-text-muted)'}},healthLabel)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},t('nodesOnline'),": ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},onlineNodes,"/",nodes.length)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"CPU: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},avgCpu.toFixed(1),"%")),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"RAM: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},avgMem.toFixed(1),"%")),maintenanceNodes>0&&/*#__PURE__*/React.createElement("span",{className:"text-[12px] flex items-center gap-1",style:{color:'var(--color-warning)'}},/*#__PURE__*/React.createElement(Icons.Wrench,{className:"w-3 h-3"})," ",maintenanceNodes," ",t('maintenance'))));}return/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl p-5",title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4"},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center mb-6"},/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement("svg",{viewBox:"0 0 100 100",className:"w-32 h-32"},/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#30363D",strokeWidth:"8"}),/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:healthColor,strokeWidth:"8",strokeLinecap:"round",strokeDasharray:`${healthScore*2.83} 283`,transform:"rotate(-90 50 50)",className:"transition-all duration-1000"})),/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 flex flex-col items-center justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-2xl font-bold text-white"},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-400"},healthLabel)))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},onlineNodes,"/",nodes.length),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('nodesOnline'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgDisk.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgStorage'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgCpu.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgCpu'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgMem.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgRam')))),maintenanceNodes>0&&/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-4 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 text-yellow-400"},/*#__PURE__*/React.createElement(Icons.Wrench,null),/*#__PURE__*/React.createElement("span",{className:"text-sm font-medium"},maintenanceNodes," Node(s) ",t('maintenance')))));}// Create Snapshot Modal - NS: Feb 2026 enhanced with efficient mode function CreateSnapshotModal({isQemu,onSubmit,onClose,loading,efficientInfo}){const{t}=useTranslation();const[snapname,setSnapname]=useState(`snap_${Date.now()}`);const[description,setDescription]=useState('');const[vmstate,setVmstate]=useState(false);const[mode,setMode]=useState('standard');const[snapSizeGb,setSnapSizeGb]=useState(efficientInfo?.recommended_snap_size_gb||10);const isEfficient=mode==='efficient';const canEfficient=efficientInfo?.eligible;const handleSubmit=()=>{if(!snapname.trim())return;onSubmit(snapname.trim(),description,vmstate,isEfficient?{mode:'efficient',snap_size_gb:snapSizeGb}:{mode:'standard'});};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:`w-full ${canEfficient?'max-w-lg':'max-w-md'} bg-proxmox-card border border-proxmox-border rounded-xl p-6 animate-scale-in`},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold text-white mb-4"},t('createSnapshot')),/*#__PURE__*/React.createElement("div",{className:"space-y-4"},canEfficient&&/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-2"},t('snapshotMode')),/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setMode('standard'),className:`flex-1 px-3 py-2 rounded-lg text-sm border transition-colors ${!isEfficient?'bg-blue-600/20 border-blue-500 text-blue-400':'bg-proxmox-dark border-proxmox-border text-gray-400 hover:border-gray-500'}`},t('normalMode')),/*#__PURE__*/React.createElement("button",{onClick:()=>setMode('efficient'),className:`flex-1 px-3 py-2 rounded-lg text-sm border transition-colors ${isEfficient?'bg-green-600/20 border-green-500 text-green-400':'bg-proxmox-dark border-proxmox-border text-gray-400 hover:border-gray-500'}`},/*#__PURE__*/React.createElement("span",{className:"flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement(Icons.Zap,null),t('efficientMode'))))),isEfficient&&efficientInfo&&/*#__PURE__*/React.createElement("div",{className:"p-3 bg-proxmox-dark rounded-lg border border-green-500/30 space-y-3"},/*#__PURE__*/React.createElement("div",{className:"text-sm font-medium text-green-400 flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.Zap,null),t('spaceSavings'),": ",efficientInfo.savings_percent,"%"),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs text-gray-400 mb-1"},/*#__PURE__*/React.createElement("span",null,t('normalSnapshotSize')),/*#__PURE__*/React.createElement("span",null,efficientInfo.total_disk_size_gb?.toFixed(1)," GB")),/*#__PURE__*/React.createElement("div",{className:"w-full h-3 bg-gray-700 rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"h-full bg-red-500 rounded-full",style:{width:'100%'}}))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs text-gray-400 mb-1"},/*#__PURE__*/React.createElement("span",null,t('efficientSnapshotSize')),/*#__PURE__*/React.createElement("span",null,"~",snapSizeGb?.toFixed(1)," GB")),/*#__PURE__*/React.createElement("div",{className:"w-full h-3 bg-gray-700 rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"h-full bg-green-500 rounded-full",style:{width:`${Math.max(3,snapSizeGb/efficientInfo.total_disk_size_gb*100)}%`}}))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('snapshotSizeGb')," (",t('recommended'),": ",efficientInfo.recommended_snap_size_gb?.toFixed(1)," GB)"),/*#__PURE__*/React.createElement("input",{type:"number",value:snapSizeGb,onChange:e=>setSnapSizeGb(parseFloat(e.target.value)||1),min:"1",max:efficientInfo.vg_free_gb-2,step:"1",className:"w-full px-3 py-1.5 bg-proxmox-card border border-proxmox-border rounded-lg text-white text-sm"})),/*#__PURE__*/React.createElement("div",{className:`text-xs flex items-center gap-1 ${efficientInfo.has_guest_agent?'text-green-400':'text-yellow-400'}`},efficientInfo.has_guest_agent?/*#__PURE__*/React.createElement(Icons.CheckCircle,null):/*#__PURE__*/React.createElement(Icons.AlertTriangle,null),efficientInfo.has_guest_agent?t('guestAgentDetected'):t('noGuestAgent')),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 italic"},t('managedByPegaprox'))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('name')),/*#__PURE__*/React.createElement("input",{type:"text",value:snapname,onChange:e=>setSnapname(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:"snapshot-name"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('description')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"text",value:description,onChange:e=>setDescription(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('snapshotDescription')})),isQemu&&!isEfficient&&/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-2 text-sm text-gray-300"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:vmstate,onChange:e=>setVmstate(e.target.checked),className:"rounded"}),t('saveRamState'))),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-3 mt-6"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-gray-300 hover:text-white"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:handleSubmit,disabled:loading||!snapname.trim(),className:"flex items-center gap-2 px-4 py-2 rounded-lg text-white disabled:opacity-50 bg-green-600 hover:bg-green-700"},loading&&/*#__PURE__*/React.createElement(Icons.RotateCw,null),isEfficient&&/*#__PURE__*/React.createElement(Icons.Zap,null),t('create')))));}// Create Replication Modal function CreateReplicationModal({nodes,onSubmit,onClose,loading}){const{t}=useTranslation();const[target,setTarget]=useState(nodes[0]||'');const[schedule,setSchedule]=useState('*/15');const[rate,setRate]=useState('');const[comment,setComment]=useState('');const scheduleOptions=[{value:'*/5',label:t('every5min')},{value:'*/15',label:t('every15min')},{value:'*/30',label:t('every30min')},{value:'0 *',label:t('hourly')},{value:'0 */2',label:t('every2hours')},{value:'0 */4',label:t('every4hours')},{value:'0 */6',label:t('every6hours')},{value:'0 */12',label:t('every12hours')},{value:'0 0',label:t('daily')}];const handleSubmit=()=>{if(!target)return;onSubmit(target,schedule,rate?parseInt(rate):null,comment);};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-md bg-proxmox-card border border-proxmox-border rounded-xl p-6 animate-scale-in"},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold text-white mb-4"},t('createReplicationJob')),/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetNode')),/*#__PURE__*/React.createElement("select",{value:target,onChange:e=>setTarget(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white"},nodes.map(n=>/*#__PURE__*/React.createElement("option",{key:n,value:n},n)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},"Schedule"),/*#__PURE__*/React.createElement("select",{value:schedule,onChange:e=>setSchedule(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white"},scheduleOptions.map(opt=>/*#__PURE__*/React.createElement("option",{key:opt.value,value:opt.value},opt.label)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('rateLimit')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"number",value:rate,onChange:e=>setRate(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('unlimited'),min:"1"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('comment')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"text",value:comment,onChange:e=>setComment(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('commentPlaceholder')}))),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-3 mt-6"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-gray-300 hover:text-white"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:handleSubmit,disabled:loading||!target,className:"flex items-center gap-2 px-4 py-2 bg-green-600 rounded-lg text-white hover:bg-green-700 disabled:opacity-50"},loading&&/*#__PURE__*/React.createElement(Icons.RotateCw,null),t('create')))));}// Shared form components (defined outside ConfigModal to prevent re-creation) // ns: could use a form library but this is fine for now diff --git a/web/src/tables.js b/web/src/tables.js index 0a9c43f..b28df11 100644 --- a/web/src/tables.js +++ b/web/src/tables.js @@ -484,7 +484,7 @@ > -
+
Score
{ramPercent}% {(() => { const d = histRef.current.mem; const mx = Math.max(...d, 1); const pts = d.map((v,i) => `${(i/19)*40},${12-((v/mx)*12)}`).join(' '); return ; })()} - {metrics.score != null && {t('score')}: {Number(metrics.score).toFixed(1)}} + {metrics.score != null && {t('score')}: {Number(metrics.score).toFixed(1)}} {formatUptime(metrics.uptime)} {isInMaintenance && maintenanceTask && maintenanceTask.status === 'running' && ( diff --git a/web/src/translations.js b/web/src/translations.js index 4a7839f..51dc32b 100644 --- a/web/src/translations.js +++ b/web/src/translations.js @@ -614,6 +614,8 @@ clusterReconfigured: 'Cluster erfolgreich neu konfiguriert', reconfigure: 'Neu konfigurieren', clusterHealth: 'Cluster Gesundheit', + clusterHealthTooltip: 'Gesundheit = 100 − (CPU×30% + RAM×30% + Speicher×20% + Offline-Nodes×20%)\n\n80+: Ausgezeichnet\n60–79: Gut\n40–59: Warnung\n< 40: Kritisch', + nodeScoreTooltip: 'Node-Score = CPU% + RAM% (niedriger ist besser)\n\n< 100: Gut (grün)\n100–150: Erhöht (gelb)\n> 150: Kritisch (rot)', excellent: 'Ausgezeichnet', good: 'Gut', warning: 'Warnung', @@ -621,6 +623,7 @@ nodesOnline: 'Nodes Online', nodeJoinHint: 'Um einen neuen Node hinzuzufügen, führe auf dem neuen Node aus:', avgScore: 'Avg. Score', + avgStorage: 'Avg. Speicher', avgCpu: 'Avg. CPU', avgRam: 'Avg. RAM', @@ -3713,6 +3716,8 @@ clusterReconfigured: 'Cluster re-configured successfully', reconfigure: 'Re-configure', clusterHealth: 'Cluster Health', + clusterHealthTooltip: 'Health = 100 − (CPU×30% + RAM×30% + Storage×20% + Offline Nodes×20%)\n\n80+: Excellent\n60–79: Good\n40–59: Warning\n< 40: Critical', + nodeScoreTooltip: 'Node score = CPU% + RAM% (lower is better)\n\n< 100: Good (green)\n100–150: Elevated (yellow)\n> 150: Critical (red)', excellent: 'Excellent', good: 'Good', warning: 'Warning', @@ -3720,6 +3725,7 @@ nodesOnline: 'Nodes Online', nodeJoinHint: 'To add a new node, run on the new node:', avgScore: 'Avg. Score', + avgStorage: 'Avg. Storage', avgCpu: 'Avg. CPU', avgRam: 'Avg. RAM', @@ -6731,6 +6737,8 @@ clusterReconfigured: 'Cluster reconfiguré avec succès', reconfigure: 'Reconfigurer', clusterHealth: 'Santé du Cluster', + clusterHealthTooltip: 'Santé = 100 − (CPU×30% + RAM×30% + Stockage×20% + Nœuds hors ligne×20%)\n\n80+ : Excellente\n60–79 : Bien\n40–59 : Avertissement\n< 40 : Critique', + nodeScoreTooltip: 'Score nœud = CPU% + RAM% (plus bas est mieux)\n\n< 100 : Bon (vert)\n100–150 : Élevé (jaune)\n> 150 : Critique (rouge)', excellent: 'Excellente', good: 'Bien', warning: 'Avertissement', @@ -6738,6 +6746,7 @@ nodesOnline: 'Nœuds en ligne', nodeJoinHint: 'Pour ajouter un nouveau nœud, exécutez sur le nouveau nœud :', avgScore: 'Moyenne des notes', + avgStorage: 'Moy. Stockage', avgCpu: 'Moyenne CPU', avgRam: 'Moyenne de la RAM', @@ -9604,6 +9613,8 @@ clusterReconfigured: 'Cluster reconfigurado exitosamente', reconfigure: 'Reconfigurar', clusterHealth: 'Salud del cluster', + clusterHealthTooltip: 'Salud = 100 − (CPU×30% + RAM×30% + Almacenamiento×20% + Nodos fuera de línea×20%)\n\n80+: Excelente\n60–79: Buena\n40–59: Advertencia\n< 40: Crítica', + nodeScoreTooltip: 'Puntuación nodo = CPU% + RAM% (menor es mejor)\n\n< 100: Bueno (verde)\n100–150: Elevado (amarillo)\n> 150: Crítico (rojo)', excellent: 'Excelente', good: 'Buena', warning: 'Advertencia', @@ -9611,6 +9622,7 @@ nodesOnline: 'Nodos en línea', nodeJoinHint: 'Para agregar un nuevo nodo, ejecute en el nodo nuevo:', avgScore: 'Puntos promedio', + avgStorage: 'Almac. promedio', avgCpu: 'CPU promedio', avgRam: 'RAM promedio', @@ -12634,6 +12646,8 @@ clusterReconfigured: 'Cluster reconfigurado com sucesso', reconfigure: 'Reconfigurar', clusterHealth: 'Saúde do Cluster', + clusterHealthTooltip: 'Saúde = 100 − (CPU×30% + RAM×30% + Armazenamento×20% + Nós offline×20%)\n\n80+: Excelente\n60–79: Bom\n40–59: Aviso\n< 40: Crítico', + nodeScoreTooltip: 'Pontuação nó = CPU% + RAM% (menor é melhor)\n\n< 100: Bom (verde)\n100–150: Elevado (amarelo)\n> 150: Crítico (vermelho)', excellent: 'Excelente', good: 'Bom', warning: 'Aviso', @@ -12641,6 +12655,7 @@ nodesOnline: 'Nós Online', nodeJoinHint: 'To add a new node, run on the new node:', // Mantido comando original avgScore: 'Pontuação Média', + avgStorage: 'Armaz. Médio', avgCpu: 'CPU Média', avgRam: 'RAM Média', diff --git a/web/src/vm_modals.js b/web/src/vm_modals.js index 34dc2f1..130a6d6 100644 --- a/web/src/vm_modals.js +++ b/web/src/vm_modals.js @@ -3553,7 +3553,7 @@
)} {cluster.connected ? ( -
+
{cluster.healthScore}%
) : ( @@ -4320,7 +4320,7 @@ {/* Health Badge */} {cluster.connected && ( -
+
{cluster.healthScore}%
)} @@ -6153,11 +6153,12 @@ const avgCpu = nodes.reduce((acc, [, m]) => acc + m.cpu_percent, 0) / nodes.length; const avgMem = nodes.reduce((acc, [, m]) => acc + m.mem_percent, 0) / nodes.length; - const avgScore = nodes.reduce((acc, [, m]) => acc + m.score, 0) / nodes.length; + const avgDisk = nodes.reduce((acc, [, m]) => acc + (m.disk_percent || 0), 0) / nodes.length; const onlineNodes = nodes.filter(([, m]) => m.status === 'online' && !m.maintenance_mode).length; const maintenanceNodes = nodes.filter(([, m]) => m.maintenance_mode).length; + const offlineNodes = nodes.length - onlineNodes - maintenanceNodes; - const healthScore = Math.max(0, 100 - (avgScore / 2)); + const healthScore = Math.max(0, 100 - (avgCpu * 0.3 + avgMem * 0.3 + avgDisk * 0.2 + (nodes.length > 0 ? (offlineNodes / nodes.length) * 100 * 0.2 : 0))); const healthLabel = healthScore >= 80 ? t('excellent') : healthScore >= 60 ? t('good') : healthScore >= 40 ? t('warning') : t('critical'); const healthColor = healthScore >= 80 ? '#22c55e' : healthScore >= 60 ? '#84cc16' : healthScore >= 40 ? '#eab308' : '#ef4444'; @@ -6165,7 +6166,7 @@ const corpHealthColor = healthScore >= 80 ? '#60b515' : healthScore >= 60 ? '#60b515' : healthScore >= 40 ? '#efc006' : '#f54f47'; if (isCorporate) { return ( -
+

{t('clusterHealth')}

@@ -6189,7 +6190,7 @@ } return ( -
+

{t('clusterHealth')}

@@ -6222,8 +6223,8 @@
{t('nodesOnline')}
-
{avgScore.toFixed(0)}
-
{t('avgScore')}
+
{avgDisk.toFixed(1)}%
+
{t('avgStorage')}
{avgCpu.toFixed(1)}%
From 66d63de327ed18ca65e6da554e3c0070305b6de9 Mon Sep 17 00:00:00 2001 From: Lukas Alstrup Date: Mon, 6 Apr 2026 22:44:25 +0200 Subject: [PATCH 2/6] fix: guard ClusterHealth against non-node metric entries and missing fields --- web/index.html | 2 +- web/src/vm_modals.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/index.html b/web/index.html index 7d21973..7d66792 100644 --- a/web/index.html +++ b/web/index.html @@ -3277,7 +3277,7 @@ useEffect(()=>{if(!xReplForm.target_cluster||!authFetch)return;let cancelled=false;const fetchTargetResources=async()=>{setXReplLoadingResources(true);setXReplTargetStorages([]);setXReplTargetBridges([]);try{const nodesRes=await authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes`);if(!nodesRes||!nodesRes.ok||cancelled)return;const nodesData=await nodesRes.json();const onlineNode=(Array.isArray(nodesData)?nodesData:nodesData.nodes||[]).find(n=>n.status==='online');if(!onlineNode||cancelled)return;const nodeName=onlineNode.node||onlineNode.name;const[storRes,netRes]=await Promise.all([authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes/${nodeName}/storage`),authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes/${nodeName}/networks`)]);if(cancelled)return;if(storRes&&storRes.ok){const storData=await storRes.json();setXReplTargetStorages((Array.isArray(storData)?storData:storData.storages||[]).filter(s=>s.content&&(s.content.includes('images')||s.content.includes('rootdir'))));}if(netRes&&netRes.ok){const netData=await netRes.json();setXReplTargetBridges((Array.isArray(netData)?netData:netData.networks||[]).filter(n=>n.type==='bridge'||n.type==='OVSBridge'||n.source==='sdn'));}}catch(err){console.error('xrepl target resources:',err);}if(!cancelled)setXReplLoadingResources(false);};fetchTargetResources();return()=>{cancelled=true;};},[xReplForm.target_cluster]);// NS: Cross-cluster replication handlers const handleCreateXRepl=async()=>{try{const res=await authFetch(`${API_URL}/cross-cluster-replications`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({source_cluster:xReplForm.source_cluster,target_cluster:xReplForm.target_cluster,vmid:parseInt(xReplForm.vmid),vm_type:xReplForm.vm_type||'qemu',target_storage:xReplForm.target_storage,target_bridge:xReplForm.target_bridge,schedule:xReplForm.schedule,retention:parseInt(xReplForm.retention)||3})});if(res&&res.ok){if(addToast)addToast(t('xReplCreated')||'Replication job created','success');setShowCreateXRepl(false);setXReplForm({source_cluster:'',vmid:'',vm_type:'qemu',target_cluster:'',target_storage:'',target_bridge:'vmbr0',schedule:'0 */6 * * *',retention:3});await fetchXReplJobs();}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplCreateFailed')||'Failed to create replication job','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleDeleteXRepl=async jobId=>{if(!confirm(t('confirmDeleteXRepl')||'Delete this replication job?'))return;try{const res=await authFetch(`${API_URL}/cross-cluster-replications/${jobId}`,{method:'DELETE'});if(res&&res.ok){if(addToast)addToast(t('xReplDeleted')||'Replication job deleted','success');await fetchXReplJobs();}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplDeleteFailed')||'Failed to delete','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleRunXReplNow=async jobId=>{try{const res=await authFetch(`${API_URL}/cross-cluster-replications/${jobId}/run`,{method:'POST'});if(res&&res.ok){if(addToast)addToast(t('xReplStarted')||'Replication started','success');}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplStartFailed')||'Failed to start','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleSave=async()=>{if(!form.name.trim())return;setSaving(true);await onSave(form);setSaving(false);};// MK: tab config const tabs=[{id:'general',label:t('general')||'General',icon:Icons.Settings},{id:'lb',label:t('crossClusterLB')||'Cross-Cluster LB',icon:Icons.Scale},{id:'replication',label:t('crossClusterReplication')||'Replication',icon:Icons.Globe},{id:'info',label:t('info')||'Info',icon:Icons.Info}];return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4"},/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl w-full max-w-2xl max-h-[80vh] overflow-y-auto"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-5 border-b border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"w-10 h-10 rounded-lg flex items-center justify-center",style:{backgroundColor:(form.color||'#E86F2D')+'33'}},/*#__PURE__*/React.createElement(Icons.Folder,{className:"w-5 h-5",style:{color:form.color||'#E86F2D'}})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-semibold text-white"},t('groupSettings')||'Group Settings'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},group.name))),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-gray-400 hover:text-white transition-colors"},/*#__PURE__*/React.createElement(Icons.X,null))),/*#__PURE__*/React.createElement("div",{className:"flex border-b border-proxmox-border"},tabs.map(tab=>/*#__PURE__*/React.createElement("button",{key:tab.id,onClick:()=>setActiveTab(tab.id),className:`flex items-center gap-2 px-4 py-2.5 text-sm font-medium transition-colors whitespace-nowrap ${activeTab===tab.id?'text-proxmox-orange border-b-2 border-proxmox-orange bg-proxmox-dark/50':'text-gray-400 hover:text-white hover:bg-proxmox-dark/30'}`},/*#__PURE__*/React.createElement(tab.icon,{className:"w-4 h-4"}),tab.label))),/*#__PURE__*/React.createElement("div",{className:"p-5"},activeTab==='general'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('name')||'Name'," *"),/*#__PURE__*/React.createElement("input",{value:form.name,onChange:e=>setForm(p=>({...p,name:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange",placeholder:"Production Cluster Group"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('description')||'Description'),/*#__PURE__*/React.createElement("textarea",{value:form.description,onChange:e=>setForm(p=>({...p,description:e.target.value})),rows:3,className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange resize-none",placeholder:"Optional description for this group..."})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('color')||'Color'),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("input",{type:"color",value:form.color,onChange:e=>setForm(p=>({...p,color:e.target.value})),className:"w-10 h-10 rounded cursor-pointer border border-proxmox-border"}),/*#__PURE__*/React.createElement("input",{type:"text",value:form.color,onChange:e=>setForm(p=>({...p,color:e.target.value})),className:"w-28 px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm font-mono focus:outline-none focus:border-proxmox-orange",placeholder:"#E86F2D"}),/*#__PURE__*/React.createElement("div",{className:"flex gap-1.5"},['#E86F2D','#3B82F6','#22C55E','#EAB308','#8B5CF6','#EC4899'].map(c=>/*#__PURE__*/React.createElement("button",{key:c,onClick:()=>setForm(p=>({...p,color:c})),className:`w-6 h-6 rounded-full border-2 transition-all ${form.color===c?'border-white scale-110':'border-transparent hover:border-gray-500'}`,style:{backgroundColor:c}})))))),activeTab==='lb'&&/*#__PURE__*/React.createElement("div",{className:"space-y-5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white"},t('enableCrossClusterLB')||'Enable Cross-Cluster Load Balancing'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('lbDescription')||'Automatically migrate VMs between clusters when resource thresholds are exceeded')),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_lb_enabled,onChange:e=>setForm(p=>({...p,cross_cluster_lb_enabled:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-proxmox-orange rounded-full peer-focus:ring-2 peer-focus:ring-proxmox-orange/50 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),form.cross_cluster_lb_enabled&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex justify-end"},/*#__PURE__*/React.createElement("button",{onClick:handleXclbBalanceNow,disabled:xclbRunning,className:"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg bg-proxmox-orange/20 text-proxmox-orange hover:bg-proxmox-orange/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"},xclbRunning?React.createElement('span',{className:'w-3.5 h-3.5 border-2 border-proxmox-orange/40 border-t-proxmox-orange rounded-full animate-spin'}):React.createElement(Icons.RefreshCw,{className:'w-3.5 h-3.5'}),t('balanceNow')||'Balance Now')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-3 bg-yellow-500/5 border border-yellow-500/20 rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-4 h-4 text-yellow-400"}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"text-sm text-yellow-300"},t('dryRunMode')||'Dry Run / Simulation Mode'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},t('dryRunDesc')||'Log what would happen without actually migrating'))),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_dry_run,onChange:e=>setForm(p=>({...p,cross_cluster_dry_run:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-yellow-500 rounded-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),/*#__PURE__*/React.createElement(Slider,{label:t('cpuThreshold')||'CPU Threshold (%)',description:t('crossClusterThresholdDesc')||'CPU threshold for cluster imbalance (10-80%)',value:form.cross_cluster_threshold,onChange:v=>setForm(p=>({...p,cross_cluster_threshold:v})),min:10,max:80}),/*#__PURE__*/React.createElement(Slider,{label:t('checkInterval')||'Check Interval',description:t('crossClusterIntervalDesc')||'Time between check cycles',value:form.cross_cluster_interval,onChange:v=>setForm(p=>({...p,cross_cluster_interval:v})),min:300,max:3600,step:60,unit:"s"}),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetStorage')||'Target Storage'),loadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loadingCrossClusterResources')||'Loading...'):commonStorages.length>0?/*#__PURE__*/React.createElement("select",{value:form.cross_cluster_target_storage,onChange:e=>setForm(p=>({...p,cross_cluster_target_storage:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange"},/*#__PURE__*/React.createElement("option",{value:""},t('selectStorage')||'Select storage...'),commonStorages.map(s=>/*#__PURE__*/React.createElement("option",{key:s.storage,value:s.storage},s.storage," (",s.type,")"))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-yellow-400 py-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3 inline mr-1"}),t('noCommonStorages')||'No common storage found across all clusters'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-1"},t('commonStorageHint')||'Only storages available on all clusters')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetBridge')||'Target Bridge'),loadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loadingCrossClusterResources')||'Loading...'):commonBridges.length>0?/*#__PURE__*/React.createElement("select",{value:form.cross_cluster_target_bridge,onChange:e=>setForm(p=>({...p,cross_cluster_target_bridge:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange"},commonBridges.filter(b=>b.source!=='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"Local Bridges"},commonBridges.filter(b=>b.source!=='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface,b.comments?` - ${b.comments}`:''))),commonBridges.filter(b=>b.source==='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"SDN VNets"},commonBridges.filter(b=>b.source==='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface," - ",b.zone||'SDN',b.alias?` (${b.alias})`:'')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-yellow-400 py-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3 inline mr-1"}),t('noCommonBridges')||'No common bridge found across all clusters'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-1"},t('commonBridgeHint')||'Only bridges available on all clusters'))),/*#__PURE__*/React.createElement(Slider,{label:t('maxMigrations')||'Max Migrations per Cycle',description:t('crossClusterMaxMigrationsDesc')||'Max migrations per check cycle',value:form.cross_cluster_max_migrations,onChange:v=>setForm(p=>({...p,cross_cluster_max_migrations:v})),min:1,max:5,step:1,unit:""}),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-3 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"text-sm text-white"},t('includeContainers')||'Include Containers'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('includeContainersDesc')||'Include containers (LXC) in cross-cluster balancing'),form.cross_cluster_include_containers&&/*#__PURE__*/React.createElement("p",{className:"text-xs text-yellow-400 mt-1 flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3"}),t('containerMigrationWarning')||'Containers are restarted during migration (downtime)')),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer ml-4"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_include_containers,onChange:e=>setForm(p=>({...p,cross_cluster_include_containers:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-proxmox-orange rounded-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-1"},t('excludedVMsCrossCluster')||'Excluded VMs/Containers'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mb-3"},t('excludedVMsCrossClusterDesc')||'VMs and containers excluded from automatic cross-cluster balancing'),loadingExcludedVMs?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loading'),"..."):groupClusters.filter(c=>c.connected).length===0?/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-600 py-2"},t('noExcludedVMsInGroup')||'No VMs excluded'):/*#__PURE__*/React.createElement("div",{className:"space-y-2"},groupClusters.filter(c=>c.connected).map(cluster=>{const excluded=excludedVMsByCluster[cluster.id]||[];const allVMs=allVMsByCluster[cluster.id]||[];const excludedIds=excluded.map(v=>v.vmid);const available=allVMs.filter(vm=>!excludedIds.includes(vm.vmid));const isExpanded=expandedClusters[cluster.id];return/*#__PURE__*/React.createElement("div",{key:cluster.id,className:"border border-proxmox-border rounded-lg overflow-hidden"},/*#__PURE__*/React.createElement("button",{onClick:()=>setExpandedClusters(prev=>({...prev,[cluster.id]:!prev[cluster.id]})),className:"w-full flex items-center justify-between p-2.5 bg-proxmox-dark/50 hover:bg-proxmox-dark text-left"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Server,{className:"w-3.5 h-3.5 text-proxmox-orange"}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white"},cluster.name),excluded.length>0&&/*#__PURE__*/React.createElement("span",{className:"text-xs bg-red-500/20 text-red-400 px-1.5 py-0.5 rounded"},excluded.length)),/*#__PURE__*/React.createElement(Icons.ChevronDown,{className:`w-4 h-4 text-gray-500 transition-transform ${isExpanded?'rotate-180':''}`})),isExpanded&&/*#__PURE__*/React.createElement("div",{className:"p-2.5 space-y-2 border-t border-proxmox-border"},excluded.length>0?/*#__PURE__*/React.createElement("div",{className:"space-y-1"},excluded.map(vm=>/*#__PURE__*/React.createElement("div",{key:vm.vmid,className:"flex items-center justify-between bg-proxmox-dark rounded px-2.5 py-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Monitor,{className:"w-3.5 h-3.5 text-red-400"}),/*#__PURE__*/React.createElement("span",{className:"text-sm"},vm.name||`VM ${vm.vmid}`),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},"(",vm.vmid,")")),/*#__PURE__*/React.createElement("button",{onClick:()=>includeVM(cluster.id,vm.vmid),className:"text-xs text-green-400 hover:text-green-300"},t('include')||'Include')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-600 p-2"},t('noExcludedVMsInGroup')||'No VMs excluded'),available.length>0&&/*#__PURE__*/React.createElement("div",{className:"flex gap-2 mt-1"},/*#__PURE__*/React.createElement("select",{id:`xclb-exclude-${cluster.id}`,className:"flex-1 bg-proxmox-dark border border-proxmox-border rounded px-2 py-1.5 text-sm",defaultValue:""},/*#__PURE__*/React.createElement("option",{value:"",disabled:true},t('selectVMToExclude')||'Select VM to exclude...'),available.map(vm=>/*#__PURE__*/React.createElement("option",{key:vm.vmid,value:vm.vmid},vm.name||`VM ${vm.vmid}`," (",vm.vmid,") - ",vm.node))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const select=document.getElementById(`xclb-exclude-${cluster.id}`);const vmid=parseInt(select?.value);if(!vmid)return;const vm=available.find(v=>v.vmid===vmid);excludeVM(cluster.id,vmid,vm?.name);select.value='';},className:"px-2.5 py-1.5 bg-red-500/20 text-red-400 rounded hover:bg-red-500/30 text-xs flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.Ban,{className:"w-3.5 h-3.5"}),t('exclude')||'Exclude'))));}))))),activeTab==='replication'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Globe,{className:"w-4 h-4 text-proxmox-orange"}),t('crossClusterReplication')||'Cross-Cluster Replication'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('crossClusterReplicationDesc')||'Replicate VM snapshots to another cluster (DR)')),groupClusters.length>=2&&/*#__PURE__*/React.createElement("button",{onClick:()=>setShowCreateXRepl(true),className:"flex items-center gap-1.5 px-3 py-1.5 bg-proxmox-orange/10 text-proxmox-orange rounded-lg text-xs hover:bg-proxmox-orange/20 transition-colors"},/*#__PURE__*/React.createElement(Icons.Plus,{className:"w-3.5 h-3.5"}),t('addDrJob')||'Add DR Job')),groupClusters.length<2?/*#__PURE__*/React.createElement("div",{className:"text-center py-8 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-8 h-8 mx-auto mb-2 text-gray-600"}),t('needTwoClusters')||'At least 2 clusters needed for cross-cluster replication'):xReplLoading?/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 py-8 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loading'),"..."):xReplJobs.length===0&&!showCreateXRepl?/*#__PURE__*/React.createElement("div",{className:"text-center py-8 text-gray-500 text-sm"},t('noReplicationJobs')||'No replication jobs configured'):/*#__PURE__*/React.createElement("div",{className:"space-y-2"},xReplJobs.map(job=>{const srcCluster=groupClusters.find(c=>c.id===job.source_cluster);const tgtCluster=groupClusters.find(c=>c.id===job.target_cluster);return/*#__PURE__*/React.createElement("div",{key:job.id,className:"bg-proxmox-dark rounded-lg p-3 flex items-center justify-between border border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex-1 min-w-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-wrap"},/*#__PURE__*/React.createElement("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${job.enabled?'bg-green-500':'bg-gray-500'}`}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white truncate"},srcCluster?.name||job.source_cluster),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3.5 h-3.5 text-gray-500 flex-shrink-0"}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white truncate"},tgtCluster?.name||job.target_cluster),/*#__PURE__*/React.createElement("span",{className:"text-xs bg-proxmox-dark px-1.5 py-0.5 rounded text-gray-400 border border-proxmox-border"},job.vm_type==='lxc'?'CT':'VM'," ",job.vmid)),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 mt-1 flex items-center gap-2 flex-wrap"},/*#__PURE__*/React.createElement("span",null,job.schedule),/*#__PURE__*/React.createElement("span",null,"\xB7"),/*#__PURE__*/React.createElement("span",null,job.target_storage||'default'),job.last_run&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("span",null,"\xB7"),/*#__PURE__*/React.createElement("span",null,t('lastRunPrefix')||'Last',": ",new Date(job.last_run).toLocaleString())),job.last_status&&/*#__PURE__*/React.createElement("span",{className:job.last_status==='OK'?'text-green-400':'text-red-400'},job.last_status),job.last_error&&/*#__PURE__*/React.createElement("span",{className:"text-red-400"},job.last_error))),/*#__PURE__*/React.createElement("div",{className:"flex gap-1 flex-shrink-0 ml-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>handleRunXReplNow(job.id),className:"p-1.5 rounded hover:bg-green-500/10 text-gray-400 hover:text-green-400",title:t('runNow')||'Run now'},/*#__PURE__*/React.createElement(Icons.Play,{className:"w-3.5 h-3.5"})),/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteXRepl(job.id),className:"p-1.5 rounded hover:bg-red-500/10 text-gray-400 hover:text-red-400",title:t('delete')||'Delete'},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-3.5 h-3.5"}))));})),showCreateXRepl&&groupClusters.length>=2&&/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark border border-proxmox-border rounded-lg p-4"},/*#__PURE__*/React.createElement("h5",{className:"text-sm font-medium text-white mb-3"},t('newCrossClusterReplication')||'New Cross-Cluster Replication'),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('sourceCluster')||'Source Cluster'),/*#__PURE__*/React.createElement("select",{value:xReplForm.source_cluster,onChange:e=>setXReplForm(f=>({...f,source_cluster:e.target.value,vmid:'',vm_type:'qemu'})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectCluster')||'Select cluster...'),groupClusters.filter(c=>c.connected).map(c=>/*#__PURE__*/React.createElement("option",{key:c.id,value:c.id},c.name)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},"VM"),xReplLoadingVMs?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.source_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.vmid,onChange:e=>{const vmid=e.target.value;const vm=xReplSourceVMs.find(v=>String(v.vmid)===vmid);setXReplForm(f=>({...f,vmid,vm_type:vm?.type||'qemu'}));},className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectVM')||'Select VM...'),xReplSourceVMs.map(vm=>/*#__PURE__*/React.createElement("option",{key:vm.vmid,value:vm.vmid},vm.name||`VM ${vm.vmid}`," (",vm.vmid,") - ",vm.type==='lxc'?'CT':'VM'))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a source cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetCluster')||'Target Cluster'),/*#__PURE__*/React.createElement("select",{value:xReplForm.target_cluster,onChange:e=>setXReplForm(f=>({...f,target_cluster:e.target.value,target_storage:'',target_bridge:'vmbr0'})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectCluster')||'Select cluster...'),groupClusters.filter(c=>c.connected&&c.id!==xReplForm.source_cluster).map(c=>/*#__PURE__*/React.createElement("option",{key:c.id,value:c.id},c.name)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetStorage')||'Target Storage'),xReplLoadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.target_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.target_storage,onChange:e=>setXReplForm(f=>({...f,target_storage:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectStorage')||'Select storage...'),xReplTargetStorages.map(s=>/*#__PURE__*/React.createElement("option",{key:s.storage,value:s.storage},s.storage," (",s.type,")"))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a target cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetBridge')||'Target Bridge'),xReplLoadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.target_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.target_bridge,onChange:e=>setXReplForm(f=>({...f,target_bridge:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},xReplTargetBridges.filter(b=>b.source!=='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"Local Bridges"},xReplTargetBridges.filter(b=>b.source!=='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface,b.comments?` - ${b.comments}`:''))),xReplTargetBridges.filter(b=>b.source==='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"SDN VNets"},xReplTargetBridges.filter(b=>b.source==='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface," - ",b.zone||'SDN',b.alias?` (${b.alias})`:'')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a target cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('scheduleCron')||'Schedule (Cron)'),/*#__PURE__*/React.createElement("input",{type:"text",value:xReplForm.schedule,onChange:e=>setXReplForm(f=>({...f,schedule:e.target.value})),placeholder:"0 */6 * * *",className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('replicationRetention')||'Retention'),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",max:"30",value:xReplForm.retention,onChange:e=>setXReplForm(f=>({...f,retention:parseInt(e.target.value)||1})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"}))),/*#__PURE__*/React.createElement("div",{className:"flex gap-2 mt-3"},/*#__PURE__*/React.createElement("button",{onClick:handleCreateXRepl,disabled:!xReplForm.source_cluster||!xReplForm.vmid||!xReplForm.target_cluster,className:"px-3 py-1.5 bg-proxmox-orange text-white rounded-lg text-sm hover:bg-proxmox-orange/90 transition-colors disabled:opacity-50"},t('create')||'Create'),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowCreateXRepl(false),className:"px-3 py-1.5 bg-proxmox-dark border border-proxmox-border text-gray-300 rounded-lg text-sm hover:bg-proxmox-darker transition-colors"},t('cancel')||'Cancel'))),Object.keys(nativeReplByCluster).length>0&&/*#__PURE__*/React.createElement("div",{className:"mt-6"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mb-3"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 text-purple-400"}),/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white"},t('nativeProxmoxReplication')||'Native Proxmox Replication (ZFS)')),/*#__PURE__*/React.createElement("div",{className:"space-y-3"},Object.entries(nativeReplByCluster).map(([cid,jobs])=>{const cluster=groupClusters.find(c=>c.id===cid);return/*#__PURE__*/React.createElement("div",{key:cid,className:"bg-proxmox-dark rounded-lg border border-proxmox-border overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"px-3 py-2 border-b border-proxmox-border bg-purple-500/5"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-medium text-purple-300"},cluster?.name||cid)),jobs.map((job,idx)=>{const hasErr=job.fail_count>0||job.error;const lastSync=job.last_sync?new Date(job.last_sync*1000).toLocaleString():'-';return/*#__PURE__*/React.createElement("div",{key:job.id||idx,className:"px-3 py-2 flex items-center justify-between border-b border-proxmox-border/50 last:border-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-wrap min-w-0"},/*#__PURE__*/React.createElement("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${job.disable?'bg-gray-500':hasErr?'bg-red-500':'bg-green-500'}`}),/*#__PURE__*/React.createElement("span",{className:"text-xs bg-proxmox-dark px-1.5 py-0.5 rounded text-gray-400 border border-proxmox-border"},"VM ",job.guest),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},job.source||'?'),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3 h-3 text-gray-600 flex-shrink-0"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},job.target),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600 font-mono"},job.schedule)),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-shrink-0 ml-2"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600"},lastSync),job.duration!=null&&/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600 font-mono"},job.duration.toFixed(1),"s")));}));})))),activeTab==='info'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-3"},t('groupInfo')||'Group Information'),/*#__PURE__*/React.createElement("div",{className:"space-y-2 text-sm"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('groupId')||'Group ID'),/*#__PURE__*/React.createElement("span",{className:"text-white font-mono text-xs"},group.id)),/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('created')||'Created'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.created?new Date(group.created).toLocaleString():'-')),group.tenant_id&&/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('tenant')||'Tenant'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.tenant_id)))),group.cross_cluster_lb_enabled&&/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-3"},t('lbStatus')||'Load Balancing Status'),/*#__PURE__*/React.createElement("div",{className:"space-y-2 text-sm"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('lastRun')||'Last LB Run'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.last_lb_run?new Date(group.last_lb_run).toLocaleString():t('neverRun')||'Never run')),/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('mode')||'Mode'),/*#__PURE__*/React.createElement("span",{className:group.cross_cluster_dry_run?'text-yellow-400':'text-green-400'},group.cross_cluster_dry_run?t('simulation')||'Simulation':t('active')||'Active')))),/*#__PURE__*/React.createElement("div",{className:"p-4 bg-blue-500/5 border border-blue-500/20 rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-start gap-3"},/*#__PURE__*/React.createElement(Icons.Info,{className:"w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5"}),/*#__PURE__*/React.createElement("div",{className:"text-sm text-gray-400"},/*#__PURE__*/React.createElement("p",{className:"mb-2"},t('lbExplanation')||'Cross-Cluster Load Balancing monitors CPU and RAM usage across all clusters in this group. When a cluster exceeds the configured threshold, VMs are automatically migrated to a less loaded cluster.'),/*#__PURE__*/React.createElement("p",null,t('lbDryRunExplanation')||'Enable Dry Run mode first to review what actions would be taken before enabling live migrations.')))))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-end gap-3 p-5 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-sm text-gray-400 hover:text-white transition-colors"},t('cancel')||'Cancel'),/*#__PURE__*/React.createElement("button",{onClick:handleSave,disabled:saving||!form.name.trim(),className:"px-5 py-2 bg-proxmox-orange hover:bg-orange-600 disabled:opacity-50 rounded-lg text-sm font-medium text-white transition-colors flex items-center gap-2"},saving?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"})," ",t('saving')||'Saving...'):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement(Icons.Save,{className:"w-4 h-4"})," ",t('save')||'Save')))));}// Cluster Health Widget -function ClusterHealth({metrics,isCorporate}){const{t}=useTranslation();const nodes=Object.entries(metrics);if(nodes.length===0)return null;const avgCpu=nodes.reduce((acc,[,m])=>acc+m.cpu_percent,0)/nodes.length;const avgMem=nodes.reduce((acc,[,m])=>acc+m.mem_percent,0)/nodes.length;const avgDisk=nodes.reduce((acc,[,m])=>acc+(m.disk_percent||0),0)/nodes.length;const onlineNodes=nodes.filter(([,m])=>m.status==='online'&&!m.maintenance_mode).length;const maintenanceNodes=nodes.filter(([,m])=>m.maintenance_mode).length;const offlineNodes=nodes.length-onlineNodes-maintenanceNodes;const healthScore=Math.max(0,100-(avgCpu*0.3+avgMem*0.3+avgDisk*0.2+(nodes.length>0?offlineNodes/nodes.length*100*0.2:0)));const healthLabel=healthScore>=80?t('excellent'):healthScore>=60?t('good'):healthScore>=40?t('warning'):t('critical');const healthColor=healthScore>=80?'#22c55e':healthScore>=60?'#84cc16':healthScore>=40?'#eab308':'#ef4444';// LW: Feb 2026 - corporate compact variant (Clarity dark theme) +function ClusterHealth({metrics,isCorporate}){const{t}=useTranslation();const nodes=Object.entries(metrics).filter(([k,m])=>m&&typeof m==='object'&&k!=='error'&&k!=='offline');if(nodes.length===0)return null;const avgCpu=nodes.reduce((acc,[,m])=>acc+(m.cpu_percent??0),0)/nodes.length;const avgMem=nodes.reduce((acc,[,m])=>acc+(m.mem_percent??0),0)/nodes.length;const avgDisk=nodes.reduce((acc,[,m])=>acc+(m.disk_percent??0),0)/nodes.length;const onlineNodes=nodes.filter(([,m])=>m.status==='online'&&!m.maintenance_mode).length;const maintenanceNodes=nodes.filter(([,m])=>m.maintenance_mode).length;const offlineNodes=nodes.length-onlineNodes-maintenanceNodes;const healthScore=Math.max(0,100-(avgCpu*0.3+avgMem*0.3+avgDisk*0.2+(nodes.length>0?offlineNodes/nodes.length*100*0.2:0)));const healthLabel=healthScore>=80?t('excellent'):healthScore>=60?t('good'):healthScore>=40?t('warning'):t('critical');const healthColor=healthScore>=80?'#22c55e':healthScore>=60?'#84cc16':healthScore>=40?'#eab308':'#ef4444';// LW: Feb 2026 - corporate compact variant (Clarity dark theme) const corpHealthColor=healthScore>=80?'#60b515':healthScore>=60?'#60b515':healthScore>=40?'#efc006':'#f54f47';if(isCorporate){return/*#__PURE__*/React.createElement("div",{className:"p-3",style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)'},title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-[11px] font-semibold uppercase tracking-wider mb-2",style:{color:'var(--corp-text-muted)'}},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4 flex-wrap"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-20 h-2 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${healthScore}%`,backgroundColor:corpHealthColor,borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-medium",style:{color:'var(--color-text)'}},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-[11px]",style:{color:'var(--corp-text-muted)'}},healthLabel)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},t('nodesOnline'),": ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},onlineNodes,"/",nodes.length)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"CPU: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},avgCpu.toFixed(1),"%")),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"RAM: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},avgMem.toFixed(1),"%")),maintenanceNodes>0&&/*#__PURE__*/React.createElement("span",{className:"text-[12px] flex items-center gap-1",style:{color:'var(--color-warning)'}},/*#__PURE__*/React.createElement(Icons.Wrench,{className:"w-3 h-3"})," ",maintenanceNodes," ",t('maintenance'))));}return/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl p-5",title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4"},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center mb-6"},/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement("svg",{viewBox:"0 0 100 100",className:"w-32 h-32"},/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#30363D",strokeWidth:"8"}),/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:healthColor,strokeWidth:"8",strokeLinecap:"round",strokeDasharray:`${healthScore*2.83} 283`,transform:"rotate(-90 50 50)",className:"transition-all duration-1000"})),/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 flex flex-col items-center justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-2xl font-bold text-white"},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-400"},healthLabel)))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},onlineNodes,"/",nodes.length),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('nodesOnline'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgDisk.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgStorage'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgCpu.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgCpu'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgMem.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgRam')))),maintenanceNodes>0&&/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-4 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 text-yellow-400"},/*#__PURE__*/React.createElement(Icons.Wrench,null),/*#__PURE__*/React.createElement("span",{className:"text-sm font-medium"},maintenanceNodes," Node(s) ",t('maintenance')))));}// Create Snapshot Modal - NS: Feb 2026 enhanced with efficient mode function CreateSnapshotModal({isQemu,onSubmit,onClose,loading,efficientInfo}){const{t}=useTranslation();const[snapname,setSnapname]=useState(`snap_${Date.now()}`);const[description,setDescription]=useState('');const[vmstate,setVmstate]=useState(false);const[mode,setMode]=useState('standard');const[snapSizeGb,setSnapSizeGb]=useState(efficientInfo?.recommended_snap_size_gb||10);const isEfficient=mode==='efficient';const canEfficient=efficientInfo?.eligible;const handleSubmit=()=>{if(!snapname.trim())return;onSubmit(snapname.trim(),description,vmstate,isEfficient?{mode:'efficient',snap_size_gb:snapSizeGb}:{mode:'standard'});};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:`w-full ${canEfficient?'max-w-lg':'max-w-md'} bg-proxmox-card border border-proxmox-border rounded-xl p-6 animate-scale-in`},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold text-white mb-4"},t('createSnapshot')),/*#__PURE__*/React.createElement("div",{className:"space-y-4"},canEfficient&&/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-2"},t('snapshotMode')),/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setMode('standard'),className:`flex-1 px-3 py-2 rounded-lg text-sm border transition-colors ${!isEfficient?'bg-blue-600/20 border-blue-500 text-blue-400':'bg-proxmox-dark border-proxmox-border text-gray-400 hover:border-gray-500'}`},t('normalMode')),/*#__PURE__*/React.createElement("button",{onClick:()=>setMode('efficient'),className:`flex-1 px-3 py-2 rounded-lg text-sm border transition-colors ${isEfficient?'bg-green-600/20 border-green-500 text-green-400':'bg-proxmox-dark border-proxmox-border text-gray-400 hover:border-gray-500'}`},/*#__PURE__*/React.createElement("span",{className:"flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement(Icons.Zap,null),t('efficientMode'))))),isEfficient&&efficientInfo&&/*#__PURE__*/React.createElement("div",{className:"p-3 bg-proxmox-dark rounded-lg border border-green-500/30 space-y-3"},/*#__PURE__*/React.createElement("div",{className:"text-sm font-medium text-green-400 flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.Zap,null),t('spaceSavings'),": ",efficientInfo.savings_percent,"%"),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs text-gray-400 mb-1"},/*#__PURE__*/React.createElement("span",null,t('normalSnapshotSize')),/*#__PURE__*/React.createElement("span",null,efficientInfo.total_disk_size_gb?.toFixed(1)," GB")),/*#__PURE__*/React.createElement("div",{className:"w-full h-3 bg-gray-700 rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"h-full bg-red-500 rounded-full",style:{width:'100%'}}))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs text-gray-400 mb-1"},/*#__PURE__*/React.createElement("span",null,t('efficientSnapshotSize')),/*#__PURE__*/React.createElement("span",null,"~",snapSizeGb?.toFixed(1)," GB")),/*#__PURE__*/React.createElement("div",{className:"w-full h-3 bg-gray-700 rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"h-full bg-green-500 rounded-full",style:{width:`${Math.max(3,snapSizeGb/efficientInfo.total_disk_size_gb*100)}%`}}))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('snapshotSizeGb')," (",t('recommended'),": ",efficientInfo.recommended_snap_size_gb?.toFixed(1)," GB)"),/*#__PURE__*/React.createElement("input",{type:"number",value:snapSizeGb,onChange:e=>setSnapSizeGb(parseFloat(e.target.value)||1),min:"1",max:efficientInfo.vg_free_gb-2,step:"1",className:"w-full px-3 py-1.5 bg-proxmox-card border border-proxmox-border rounded-lg text-white text-sm"})),/*#__PURE__*/React.createElement("div",{className:`text-xs flex items-center gap-1 ${efficientInfo.has_guest_agent?'text-green-400':'text-yellow-400'}`},efficientInfo.has_guest_agent?/*#__PURE__*/React.createElement(Icons.CheckCircle,null):/*#__PURE__*/React.createElement(Icons.AlertTriangle,null),efficientInfo.has_guest_agent?t('guestAgentDetected'):t('noGuestAgent')),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 italic"},t('managedByPegaprox'))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('name')),/*#__PURE__*/React.createElement("input",{type:"text",value:snapname,onChange:e=>setSnapname(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:"snapshot-name"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('description')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"text",value:description,onChange:e=>setDescription(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('snapshotDescription')})),isQemu&&!isEfficient&&/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-2 text-sm text-gray-300"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:vmstate,onChange:e=>setVmstate(e.target.checked),className:"rounded"}),t('saveRamState'))),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-3 mt-6"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-gray-300 hover:text-white"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:handleSubmit,disabled:loading||!snapname.trim(),className:"flex items-center gap-2 px-4 py-2 rounded-lg text-white disabled:opacity-50 bg-green-600 hover:bg-green-700"},loading&&/*#__PURE__*/React.createElement(Icons.RotateCw,null),isEfficient&&/*#__PURE__*/React.createElement(Icons.Zap,null),t('create')))));}// Create Replication Modal function CreateReplicationModal({nodes,onSubmit,onClose,loading}){const{t}=useTranslation();const[target,setTarget]=useState(nodes[0]||'');const[schedule,setSchedule]=useState('*/15');const[rate,setRate]=useState('');const[comment,setComment]=useState('');const scheduleOptions=[{value:'*/5',label:t('every5min')},{value:'*/15',label:t('every15min')},{value:'*/30',label:t('every30min')},{value:'0 *',label:t('hourly')},{value:'0 */2',label:t('every2hours')},{value:'0 */4',label:t('every4hours')},{value:'0 */6',label:t('every6hours')},{value:'0 */12',label:t('every12hours')},{value:'0 0',label:t('daily')}];const handleSubmit=()=>{if(!target)return;onSubmit(target,schedule,rate?parseInt(rate):null,comment);};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-md bg-proxmox-card border border-proxmox-border rounded-xl p-6 animate-scale-in"},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold text-white mb-4"},t('createReplicationJob')),/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetNode')),/*#__PURE__*/React.createElement("select",{value:target,onChange:e=>setTarget(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white"},nodes.map(n=>/*#__PURE__*/React.createElement("option",{key:n,value:n},n)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},"Schedule"),/*#__PURE__*/React.createElement("select",{value:schedule,onChange:e=>setSchedule(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white"},scheduleOptions.map(opt=>/*#__PURE__*/React.createElement("option",{key:opt.value,value:opt.value},opt.label)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('rateLimit')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"number",value:rate,onChange:e=>setRate(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('unlimited'),min:"1"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('comment')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"text",value:comment,onChange:e=>setComment(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('commentPlaceholder')}))),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-3 mt-6"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-gray-300 hover:text-white"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:handleSubmit,disabled:loading||!target,className:"flex items-center gap-2 px-4 py-2 bg-green-600 rounded-lg text-white hover:bg-green-700 disabled:opacity-50"},loading&&/*#__PURE__*/React.createElement(Icons.RotateCw,null),t('create')))));}// Shared form components (defined outside ConfigModal to prevent re-creation) diff --git a/web/src/vm_modals.js b/web/src/vm_modals.js index 130a6d6..b551b2a 100644 --- a/web/src/vm_modals.js +++ b/web/src/vm_modals.js @@ -6148,12 +6148,12 @@ // Cluster Health Widget function ClusterHealth({ metrics, isCorporate }) { const { t } = useTranslation(); - const nodes = Object.entries(metrics); + const nodes = Object.entries(metrics).filter(([k, m]) => m && typeof m === 'object' && k !== 'error' && k !== 'offline'); if (nodes.length === 0) return null; - const avgCpu = nodes.reduce((acc, [, m]) => acc + m.cpu_percent, 0) / nodes.length; - const avgMem = nodes.reduce((acc, [, m]) => acc + m.mem_percent, 0) / nodes.length; - const avgDisk = nodes.reduce((acc, [, m]) => acc + (m.disk_percent || 0), 0) / nodes.length; + const avgCpu = nodes.reduce((acc, [, m]) => acc + (m.cpu_percent ?? 0), 0) / nodes.length; + const avgMem = nodes.reduce((acc, [, m]) => acc + (m.mem_percent ?? 0), 0) / nodes.length; + const avgDisk = nodes.reduce((acc, [, m]) => acc + (m.disk_percent ?? 0), 0) / nodes.length; const onlineNodes = nodes.filter(([, m]) => m.status === 'online' && !m.maintenance_mode).length; const maintenanceNodes = nodes.filter(([, m]) => m.maintenance_mode).length; const offlineNodes = nodes.length - onlineNodes - maintenanceNodes; From 38bc94babd01ce93727d942646b5f22910049846 Mon Sep 17 00:00:00 2001 From: Lukas Alstrup Date: Mon, 6 Apr 2026 22:50:49 +0200 Subject: [PATCH 3/6] fix: skip missing disk stats in health calculation instead of treating as 0% --- web/index.html | 15 ++++++++------- web/src/translations.js | 10 +++++----- web/src/vm_modals.js | 11 ++++++++--- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/web/index.html b/web/index.html index 7d66792..e41d9f9 100644 --- a/web/index.html +++ b/web/index.html @@ -2048,7 +2048,7 @@ poolPermissions:'Pool-Berechtigungen',poolPermissionsDesc:'Gewähren Sie Benutzern oder Gruppen Zugriff auf Proxmox Resource Pools. Berechtigungen gelten für alle VMs im Pool.',managePools:'Pools verwalten',poolManagerDesc:'Erstellen, bearbeiten und löschen Sie Resource Pools. Weisen Sie VMs zu Pools für organisierte Berechtigungsverwaltung zu.',createPool:'Pool erstellen',editPool:'Pool bearbeiten',deletePool:'Pool löschen',poolId:'Pool-ID',poolIdRequired:'Pool-ID ist erforderlich',poolIdHint:'Nur Buchstaben, Zahlen, Bindestriche und Unterstriche',poolIdCannotChange:'Pool-ID kann nicht geändert werden',poolCreated:'Pool erfolgreich erstellt',poolUpdated:'Pool erfolgreich aktualisiert',poolDeleted:'Pool erfolgreich gelöscht',confirmDeletePool:'Sind Sie sicher, dass Sie diesen Pool löschen möchten? Dies kann nicht rückgängig gemacht werden.',noPoolsYet:'Noch keine Pools',createFirstPool:'Erstellen Sie Ihren ersten Pool, um VMs zu organisieren',poolMembers:'Mitglieder',members:'Mitglieder',addVmToPool:'VM zum Pool hinzufügen',assignToPool:'Pool zuweisen',removeFromPool:'Aus Pool entfernen',vmAddedToPool:'VM zum Pool hinzugefügt',vmRemovedFromPool:'VM aus Pool entfernt',confirmRemoveVmFromPool:'VM aus diesem Pool entfernen?',selectVmToAdd:'Wählen Sie eine VM zum Hinzufügen aus',allVmsInPools:'Alle VMs sind bereits in Pools',optionalDescription:'Optionale Beschreibung...',userPermissions:'Benutzer-Berechtigungen',selectPool:'Pool auswählen',noPools:'Keine Resource Pools in diesem Cluster gefunden',noPoolPerms:'Keine Berechtigungen für diesen Pool konfiguriert',selectPoolFirst:'Wählen Sie einen Cluster und Pool zur Verwaltung',addPoolPerm:'Pool-Berechtigung hinzufügen',editPoolPerm:'Pool-Berechtigung bearbeiten',poolPermSaved:'Pool-Berechtigung gespeichert',poolPermDeleted:'Pool-Berechtigung entfernt',refreshPools:'Pools von Proxmox aktualisieren',poolCacheRefreshed:'Pool-Cache aktualisiert',confirmDeletePoolPerm:'Berechtigung entfernen?',subjectType:'Typ',permissionsFor:'Berechtigungen für',addPermission:'Berechtigung hinzufügen',groupName:'Gruppenname',auditLog:'Audit Log',auditLogDescription:'Alle Benutzeraktionen der letzten 90 Tage',noAuditLogs:'Keine Audit-Einträge vorhanden',action:'Aktion',details:'Details',timestamp:'Zeitstempel',ipAddress:'IP-Adresse',userCreated:'Benutzer erstellt',userUpdated:'Benutzer aktualisiert',userDeleted:'Benutzer gelöscht',userLogin:'Anmeldung',userLogout:'Abmeldung',passwordChanged:'Passwort geändert',clusterAdded:'Cluster hinzugefügt',apiTokenWarningTitle:'API Token Authentifizierung',apiTokenWarningDesc:'Ohne Passwort funktionieren SSH-Features nicht (HA, Rolling Updates, SMBIOS, Node Shell). Empfohlen: root@pam + Passwort verwenden — PegaProx erstellt automatisch einen API-Token für 2FA-Kompatibilität.',apiTokenRecommended:'Empfohlen bei aktivierter 2FA',apiTokenCreated:'API-Token auf PVE erstellt — 2FA kann jetzt aktiviert werden',sshPasswordStillNeeded:'SSH nutzt weiterhin das Passwort (HA, Wartung) — bitte nicht ändern',authModeToken:'API: Token (2FA-sicher)',authModePassword:'API: Passwort',sshAuthMode:'SSH: Passwort',dontChangePvePassword:'PVE-Passwort nicht ändern ohne es hier zu aktualisieren',clusterDeleted:'Cluster gelöscht',clusterConfigChanged:'Cluster-Konfiguration geändert',vmStarted:'VM gestartet',vmStopped:'VM gestoppt',vmRestarted:'VM neu gestartet',vmCreated:'VM erstellt',vmDeleted:'VM gelöscht',vmCloned:'VM geklont',vmMigrated:'VM migriert',vmUnlocked:'VM entsperrt',vmLocked:'VM gesperrt',unlockVm:'VM entsperren',lockReason:'Sperrgrund',unlockWarning:'Warnung: Das Entsperren einer VM während einer aktiven Operation kann zu Datenverlust oder anderen Problemen führen. Nur fortfahren wenn die Operation fehlgeschlagen oder abgebrochen wurde.',vmBulkMigrated:'Massen-Migration',vmConfigChanged:'VM-Konfiguration geändert',vmSuspended:'VM angehalten',vmResumed:'VM fortgesetzt',vmDiskAdded:'Festplatte hinzugefügt',vmDiskRemoved:'Festplatte entfernt',vmDiskResized:'Festplatte vergrößert',vmDiskMoved:'Festplatte verschoben',vmNetworkAdded:'Netzwerk hinzugefügt',vmNetworkRemoved:'Netzwerk entfernt',vmNetworkUpdated:'Netzwerk aktualisiert',removeDiskConfirm:'Festplatte wirklich entfernen',detachDisk:'Abklemmen',importDisk:'Festplatte importieren',importDiskDesc:'Vorhandenes Disk-Image von Storage importieren',selectImportStorage:'Quell-Storage auswählen',selectDiskImage:'Disk-Image auswählen',targetBus:'Ziel-Bus',reassignOwner:'Besitzer ändern',reassignOwnerDesc:'Festplatte einer anderen VM zuweisen',targetVm:'Ziel-VM',diskReassigned:'Festplatte neu zugewiesen',diskImported:'Festplatte importiert',noImportableDisks:'Keine importierbaren Disk-Images gefunden',reassign:'Neu zuweisen',import:'Importieren',detachDiskConfirm:'Festplatte wirklich abklemmen? Sie wird zu einer ungenutzten Festplatte.',diskDetached:'Festplatte abgeklemmt',diskDeleted:'Festplatte gelöscht',diskAdded:'Festplatte hinzugefügt',dataWillBeDeleted:'Daten werden dauerhaft gelöscht!',removeNetworkConfirm:'Netzwerk wirklich entfernen',snapshotCreated:'Snapshot erstellt',snapshotDeleted:'Snapshot gelöscht',snapshotRestored:'Snapshot wiederhergestellt',rollbackStarted:'Rollback gestartet',includeRam:'RAM einschließen',snapshotName:'Snapshot Name',rollbackConfirm:'Wirklich zu diesem Snapshot zurückrollen? Dies kann nicht rückgängig gemacht werden!',replicationCreated:'Replikation erstellt',replicationDeleted:'Replikation gelöscht',replicationTriggered:'Replikation manuell gestartet',haEnabled:'HA aktiviert',haDisabled:'HA deaktiviert',noFallbackHosts:'Keine Fallback-Hosts gefunden (Single-Node Cluster?)',haVmAdded:'VM zu HA hinzugefügt',haVmRemoved:'VM aus HA entfernt',nodeMaintenanceEntered:'Wartungsmodus aktiviert',nodeMaintenanceExited:'Wartungsmodus beendet',nodeUpdateStarted:'Node-Update gestartet',twoFactorAuth:'2-Faktor-Authentifizierung',twoFactorEnabled:'2FA aktiviert',twoFactorDisabled:'2FA deaktiviert',enable2FA:'2FA aktivieren',disable2FA:'2FA deaktivieren',setup2FA:'2FA einrichten',scan2FACode:'QR-Code mit Authenticator-App scannen',enter2FACode:'6-stelligen Code eingeben',verify2FA:'Verifizieren',secretKey:'Geheimer Schlüssel',twoFARequired:'2FA-Code erforderlich',invalid2FACode:'Ungültiger 2FA-Code',force2FA:'2FA erzwingen',force2FADesc:'Alle Benutzer müssen Zwei-Faktor-Authentifizierung einrichten bevor sie PegaProx verwenden können.',force2FAHint:'OIDC/Entra-Benutzer sind ausgenommen (nutzen MFA ihres Identity Providers). Benutzer ohne 2FA sehen beim Login einen Einrichtungs-Dialog.',force2FAExcludeAdmins:'Admin-Konten ausschließen',force2FAExcludeAdminsDesc:'Admins können PegaProx ohne 2FA nutzen',force2FASetupTitle:'2FA-Einrichtung erforderlich',force2FASetupDesc:'Ihr Administrator hat Zwei-Faktor-Authentifizierung für alle Benutzer verpflichtend gemacht. Bitte richten Sie 2FA ein um fortzufahren.',resetPassword:'Passwort zurücksetzen',passwordManagedExternally:'Ihr Passwort wird extern verwaltet.',passwordManagedExternallyHint:'Bitte ändern Sie Ihr Passwort direkt in Ihrem Verzeichnisdienst.',newPassword:'Neues Passwort',confirmPassword:'Passwort bestätigen',currentPassword:'Aktuelles Passwort',passwordsDoNotMatch:'Passwörter stimmen nicht überein',passwordTooShort:'Passwort muss mindestens 4 Zeichen haben',passwordResetSuccess:'Passwort erfolgreich geändert',myProfile:'Mein Profil',security:'Sicherheit',snapshotNotSupported:'Snapshots werden nicht unterstützt',snapshotWarnings:'Snapshot-Warnungen',filterByUser:'Nach Benutzer filtern',filterByAction:'Nach Aktion filtern',allUsers:'Alle Benutzer',allActions:'Alle Aktionen',exportAuditLog:'Exportieren',refreshAuditLog:'Aktualisieren',// Header addCluster:'Cluster hinzufügen',addXcpngPool:'XCP-ng Pool hinzufügen (Tech Preview)',xcpngConnectHint:'Verbinde dich mit dem Pool-Master. XAPI Port 443 wird standardmäßig verwendet.',xcpngTechPreviewNote:'Einige Funktionen sind eingeschränkt oder können sich ändern.',pveClusterDesc:'Virtuelle Maschinen & Container',pbsDesc:'Backup-Verwaltung',vmwareDesc:'ESXi-Infrastruktur',xcpngDesc:'XCP-ng / Xen Hypervisor (Tech Preview)',addConnection:'Verbindung hinzufügen',connectionType:'Verbindungstyp',clusterManagement:'PegaProx Cluster Management for Proxmox VE',// Tabs overview:'Übersicht',resources:'Ressourcen',datacenter:'Datacenter',settings:'Einstellungen',of:'von',showing:'Zeige',perPage:'Pro Seite',loadingDatacenter:'Lade Datacenter Daten...',// Cluster -clusters:'Cluster',noClusterSelected:'Kein Cluster ausgewählt',allClustersOverview:'Alle Cluster Übersicht',allClusters:'Alle Cluster',multiClusterSummary:'Zusammenfassung aller verwalteten Cluster',clustersConnected:'Cluster verbunden',clusterOverview:'Cluster Übersicht',vmsRunning:'VMs laufend',vmsStopped:'VMs gestoppt',noClustersConfigured:'Keine Cluster konfiguriert',addClusterToStart:'Füge einen Cluster hinzu um zu beginnen',clickClusterTip:'Klicke auf eine Cluster-Zeile oder wähle aus der Sidebar um Details anzuzeigen und Ressourcen zu verwalten.',health:'Zustand',average:'Durchschnitt',noDataAvailable:'Keine Daten verfügbar',clickToManage:'Klicken zum Verwalten',sortBy:'Sortieren nach',ungrouped:'Nicht gruppiert',alerts:'Warnungen',updated:'Aktualisiert',justNow:'gerade eben',topResources:'Top Ressourcen',highestCpuUsage:'Höchste CPU- und RAM-Auslastung über alle Cluster',clickToOpenVm:'Klicken um VM zu öffnen',selectCluster:'Wähle einen Cluster aus der Liste aus',addFirstCluster:'Ersten Cluster hinzufügen',connectCluster:'Verbinde einen Proxmox Cluster mit PegaProx',clusterName:'Cluster Name',host:'Host',username:'Benutzername',password:'Passwort',passwordOrToken:'Passwort / Token',apiTokenHint:'Für API Tokens: user@realm!tokenid',sslVerification:'SSL Verifizierung',connecting:'Verbinde...',addNewCluster:'Neuen Cluster hinzufügen',testConnection:'Verbindung testen',deleteCluster:'Cluster löschen',deleteClusterConfirm:'Cluster wirklich löschen?',reconfigureCluster:'Cluster neu konfigurieren',reconfigureHint:'Geben Sie Ihr PegaProx-Passwort ein, um Ihre Identität zu bestätigen.',clusterReconfigured:'Cluster erfolgreich neu konfiguriert',reconfigure:'Neu konfigurieren',clusterHealth:'Cluster Gesundheit',clusterHealthTooltip:'Gesundheit = 100 − (CPU×30% + RAM×30% + Speicher×20% + Offline-Nodes×20%)\n\n80+: Ausgezeichnet\n60–79: Gut\n40–59: Warnung\n< 40: Kritisch',nodeScoreTooltip:'Node-Score = CPU% + RAM% (niedriger ist besser)\n\n< 100: Gut (grün)\n100–150: Erhöht (gelb)\n> 150: Kritisch (rot)',excellent:'Ausgezeichnet',good:'Gut',warning:'Warnung',critical:'Kritisch',nodesOnline:'Nodes Online',nodeJoinHint:'Um einen neuen Node hinzuzufügen, führe auf dem neuen Node aus:',avgScore:'Avg. Score',avgStorage:'Avg. Speicher',avgCpu:'Avg. CPU',avgRam:'Avg. RAM',// Nodes +clusters:'Cluster',noClusterSelected:'Kein Cluster ausgewählt',allClustersOverview:'Alle Cluster Übersicht',allClusters:'Alle Cluster',multiClusterSummary:'Zusammenfassung aller verwalteten Cluster',clustersConnected:'Cluster verbunden',clusterOverview:'Cluster Übersicht',vmsRunning:'VMs laufend',vmsStopped:'VMs gestoppt',noClustersConfigured:'Keine Cluster konfiguriert',addClusterToStart:'Füge einen Cluster hinzu um zu beginnen',clickClusterTip:'Klicke auf eine Cluster-Zeile oder wähle aus der Sidebar um Details anzuzeigen und Ressourcen zu verwalten.',health:'Zustand',average:'Durchschnitt',noDataAvailable:'Keine Daten verfügbar',clickToManage:'Klicken zum Verwalten',sortBy:'Sortieren nach',ungrouped:'Nicht gruppiert',alerts:'Warnungen',updated:'Aktualisiert',justNow:'gerade eben',topResources:'Top Ressourcen',highestCpuUsage:'Höchste CPU- und RAM-Auslastung über alle Cluster',clickToOpenVm:'Klicken um VM zu öffnen',selectCluster:'Wähle einen Cluster aus der Liste aus',addFirstCluster:'Ersten Cluster hinzufügen',connectCluster:'Verbinde einen Proxmox Cluster mit PegaProx',clusterName:'Cluster Name',host:'Host',username:'Benutzername',password:'Passwort',passwordOrToken:'Passwort / Token',apiTokenHint:'Für API Tokens: user@realm!tokenid',sslVerification:'SSL Verifizierung',connecting:'Verbinde...',addNewCluster:'Neuen Cluster hinzufügen',testConnection:'Verbindung testen',deleteCluster:'Cluster löschen',deleteClusterConfirm:'Cluster wirklich löschen?',reconfigureCluster:'Cluster neu konfigurieren',reconfigureHint:'Geben Sie Ihr PegaProx-Passwort ein, um Ihre Identität zu bestätigen.',clusterReconfigured:'Cluster erfolgreich neu konfiguriert',reconfigure:'Neu konfigurieren',clusterHealth:'Cluster Gesundheit',clusterHealthTooltip:'Gesundheit = 100 − (CPU×30% + RAM×30% + Speicher×20% + Offline-Nodes×20%)\nOhne Speicherdaten: CPU×37,5% + RAM×37,5% + Offline×25%\n\n80+: Ausgezeichnet\n60–79: Gut\n40–59: Warnung\n< 40: Kritisch',nodeScoreTooltip:'Node-Score = CPU% + RAM% (niedriger ist besser)\n\n< 100: Gut (grün)\n100–150: Erhöht (gelb)\n> 150: Kritisch (rot)',excellent:'Ausgezeichnet',good:'Gut',warning:'Warnung',critical:'Kritisch',nodesOnline:'Nodes Online',nodeJoinHint:'Um einen neuen Node hinzuzufügen, führe auf dem neuen Node aus:',avgScore:'Avg. Score',avgStorage:'Avg. Speicher',avgCpu:'Avg. CPU',avgRam:'Avg. RAM',// Nodes nodes:'Nodes',node:'Node',loadingMetrics:'Lade Metriken...',connectionError:'Verbindungsfehler',retry:'Erneut versuchen',checkConnectionAndRetry:'Bitte überprüfen Sie Ihre Verbindung und versuchen Sie es erneut.',connectionTimeout:'Zeitüberschreitung - Proxmox nicht erreichbar',maintenance:'Wartung',enterMaintenance:'Wartungsmodus aktivieren',exitMaintenance:'Wartungsmodus beenden',maintenanceMode:'Wartungsmodus',update:'Update',startUpdate:'Update starten',nodeConfig:'Node Konfiguration',cpuHistory:'CPU Verlauf',ramHistory:'RAM Verlauf',ramUsage:'RAM Nutzung',cpuUsage:'CPU Nutzung',diskUsage:'Disk Nutzung',allocated:'zugewiesen',showMore:'Mehr anzeigen',showLess:'Weniger anzeigen',// VMs & Resources virtualMachines:'Virtuelle Maschinen',containers:'Container',vm:'VM',lxc:'LXC',lxcContainer:'LXC Container',container:'Container',guests:'Guests',start:'Starten',stop:'Stoppen',shutdown:'Herunterfahren',reboot:'Neustarten',forceStop:'Force Stop',forceReset:'Force Reset',forceStopConfirm:'wirklich hart ausschalten? Dies kann zu Datenverlust führen!',migrate:'Migrieren',migrateVm:'VM migrieren',crossClusterMigration:'Cross-Cluster Migration',crossClusterMigrateDesc:'VM zu anderem Proxmox-Cluster migrieren',crossClusterMigrate:'Cross-Cluster migrieren',crossClusterStarted:'Cross-Cluster Migration gestartet',crossClusterFailed:'Cross-Cluster Migration fehlgeschlagen',crossClusterLB:'Cross-Cluster Lastverteilung',crossClusterLBEnabled:'Cross-Cluster LB aktiviert',crossClusterLBDisabled:'Cross-Cluster LB deaktiviert',crossClusterLBThreshold:'Score-Schwellenwert',crossClusterLBInterval:'Prüfintervall',crossClusterLBDryRun:'Simulation (Dry Run)',crossClusterLBLastRun:'Letzter Lauf',crossClusterReplication:'Cross-Cluster Replikation',crossClusterReplicationDesc:'VM-Snapshots zu anderem Cluster replizieren (DR)',groupOverview:'Gruppenübersicht',groupSettings:'Gruppeneinstellungen',groupSettingsSaved:'Gruppeneinstellungen gespeichert',replicationSchedule:'Zeitplan',replicationRetention:'Aufbewahrung',maxMigrations:'Max. Migrationen pro Zyklus',lbHistory:'LB-Verlauf',clusterScore:'Cluster-Score',noReplicationJobs:'Keine Cross-Cluster Replikationsjobs',createReplicationJob:'DR-Job erstellen',replicationStarted:'Replikation gestartet',replicationJobCreated:'Replikationsjob erstellt',replicationJobDeleted:'Replikationsjob gelöscht',recentLbActions:'Letzte Cross-Cluster LB-Aktionen',noLbEvents:'Noch keine Cross-Cluster LB-Ereignisse',enableCrossClusterLB:'Cross-Cluster Lastverteilung aktivieren',lbDescription:'VMs automatisch zwischen Clustern migrieren, wenn Ressourcen-Schwellenwerte überschritten werden',dryRunMode:'Dry Run / Simulationsmodus',cpuThreshold:'CPU-Schwellenwert (%)',lbStatus:'Lastverteilungs-Status',lbExplanation:'Die Cross-Cluster Lastverteilung überwacht CPU- und RAM-Auslastung über alle Cluster in dieser Gruppe. Wenn ein Cluster den konfigurierten Schwellenwert überschreitet, werden VMs automatisch zu einem weniger ausgelasteten Cluster migriert.',lbDryRunExplanation:'Aktivieren Sie zuerst den Dry Run-Modus, um geplante Aktionen zu prüfen, bevor Sie Live-Migrationen aktivieren.',neverRun:'Noch nie ausgeführt',newCrossClusterReplication:'Neue Cross-Cluster Replikation',confirmDeleteXRepl:'Diesen Cross-Cluster Replikationsjob wirklich löschen?',addDrJob:'DR-Job hinzufügen',scheduleCron:'Zeitplan (Cron)',targetStorageHint:'Storage-Name auf dem Ziel-Cluster',maxMigrationsHint:'Maximale Anzahl VMs pro Prüfzyklus (1-5)',proxlbCredit:'ProxLB von gyptazy',proxlbCreditDesc:'Unsere Lastverteilungsfunktionalität basiert auf der hervorragenden Arbeit von ProxLB. Besonderer Dank an gyptazy für die Erstellung und Open-Source-Bereitstellung dieses großartigen Tools!',xReplCreated:'Cross-Cluster Replikation erstellt',xReplDeleted:'Cross-Cluster Replikation gelöscht',xReplStarted:'Cross-Cluster Replikation gestartet',xReplCreateFailed:'Job-Erstellung fehlgeschlagen',xReplDeleteFailed:'Löschen fehlgeschlagen',xReplStartFailed:'Start fehlgeschlagen',lastRunPrefix:'Zuletzt',runNow:'Jetzt ausführen',loadingCrossClusterResources:'Lade Storage/Netzwerk für alle Cluster...',noCommonStorages:'Kein gemeinsamer Storage über alle Cluster gefunden',noCommonBridges:'Keine gemeinsame Bridge über alle Cluster gefunden',selectBridge:'Bridge auswählen...',crossClusterThresholdDesc:'CPU-Schwellenwert für Cluster-Imbalance (10-80%)',crossClusterIntervalDesc:'Zeit zwischen Prüfzyklen',crossClusterMaxMigrationsDesc:'Maximale Migrationen pro Prüfzyklus',commonStorageHint:'Nur Storages, die auf allen Clustern in der Gruppe verfügbar sind',commonBridgeHint:'Nur Bridges, die auf allen Clustern in der Gruppe verfügbar sind',includeContainers:'Container einbeziehen',includeContainersDesc:'Container (LXC) beim Cross-Cluster-Balancing berücksichtigen',containerMigrationWarning:'Container werden beim Migrieren neu gestartet (Downtime)',excludedVMsCrossCluster:'Ausgeschlossene VMs/Container',excludedVMsCrossClusterDesc:'VMs und Container, die vom automatischen Cross-Cluster-Balancing ausgeschlossen sind',clusterExcludedVMs:'Ausgeschlossene VMs',noExcludedVMsInGroup:'Keine VMs ausgeschlossen',selectCluster:'Cluster auswählen...',selectClusterFirst:'Bitte zuerst einen Ziel-Cluster auswählen',simulationMode:'Simulationsmodus',sourceVm:'Quell-VM',targetCluster:'Ziel-Cluster',targetNode:'Ziel-Node',targetStorage:'Ziel-Storage',targetBridge:'Ziel-Netzwerk (Bridge)',moveDisk:'Festplatte verschieben',resizeDisk:'Festplatte vergrößern',diskResized:'Festplatte vergrößert',deleteSourceDisk:'Quelle nach Verschieben löschen',deleteSourceDiskWarning:'Die Original-Festplatte bleibt auf dem Quell-Storage. Sie können sie später manuell entfernen.',move:'Verschieben',from:'von',noNetworkInterfaces:'Keine Netzwerk-Interfaces',nameRequired:'Name erforderlich',newVmid:'Neue VMID (optional)',sameIdPlaceholder:'Leer = gleiche ID',liveMigrationOption:'Live-Migration (VM läuft weiter)',deleteSourceAfter:'Quell-VM nach Migration löschen',largeDiskWarning:'Große Disk erkannt',largeDiskExplanation:'Live-Migration bei Disks >100GB kann mit "401 Unauthorized" fehlschlagen (Proxmox WebSocket-Ticket Timeout). Der Server wechselt automatisch auf Offline-Migration, es sei denn, Sie erzwingen Online.',forceOnlineMigration:'Online-Migration trotzdem erzwingen (kann fehlschlagen)',autoTokenInfo:'PegaProx erstellt temporäre API-Tokens für die Migration und löscht diese automatisch nach Abschluss.',clusterReachableInfo:'Die Cluster müssen sich gegenseitig über das Netzwerk erreichen können. Der konfigurierte Benutzer muss Rechte zum Erstellen von API-Tokens haben.',selectCluster:'Cluster auswählen...',selectNode:'Node auswählen',selectStorage:'Storage auswählen...',loadingNodes:'Lade Ziel-Nodes...',loadingStorageNetwork:'Lade Storage/Netzwerk...',liveMigration:'Live-Migration (ohne Downtime)',on:'auf',targetStorage:'Ziel-Storage',sameAsSource:'Gleich wie Quelle',loadingStorages:'Lade Storages',free:'frei',withLocalDisks:'Mit lokalen Disks',withLocalDisksDesc:'Lokale Disks zum Ziel-Storage migrieren',localDisksDetected:'Diese VM hat lokale Disks. Migration erfordert Kopieren der Disk-Daten.',requiredForThisVm:'Erforderlich für diese VM',cdDvdMounted:'CD/DVD eingelegt',cdDvdMigrationWarning:'Live-Migration ist mit eingelegter CD/DVD nicht möglich. Bitte zuerst die CD/DVD auswerfen oder Offline-Migration verwenden.',isoMounted:'ISO/CD-ROM eingelegt',isoEjected:'CD-ROM ausgeworfen',isoMigrationWarning:'Migration kann fehlschlagen wenn die ISO auf dem Ziel-Node nicht verfügbar ist. CD/DVD auswerfen oder ISO auf Shared Storage sicherstellen.',localStorage:'Lokal',bootOrderIssue:'Boot Order Problem',bootOrderWarning:'Boot Order referenziert nicht-existierende Disks',bootOrder:'Boot-Reihenfolge',dragToReorder:'Klicken zum Umschalten, Pfeile zum Sortieren',noBootDevices:'Keine Boot-Geräte gefunden',resizeDiskHint:'Erhöhen um (z.B. +10G) oder neue Größe',// SMBIOS Settings smbiosSettings:'SMBIOS Einstellungen',smbiosHint:'System Management BIOS - nützlich für Windows-Lizenzierung und VM-Identifikation',applySmbiosFromClusterConfig:'SMBIOS-Einstellungen aus Cluster-Konfiguration anwenden',requiresRestart:'NEUSTART',readOnly:'schreibgeschützt',autoGenerated:'auto-generiert',smbiosFormatHint:'Nur Buchstaben und Zahlen erlaubt (A-Za-z0-9)',preview:'Vorschau',currentValue:'Aktueller Wert',managedByProxmox:'von Proxmox verwaltet',willBeAutoGenerated:'Wird beim Speichern automatisch generiert',forceConntrack:'Force (Conntrack State)',forceConntrackDesc:'Erzwingt Migration auch wenn Conntrack-Einträge existieren',containerNoLiveMigration:'Container unterstützen keine echte Live-Migration',startingMigration:'Starte Migration von',migrationStarted:'Migration gestartet:',migrationFailed:'Migration fehlgeschlagen',clone:'Klonen',console:'Konsole',openConsole:'Konsole öffnen',metrics:'Metriken',config:'Konfiguration',configuration:'Konfiguration',createVm:'Neue VM erstellen',createContainer:'Neuen Container erstellen',newVm:'Neue VM',newContainer:'Neuer Container',power:'Energieoptionen',snapshot:'Snapshot',editSettings:'Einstellungen',refreshData:'Aktualisieren',sshConsole:'SSH-Konsole',noNodesAvailable:'Keine Nodes verfügbar. Bitte warten Sie bis die Cluster-Daten geladen sind.',loadingStorage:'Lade Storage-Liste...',noIsoAvailable:'Keine ISO-Images gefunden',noTemplateAvailable:'Keine Templates gefunden',noStorageAvailable:'Keine Storage verfügbar',noIsoStorage:'Kein Storage mit ISO-Inhalt gefunden',ram:'RAM',cpu:'CPU',disk:'Disk',network:'Netzwerk',// VM Creation Wizard @@ -2136,7 +2136,7 @@ poolPermissions:'Pool Permissions',poolPermissionsDesc:'Grant users or groups access to Proxmox resource pools. Permissions apply to all VMs within the pool.',managePools:'Manage Pools',poolManagerDesc:'Create, edit, and delete resource pools. Assign VMs to pools for organized permission management.',createPool:'Create Pool',editPool:'Edit Pool',deletePool:'Delete Pool',poolId:'Pool ID',poolIdRequired:'Pool ID is required',poolIdHint:'Letters, numbers, dashes and underscores only',poolIdCannotChange:'Pool ID cannot be changed',poolCreated:'Pool created successfully',poolUpdated:'Pool updated successfully',poolDeleted:'Pool deleted successfully',confirmDeletePool:'Are you sure you want to delete this pool? This cannot be undone.',noPoolsYet:'No pools yet',createFirstPool:'Create your first pool to organize VMs',poolMembers:'Members',members:'members',addVmToPool:'Add VM to Pool',assignToPool:'Assign to Pool',removeFromPool:'Remove from pool',vmAddedToPool:'VM added to pool',vmRemovedFromPool:'VM removed from pool',confirmRemoveVmFromPool:'Remove VM from this pool?',selectVmToAdd:'Select a VM to add to this pool',allVmsInPools:'All VMs are already in pools',optionalDescription:'Optional description...',userPermissions:'User Permissions',selectPool:'Select Pool',noPools:'No resource pools found in this cluster',noPoolPerms:'No permissions configured for this pool',selectPoolFirst:'Select a cluster and pool to manage permissions',addPoolPerm:'Add Pool Permission',editPoolPerm:'Edit Pool Permission',poolPermSaved:'Pool permission saved',poolPermDeleted:'Pool permission removed',refreshPools:'Refresh pools from Proxmox',poolCacheRefreshed:'Pool cache refreshed',confirmDeletePoolPerm:'Remove permission?',subjectType:'Type',permissionsFor:'Permissions for',addPermission:'Add Permission',groupName:'Group Name',auditLog:'Audit Log',auditLogDescription:'All user actions from the last 90 days',noAuditLogs:'No audit entries available',action:'Action',details:'Details',timestamp:'Timestamp',ipAddress:'IP Address',userCreated:'User created',userUpdated:'User updated',userDeleted:'User deleted',userLogin:'Login',userLogout:'Logout',passwordChanged:'Password changed',clusterAdded:'Cluster added',apiTokenWarningTitle:'API Token Authentication',apiTokenWarningDesc:'Without a password, SSH features won\'t work (HA, Rolling Updates, SMBIOS, Node Shell). Recommended: Use root@pam + password — PegaProx auto-creates an API token for 2FA compatibility.',apiTokenRecommended:'Recommended when 2FA is enabled',apiTokenCreated:'API token created on PVE — 2FA can now be safely enabled',sshPasswordStillNeeded:'SSH still uses the password (HA, maintenance) — don\'t change it',authModeToken:'API: Token (2FA-safe)',authModePassword:'API: Password',sshAuthMode:'SSH: Password',dontChangePvePassword:'Don\'t change the PVE password without updating it here',clusterDeleted:'Cluster deleted',clusterConfigChanged:'Cluster config changed',vmStarted:'VM started',vmStopped:'VM stopped',vmRestarted:'VM restarted',vmCreated:'VM created',vmDeleted:'VM deleted',vmCloned:'VM cloned',vmMigrated:'VM migrated',vmUnlocked:'VM unlocked',vmLocked:'VM locked',unlockVm:'Unlock VM',lockReason:'Lock Reason',unlockWarning:'Warning: Unlocking a VM during an active operation may cause data corruption or other issues. Only proceed if you are sure the operation has failed or been cancelled.',vmBulkMigrated:'Bulk migration',vmConfigChanged:'VM config changed',vmSuspended:'VM suspended',vmResumed:'VM resumed',vmDiskAdded:'Disk added',vmDiskRemoved:'Disk removed',vmDiskResized:'Disk resized',vmDiskMoved:'Disk moved',vmNetworkAdded:'Network added',vmNetworkRemoved:'Network removed',vmNetworkUpdated:'Network updated',removeDiskConfirm:'Really remove disk',detachDisk:'Detach',importDisk:'Import Disk',importDiskDesc:'Import existing disk image from storage',selectImportStorage:'Select Source Storage',selectDiskImage:'Select Disk Image',targetBus:'Target Bus',reassignOwner:'Reassign Owner',reassignOwnerDesc:'Assign disk to a different VM',targetVm:'Target VM',diskReassigned:'Disk reassigned',diskImported:'Disk imported',noImportableDisks:'No importable disk images found',reassign:'Reassign',import:'Import',detachDiskConfirm:'Really detach disk? It will become an unused disk.',diskDetached:'Disk detached',diskDeleted:'Disk deleted',diskAdded:'Disk added',dataWillBeDeleted:'Data will be permanently deleted!',removeNetworkConfirm:'Really remove network',snapshotCreated:'Snapshot created',snapshotDeleted:'Snapshot deleted',snapshotRestored:'Snapshot restored',rollbackStarted:'Rollback started',includeRam:'Include RAM',snapshotName:'Snapshot name',rollbackConfirm:'Really rollback to this snapshot? This cannot be undone!',replicationCreated:'Replication created',replicationDeleted:'Replication deleted',replicationTriggered:'Replication triggered',haEnabled:'HA enabled',haDisabled:'HA disabled',noFallbackHosts:'No fallback hosts found (Single-Node Cluster?)',haVmAdded:'VM added to HA',haVmRemoved:'VM removed from HA',nodeMaintenanceEntered:'Maintenance mode entered',nodeMaintenanceExited:'Maintenance mode exited',nodeUpdateStarted:'Node update started',twoFactorAuth:'Two-Factor Authentication',twoFactorEnabled:'2FA enabled',twoFactorDisabled:'2FA disabled',enable2FA:'Enable 2FA',disable2FA:'Disable 2FA',setup2FA:'Setup 2FA',scan2FACode:'Scan QR code with authenticator app',enter2FACode:'Enter 6-digit code',verify2FA:'Verify',secretKey:'Secret Key',twoFARequired:'2FA code required',invalid2FACode:'Invalid 2FA code',force2FA:'Enforce 2FA',force2FADesc:'Require all users to set up Two-Factor Authentication before they can use PegaProx.',force2FAHint:'OIDC/Entra users are exempt (they use their Identity Provider\'s MFA). Users without 2FA will see a setup dialog on login.',force2FAExcludeAdmins:'Exclude admin accounts',force2FAExcludeAdminsDesc:'Admins can use PegaProx without 2FA',force2FASetupTitle:'2FA Setup Required',force2FASetupDesc:'Your administrator has made Two-Factor Authentication mandatory for all users. Please set up 2FA to continue.',resetPassword:'Reset Password',passwordManagedExternally:'Your password is managed externally.',passwordManagedExternallyHint:'Please change your password directly in your directory service.',newPassword:'New Password',confirmPassword:'Confirm Password',currentPassword:'Current Password',passwordsDoNotMatch:'Passwords do not match',passwordTooShort:'Password must be at least 4 characters',passwordResetSuccess:'Password changed successfully',myProfile:'My Profile',security:'Security',snapshotNotSupported:'Snapshots not supported',snapshotWarnings:'Snapshot warnings',filterByUser:'Filter by user',filterByAction:'Filter by action',allUsers:'All Users',allActions:'All Actions',exportAuditLog:'Export',refreshAuditLog:'Refresh',// Header addCluster:'Add Cluster',addXcpngPool:'Add XCP-ng Pool (Tech Preview)',xcpngConnectHint:'Connect to the pool master host. XAPI port 443 is used by default.',xcpngTechPreviewNote:'Some features may be limited or subject to change.',pveClusterDesc:'Virtual machines & containers',pbsDesc:'Backup management',vmwareDesc:'ESXi infrastructure',xcpngDesc:'XCP-ng / Xen Hypervisor (Tech Preview)',addConnection:'Add Connection',connectionType:'Connection Type',clusterManagement:'PegaProx Cluster Management for Proxmox VE',// Tabs overview:'Overview',resources:'Resources',datacenter:'Datacenter',settings:'Settings',of:'of',showing:'Showing',perPage:'Per page',loadingDatacenter:'Loading datacenter data...',// Cluster -clusters:'Clusters',noClusterSelected:'No Cluster Selected',allClustersOverview:'All Clusters Overview',allClusters:'All Clusters',multiClusterSummary:'Summary of all managed clusters',clustersConnected:'Clusters Connected',clusterOverview:'Cluster Overview',vmsRunning:'VMs Running',vmsStopped:'VMs Stopped',noClustersConfigured:'No clusters configured',addClusterToStart:'Add a cluster to get started',clickClusterTip:'Click on a cluster row or select from the sidebar to view detailed information and manage resources.',health:'Health',average:'Average',noDataAvailable:'No data available',clickToManage:'Click to manage',sortBy:'Sort by',ungrouped:'Ungrouped',alerts:'Alerts',updated:'Updated',justNow:'just now',topResources:'Top Resources',highestCpuUsage:'Highest CPU and RAM usage across all clusters',clickToOpenVm:'Click to open VM',selectCluster:'Select a cluster from the list',addFirstCluster:'Add First Cluster',connectCluster:'Connect a Proxmox cluster with PegaProx',clusterName:'Cluster Name',host:'Host',username:'Username',password:'Password',passwordOrToken:'Password / Token',apiTokenHint:'For API tokens: user@realm!tokenid',sslVerification:'SSL Verification',connecting:'Connecting...',addNewCluster:'Add New Cluster',testConnection:'Test Connection',deleteCluster:'Delete Cluster',deleteClusterConfirm:'Really delete cluster?',reconfigureCluster:'Re-configure Cluster',reconfigureHint:'Enter your PegaProx password to verify your identity.',clusterReconfigured:'Cluster re-configured successfully',reconfigure:'Re-configure',clusterHealth:'Cluster Health',clusterHealthTooltip:'Health = 100 − (CPU×30% + RAM×30% + Storage×20% + Offline Nodes×20%)\n\n80+: Excellent\n60–79: Good\n40–59: Warning\n< 40: Critical',nodeScoreTooltip:'Node score = CPU% + RAM% (lower is better)\n\n< 100: Good (green)\n100–150: Elevated (yellow)\n> 150: Critical (red)',excellent:'Excellent',good:'Good',warning:'Warning',critical:'Critical',nodesOnline:'Nodes Online',nodeJoinHint:'To add a new node, run on the new node:',avgScore:'Avg. Score',avgStorage:'Avg. Storage',avgCpu:'Avg. CPU',avgRam:'Avg. RAM',// Nodes +clusters:'Clusters',noClusterSelected:'No Cluster Selected',allClustersOverview:'All Clusters Overview',allClusters:'All Clusters',multiClusterSummary:'Summary of all managed clusters',clustersConnected:'Clusters Connected',clusterOverview:'Cluster Overview',vmsRunning:'VMs Running',vmsStopped:'VMs Stopped',noClustersConfigured:'No clusters configured',addClusterToStart:'Add a cluster to get started',clickClusterTip:'Click on a cluster row or select from the sidebar to view detailed information and manage resources.',health:'Health',average:'Average',noDataAvailable:'No data available',clickToManage:'Click to manage',sortBy:'Sort by',ungrouped:'Ungrouped',alerts:'Alerts',updated:'Updated',justNow:'just now',topResources:'Top Resources',highestCpuUsage:'Highest CPU and RAM usage across all clusters',clickToOpenVm:'Click to open VM',selectCluster:'Select a cluster from the list',addFirstCluster:'Add First Cluster',connectCluster:'Connect a Proxmox cluster with PegaProx',clusterName:'Cluster Name',host:'Host',username:'Username',password:'Password',passwordOrToken:'Password / Token',apiTokenHint:'For API tokens: user@realm!tokenid',sslVerification:'SSL Verification',connecting:'Connecting...',addNewCluster:'Add New Cluster',testConnection:'Test Connection',deleteCluster:'Delete Cluster',deleteClusterConfirm:'Really delete cluster?',reconfigureCluster:'Re-configure Cluster',reconfigureHint:'Enter your PegaProx password to verify your identity.',clusterReconfigured:'Cluster re-configured successfully',reconfigure:'Re-configure',clusterHealth:'Cluster Health',clusterHealthTooltip:'Health = 100 − (CPU×30% + RAM×30% + Storage×20% + Offline Nodes×20%)\nWithout storage data: CPU×37.5% + RAM×37.5% + Offline×25%\n\n80+: Excellent\n60–79: Good\n40–59: Warning\n< 40: Critical',nodeScoreTooltip:'Node score = CPU% + RAM% (lower is better)\n\n< 100: Good (green)\n100–150: Elevated (yellow)\n> 150: Critical (red)',excellent:'Excellent',good:'Good',warning:'Warning',critical:'Critical',nodesOnline:'Nodes Online',nodeJoinHint:'To add a new node, run on the new node:',avgScore:'Avg. Score',avgStorage:'Avg. Storage',avgCpu:'Avg. CPU',avgRam:'Avg. RAM',// Nodes nodes:'Nodes',node:'Node',loadingMetrics:'Loading metrics...',connectionError:'Connection Error',retry:'Retry',checkConnectionAndRetry:'Please check your connection and try again.',connectionTimeout:'Timeout - Proxmox unreachable',maintenance:'Maintenance',enterMaintenance:'Enter Maintenance Mode',exitMaintenance:'Exit Maintenance Mode',maintenanceMode:'Maintenance Mode',update:'Update',startUpdate:'Start Update',nodeConfig:'Node Configuration',cpuHistory:'CPU History',ramHistory:'RAM History',ramUsage:'RAM Usage',cpuUsage:'CPU Usage',diskUsage:'Disk Usage',allocated:'allocated',showMore:'Show more',showLess:'Show less',// VMs & Resources virtualMachines:'Virtual Machines',containers:'Containers',vm:'VM',lxc:'LXC',lxcContainer:'LXC Container',container:'Container',guests:'Guests',start:'Start',stop:'Stop',shutdown:'Shutdown',reboot:'Reboot',forceStop:'Force Stop',forceReset:'Force Reset',forceStopConfirm:'really force stop? This may cause data loss!',migrate:'Migrate',migrateVm:'Migrate VM',crossClusterMigration:'Cross-Cluster Migration',crossClusterMigrateDesc:'Migrate VM to another Proxmox cluster',crossClusterMigrate:'Cross-Cluster Migrate',crossClusterStarted:'Cross-Cluster Migration started',crossClusterFailed:'Cross-Cluster Migration failed',crossClusterLB:'Cross-Cluster Load Balancing',crossClusterLBEnabled:'Cross-Cluster LB enabled',crossClusterLBDisabled:'Cross-Cluster LB disabled',crossClusterLBThreshold:'Score Threshold',crossClusterLBInterval:'Check Interval',crossClusterLBDryRun:'Simulation (Dry Run)',crossClusterLBLastRun:'Last Run',crossClusterReplication:'Cross-Cluster Replication',crossClusterReplicationDesc:'Replicate VM snapshots to another cluster (DR)',groupOverview:'Group Overview',groupSettings:'Group Settings',groupSettingsSaved:'Group settings saved',replicationSchedule:'Schedule',replicationRetention:'Retention',maxMigrations:'Max Migrations per Cycle',lbHistory:'LB History',clusterScore:'Cluster Score',noReplicationJobs:'No cross-cluster replication jobs',createReplicationJob:'Create DR Job',replicationStarted:'Replication started',replicationJobCreated:'Replication job created',replicationJobDeleted:'Replication job deleted',recentLbActions:'Recent cross-cluster LB actions',noLbEvents:'No cross-cluster LB events yet',enableCrossClusterLB:'Enable Cross-Cluster Load Balancing',lbDescription:'Automatically migrate VMs between clusters when resource thresholds are exceeded',dryRunMode:'Dry Run / Simulation Mode',cpuThreshold:'CPU Threshold (%)',lbStatus:'Load Balancing Status',lbExplanation:'Cross-Cluster Load Balancing monitors CPU and RAM usage across all clusters in this group. When a cluster exceeds the configured threshold, VMs are automatically migrated to a less loaded cluster.',lbDryRunExplanation:'Enable Dry Run mode first to review what actions would be taken before enabling live migrations.',neverRun:'Never run',newCrossClusterReplication:'New Cross-Cluster Replication',confirmDeleteXRepl:'Delete this cross-cluster replication job?',addDrJob:'Add DR Job',scheduleCron:'Schedule (cron)',targetStorageHint:'Storage name on destination cluster',maxMigrationsHint:'Limit how many VMs are moved each check cycle (1-5)',proxlbCredit:'ProxLB by gyptazy',proxlbCreditDesc:'Our load balancing functionality is based on the excellent work from ProxLB. Special thanks to gyptazy for creating and open-sourcing this amazing tool!',xReplCreated:'Cross-cluster replication created',xReplDeleted:'Cross-cluster replication deleted',xReplStarted:'Cross-cluster replication started',xReplCreateFailed:'Failed to create job',xReplDeleteFailed:'Delete failed',xReplStartFailed:'Failed to start',lastRunPrefix:'Last',runNow:'Run now',loadingCrossClusterResources:'Loading storage/network for all clusters...',noCommonStorages:'No common storage found across all clusters',noCommonBridges:'No common bridge found across all clusters',selectBridge:'Select bridge...',crossClusterThresholdDesc:'CPU threshold for cluster imbalance (10-80%)',crossClusterIntervalDesc:'Time between check cycles',crossClusterMaxMigrationsDesc:'Max migrations per check cycle',commonStorageHint:'Only storages available on all clusters in this group',commonBridgeHint:'Only bridges available on all clusters in this group',includeContainers:'Include Containers',includeContainersDesc:'Include containers (LXC) in cross-cluster balancing',containerMigrationWarning:'Containers are restarted during migration (downtime)',excludedVMsCrossCluster:'Excluded VMs/Containers',excludedVMsCrossClusterDesc:'VMs and containers excluded from automatic cross-cluster balancing',clusterExcludedVMs:'Excluded VMs',noExcludedVMsInGroup:'No VMs excluded',selectCluster:'Select cluster...',selectClusterFirst:'Select a target cluster first',simulationMode:'Simulation Mode',sourceVm:'Source VM',targetCluster:'Target Cluster',targetNode:'Target Node',targetStorage:'Target Storage',targetBridge:'Target Network (Bridge)',moveDisk:'Move Disk',resizeDisk:'Resize Disk',diskResized:'Disk resized',deleteSourceDisk:'Delete source after move',deleteSourceDiskWarning:'The original disk will remain on the source storage. You can remove it manually later.',move:'Move',from:'from',noNetworkInterfaces:'No network interfaces',nameRequired:'Name required',newVmid:'New VMID (optional)',sameIdPlaceholder:'Empty = same ID',liveMigrationOption:'Live Migration (VM keeps running)',deleteSourceAfter:'Delete source VM after migration',largeDiskWarning:'Large Disk Detected',largeDiskExplanation:'Live migration for disks >100GB may fail with "401 Unauthorized" due to Proxmox WebSocket ticket timeout. The server will automatically use offline migration unless forced.',forceOnlineMigration:'Force online migration anyway (may fail)',autoTokenInfo:'PegaProx automatically creates temporary API tokens for migration and deletes them after completion.',clusterReachableInfo:'Clusters must be able to reach each other over the network. The configured user must have permissions to create API tokens.',selectCluster:'Select cluster...',selectNode:'Select Node',selectStorage:'Select storage...',loadingNodes:'Loading target nodes...',loadingStorageNetwork:'Loading storage/network...',liveMigration:'Live Migration (no downtime)',on:'on',targetStorage:'Target Storage',sameAsSource:'Same as source',loadingStorages:'Loading storages',free:'free',withLocalDisks:'With Local Disks',withLocalDisksDesc:'Migrate local disks to target storage',localDisksDetected:'This VM has local disks. Migration requires copying disk data.',requiredForThisVm:'Required for this VM',cdDvdMounted:'CD/DVD Drive Mounted',cdDvdMigrationWarning:'Live migration is not possible with a CD/DVD mounted. Please eject the CD/DVD first or use offline migration.',isoMounted:'ISO/CD-ROM Mounted',isoEjected:'CD-ROM Ejected',isoMigrationWarning:'Migration may fail if the ISO is not available on the target node. Eject the CD/DVD or ensure the ISO exists on shared storage.',localStorage:'Local',bootOrderIssue:'Boot Order Issue',bootOrderWarning:'Boot order references non-existent disks',bootOrder:'Boot Order',dragToReorder:'Click to toggle, use arrows to reorder',noBootDevices:'No boot devices found',resizeDiskHint:'Increase by (e.g. +10G) or new size',// SMBIOS Settings smbiosSettings:'SMBIOS Settings',smbiosHint:'System Management BIOS - useful for Windows licensing and VM identification',applySmbiosFromClusterConfig:'Apply SMBIOS settings from cluster configuration',requiresRestart:'RESTART',readOnly:'read-only',autoGenerated:'auto-generated',smbiosFormatHint:'Only letters and numbers allowed (A-Za-z0-9)',preview:'Preview',currentValue:'Current Value',managedByProxmox:'managed by Proxmox',willBeAutoGenerated:'Will be auto-generated on save',forceConntrack:'Force (Conntrack State)',forceConntrackDesc:'Force migration even if conntrack entries exist',containerNoLiveMigration:'Containers do not support true live migration',startingMigration:'Starting migration of',migrationStarted:'Migration started:',migrationFailed:'Migration failed',clone:'Clone',console:'Console',openConsole:'Open Console',metrics:'Metrics',config:'Configuration',configuration:'Configuration',createVm:'Create new VM',createContainer:'Create new Container',newVm:'New VM',newContainer:'New Container',power:'Power',snapshot:'Snapshot',editSettings:'Settings',refreshData:'Refresh',sshConsole:'SSH Console',noNodesAvailable:'No nodes available. Please wait for cluster data to load.',loadingStorage:'Loading storage list...',noIsoAvailable:'No ISO images found',noTemplateAvailable:'No templates found',noStorageAvailable:'No storage available',noIsoStorage:'No storage with ISO content found',ram:'RAM',cpu:'CPU',disk:'Disk',network:'Network',// VM Creation Wizard @@ -2225,7 +2225,7 @@ poolPermissions:'Permissions de Pool',poolPermissionsDesc:'Accordez aux utilisateurs ou groupes l\'accès aux pools Proxmox. Les permissions s\'appliquent à tous les VMs du groupe.',managePools:'Gérer les groupes',poolManagerDesc:'Créer, modifier et supprimer des pools. Attribuer les VMs au sein des groupes pour une gestion des autorisations organisée.',createPool:'Créer un Pool',editPool:'Modifier le Pool',deletePool:'Supprimer le pool',poolId:'ID du pool',poolIdRequired:'L\'ID du pool est requis.',poolIdHint:'Lettres, chiffres, tirets et soulignements seulement',poolIdCannotChange:'L\'ID de la piscine ne peut pas être modifié.',poolCreated:'Pool créé avec succès',poolUpdated:'Le pool a été mis à jour avec succès.',poolDeleted:'Pool supprimé avec succès',confirmDeletePool:'Êtes-vous sûr de vouloir supprimer ce pool ? Cela ne peut pas être annulé.',noPoolsYet:'Aucun pool pour l\'instant',createFirstPool:'Créez votre premier pool pour organiser les VMs',poolMembers:'Membres',members:'membres',addVmToPool:'Ajouter une VM au pool',assignToPool:'Affecter au pool',removeFromPool:'Retirer du pool',vmAddedToPool:'Une VM a été ajoutée au pool.',vmRemovedFromPool:'Une VM a été retirée du pool',confirmRemoveVmFromPool:'Retirer la VM de ce pool ?',selectVmToAdd:'Sélectionnez une VM à ajouter à ce pool',allVmsInPools:'Toutes les VMs sont déjà dans des pools',optionalDescription:'Description facultative...',userPermissions:'Permissions Utilisateur',selectPool:'Sélectionnez le Pool',noPools:'Aucun groupe de ressources trouvé dans ce cluster',noPoolPerms:'Aucune permission configurée pour ce pool',selectPoolFirst:'Sélectionnez un cluster et un pool pour gérer les autorisations',addPoolPerm:'Ajouter une permission de pool',editPoolPerm:'Modifier les permissions du pool',poolPermSaved:'Permission de groupe enregistrée',poolPermDeleted:'Permission de pool supprimée',refreshPools:'Rafraîchir les pools depuis Proxmox',poolCacheRefreshed:'Le cache du pool a été mis à jour',confirmDeletePoolPerm:'Supprimer cette permission ?',subjectType:'Type',permissionsFor:'Permissions pour',addPermission:'Ajouter une permission',groupName:'Nom du groupe',auditLog:'Journal d\'audit',auditLogDescription:'Toutes les actions des utilisateurs au cours des 90 derniers jours',noAuditLogs:'Aucune entrée d\'audit disponible',action:'Action',details:'Détails',timestamp:'Horodatage',ipAddress:'Adresse IP',userCreated:'Utilisateur créé',userUpdated:'Utilisateur mis à jour',userDeleted:'Utilisateur supprimé',userLogin:'Se connecter',userLogout:'Déconnexion',passwordChanged:'Mot de passe modifié',clusterAdded:'Cluster ajouté',apiTokenWarningTitle:'Authentification par jeton API',apiTokenWarningDesc:'Sans mot de passe, les fonctionnalités SSH ne fonctionneront pas (HA, Mises à jour enroulées, SMBIOS, Shell du nœud). Recommandé : Utilisez root@pam + mot de passe — PegaProx crée automatiquement un jeton API pour la compatibilité avec le 2FA.',apiTokenRecommended:'Recommandé lorsque l\'authentification à deux facteurs est activée',apiTokenCreated:'Un jeton API créé sur Proxmox VE — la 2FA peut maintenant être activée en toute sécurité.',sshPasswordStillNeeded:'SSH utilise toujours le mot de passe (HA, maintenance) - ne pas le changer',authModeToken:'API : Jeton (sécurisé 2FA)',authModePassword:'API : Mot de passe',sshAuthMode:'SSH : Mot de passe',dontChangePvePassword:'Ne pas changer le mot de passe PVE sans le changer ici aussi',clusterDeleted:'Cluster supprimé',clusterConfigChanged:'La configuration du cluster a changé.',vmStarted:'La VM a démarré',vmStopped:'VM arrêtée',vmRestarted:'La machine virtuelle a été redémarrée.',vmCreated:'Une VM a été créée.',vmDeleted:'VM supprimée',vmCloned:'VM clonée',vmMigrated:'VM migrée',vmUnlocked:'VM déverrouillée',vmLocked:'La machine virtuelle est verrouillée.',unlockVm:'Déverrouiller la VM',lockReason:'Raison du verrouillage',unlockWarning:'Attention : Déverrouiller une VM pendant une opération active peut entraîner des dommages aux données ou d\'autres problèmes. Ne procédez que si vous êtes sûr que l\'opération a échoué ou été annulée.',vmBulkMigrated:'Migration en masse',vmConfigChanged:'La configuration de la VM a changé.',vmSuspended:'VM suspendue',vmResumed:'La VM a été reprise.',vmDiskAdded:'Disque ajouté',vmDiskRemoved:'Disque supprimé',vmDiskResized:'Disque redimensionné',vmDiskMoved:'Disque déplacé',vmNetworkAdded:'Réseau ajouté',vmNetworkRemoved:'Réseau supprimé',vmNetworkUpdated:'Réseau mis à jour',removeDiskConfirm:'Voulez-vous vraiment supprimer le disque ?',detachDisk:'Détacher',importDisk:'Importer un disque',importDiskDesc:'Importer une image de disque existante depuis le stockage',selectImportStorage:'Sélectionner le stockage source',selectDiskImage:'Sélectionnez l\'image du disque',targetBus:'Bus cible',reassignOwner:'Transférer le propriétaire',reassignOwnerDesc:'Attribuez un disque à une autre machine virtuelle',targetVm:'VM cible',diskReassigned:'Disque réaffecté',diskImported:'Disque importé',noImportableDisks:'Aucune image de disque importable trouvée',reassign:'Réaffecter',import:'Importer',detachDiskConfirm:'Voulez-vous vraiment détacher le disque ? Il deviendra un disque inutilisé.',diskDetached:'Disque détaché',diskDeleted:'Disque supprimé',diskAdded:'Disque ajouté',dataWillBeDeleted:'Les données seront définitivement supprimées!',removeNetworkConfirm:'Voulez-vous vraiment supprimer le réseau ?',snapshotCreated:'Point de restauration créé',snapshotDeleted:'Point de restauration supprimé',snapshotRestored:'Le point de restauration a été restauré.',rollbackConfirm:'Voulez-vous vraiment revenir à ce point de restauration ? Cela ne peut pas être annulé !',replicationCreated:'Réplication créée',replicationDeleted:'La réplication a été supprimée.',replicationTriggered:'Réplication déclenchée',haEnabled:'HA Activé',haDisabled:'HA désactivé',noFallbackHosts:'Aucun hôte de secours trouvé (Cluster à un nœud ?)',haVmAdded:'Une VM a été ajoutée au HA',haVmRemoved:'VM retirée de l\'HA',nodeMaintenanceEntered:'Mode de maintenance entré',nodeMaintenanceExited:'Mode de maintenance sorti',nodeUpdateStarted:'Mise à jour du nœud démarrée',twoFactorAuth:'Authentification à deux facteurs',twoFactorEnabled:'2FA activé',twoFactorDisabled:'2FA désactivé',enable2FA:'Activer la 2FA',disable2FA:'Désactiver 2FA',setup2FA:'Configurer 2FA',scan2FACode:'Scanner le code QR avec l\'application d\'authentification',enter2FACode:'Entrez un code à 6 chiffres',verify2FA:'Vérifier',secretKey:'Clé secrète',twoFARequired:'Code 2FA requis',invalid2FACode:'Code 2FA invalide',force2FA:'Imposez la 2FA',force2FADesc:'Tous les utilisateurs doivent configurer l\'authentification à deux facteurs avant de pouvoir utiliser PegaProx.',force2FAHint:'Les utilisateurs OIDC/Entra sont exemptés (ils utilisent la MFA de leur fournisseur d\'identité). Les utilisateurs sans 2FA verront un assistant de configuration à l\'inscription.',force2FAExcludeAdmins:'Exclure les comptes administrateurs',force2FAExcludeAdminsDesc:'Les administrateurs peuvent utiliser PegaProx sans 2FA',force2FASetupTitle:'Configuration de la 2FA requise',force2FASetupDesc:'Votre administrateur a rendu l\'authentification à deux facteurs obligatoire pour tous les utilisateurs. Veuillez configurer 2FA pour continuer.',resetPassword:'Réinitialiser le mot de passe',passwordManagedExternally:'Votre mot de passe est géré en externe.',passwordManagedExternallyHint:'Veuillez modifier votre mot de passe directement dans votre service d\'annuaire.',newPassword:'Nouveau mot de passe',confirmPassword:'Confirmer le mot de passe',currentPassword:'Mot de passe actuel',passwordsDoNotMatch:'Les mots de passe ne correspondent pas.',passwordTooShort:'Le mot de passe doit comporter au moins 4 caractères.',passwordResetSuccess:'Mot de passe modifié avec succès',myProfile:'Mon profil',security:'sécurité',snapshotNotSupported:'Les point de restauration ne sont pas pris en charge.',snapshotWarnings:'Avertissements sur le point de restauration',filterByUser:'Filtrer par utilisateur',filterByAction:'Filtrer par action',allUsers:'Tous les utilisateurs',allActions:'Toutes les actions',exportAuditLog:'Exporter',refreshAuditLog:'Actualiser',// Header addCluster:'Ajouter un Cluster',addXcpngPool:'Ajouter un Pool XCP-ng (Préversion Technique)',xcpngConnectHint:'Connectez-vous au serveur maître du pool. Le port XAPI 443 est utilisé par défaut.',xcpngTechPreviewNote:'Certaines fonctionnalités peuvent être limitées ou sujettes à modification.',pveClusterDesc:'Machines virtuelles et conteneurs',pbsDesc:'Gestion des sauvegardes',vmwareDesc:'Infrastructure ESXi',xcpngDesc:'XCP-ng / Hyperviseur Xen (Prévisualisation technique)',addConnection:'Ajouter une Connexion',connectionType:'Type de connexion',clusterManagement:'Gestion de cluster',// Tabs overview:'Vue d\\',resources:'Ressources',datacenter:'Centre de données',settings:'Paramètres',of:'de',showing:'Affichant',perPage:'Par page',loadingDatacenter:'Chargement...',// Cluster -clusters:'Clusters',noClusterSelected:'Aucun cluster sélectionné',allClustersOverview:'Vue d\'ensemble de tous les clusters',allClusters:'Tous les clusters',multiClusterSummary:'Résumé de tous les clusters gérés',clustersConnected:'Clusters Connectés',clusterOverview:'Vue d\'ensemble du cluster',vmsRunning:'VMs en cours d\'exécution',vmsStopped:'Les VMs ont été arrêtées.',noClustersConfigured:'Aucun cluster configuré',addClusterToStart:'Ajoutez un cluster pour commencer',clickClusterTip:'Cliquez sur une ligne de cluster ou sélectionnez dans la barre latérale pour afficher des informations détaillées et gérer les ressources.',health:'Santé',average:'Moyenne',noDataAvailable:'Aucune donnée disponible',clickToManage:'Cliquer pour gérer',sortBy:'Trier par',ungrouped:'Non regroupé',alerts:'Alertes',updated:'Mis à jour',justNow:'Il y a quelques instants',topResources:'Ressources principales',highestCpuUsage:'Utilisation maximale de CPU et de RAM sur tous les clusters',clickToOpenVm:'Cliquez pour ouvrir la VM',selectCluster:'Sélectionnez un cluster de stockage',addFirstCluster:'Ajouter le Premier Cluster',connectCluster:'Connectez un cluster Proxmox avec PegaProx',clusterName:'Nom du cluster',host:'Serveur',username:'Nom d\\',password:'Mot de passe',passwordOrToken:'Mot de passe / Jeton',apiTokenHint:'Pour les jetons API : utilisateur@domaine!identifiant_token',sslVerification:'Vérification SSL',connecting:'Connexion...',addNewCluster:'Ajouter un Nouveau Cluster',testConnection:'Tester la Connexion',deleteCluster:'Supprimer le cluster',deleteClusterConfirm:'Supprimer vraiment le cluster ?',reconfigureCluster:'Reconfigurer le cluster',reconfigureHint:'Entrez votre mot de passe PegaProx pour vérifier votre identité.',clusterReconfigured:'Cluster reconfiguré avec succès',reconfigure:'Reconfigurer',clusterHealth:'Santé du Cluster',clusterHealthTooltip:'Santé = 100 − (CPU×30% + RAM×30% + Stockage×20% + Nœuds hors ligne×20%)\n\n80+ : Excellente\n60–79 : Bien\n40–59 : Avertissement\n< 40 : Critique',nodeScoreTooltip:'Score nœud = CPU% + RAM% (plus bas est mieux)\n\n< 100 : Bon (vert)\n100–150 : Élevé (jaune)\n> 150 : Critique (rouge)',excellent:'Excellente',good:'Bien',warning:'Avertissement',critical:'Critique',nodesOnline:'Nœuds en ligne',nodeJoinHint:'Pour ajouter un nouveau nœud, exécutez sur le nouveau nœud :',avgScore:'Moyenne des notes',avgStorage:'Moy. Stockage',avgCpu:'Moyenne CPU',avgRam:'Moyenne de la RAM',// Nodes +clusters:'Clusters',noClusterSelected:'Aucun cluster sélectionné',allClustersOverview:'Vue d\'ensemble de tous les clusters',allClusters:'Tous les clusters',multiClusterSummary:'Résumé de tous les clusters gérés',clustersConnected:'Clusters Connectés',clusterOverview:'Vue d\'ensemble du cluster',vmsRunning:'VMs en cours d\'exécution',vmsStopped:'Les VMs ont été arrêtées.',noClustersConfigured:'Aucun cluster configuré',addClusterToStart:'Ajoutez un cluster pour commencer',clickClusterTip:'Cliquez sur une ligne de cluster ou sélectionnez dans la barre latérale pour afficher des informations détaillées et gérer les ressources.',health:'Santé',average:'Moyenne',noDataAvailable:'Aucune donnée disponible',clickToManage:'Cliquer pour gérer',sortBy:'Trier par',ungrouped:'Non regroupé',alerts:'Alertes',updated:'Mis à jour',justNow:'Il y a quelques instants',topResources:'Ressources principales',highestCpuUsage:'Utilisation maximale de CPU et de RAM sur tous les clusters',clickToOpenVm:'Cliquez pour ouvrir la VM',selectCluster:'Sélectionnez un cluster de stockage',addFirstCluster:'Ajouter le Premier Cluster',connectCluster:'Connectez un cluster Proxmox avec PegaProx',clusterName:'Nom du cluster',host:'Serveur',username:'Nom d\\',password:'Mot de passe',passwordOrToken:'Mot de passe / Jeton',apiTokenHint:'Pour les jetons API : utilisateur@domaine!identifiant_token',sslVerification:'Vérification SSL',connecting:'Connexion...',addNewCluster:'Ajouter un Nouveau Cluster',testConnection:'Tester la Connexion',deleteCluster:'Supprimer le cluster',deleteClusterConfirm:'Supprimer vraiment le cluster ?',reconfigureCluster:'Reconfigurer le cluster',reconfigureHint:'Entrez votre mot de passe PegaProx pour vérifier votre identité.',clusterReconfigured:'Cluster reconfiguré avec succès',reconfigure:'Reconfigurer',clusterHealth:'Santé du Cluster',clusterHealthTooltip:'Santé = 100 − (CPU×30% + RAM×30% + Stockage×20% + Nœuds hors ligne×20%)\nSans données stockage : CPU×37,5% + RAM×37,5% + Hors ligne×25%\n\n80+ : Excellente\n60–79 : Bien\n40–59 : Avertissement\n< 40 : Critique',nodeScoreTooltip:'Score nœud = CPU% + RAM% (plus bas est mieux)\n\n< 100 : Bon (vert)\n100–150 : Élevé (jaune)\n> 150 : Critique (rouge)',excellent:'Excellente',good:'Bien',warning:'Avertissement',critical:'Critique',nodesOnline:'Nœuds en ligne',nodeJoinHint:'Pour ajouter un nouveau nœud, exécutez sur le nouveau nœud :',avgScore:'Moyenne des notes',avgStorage:'Moy. Stockage',avgCpu:'Moyenne CPU',avgRam:'Moyenne de la RAM',// Nodes nodes:'Nœuds',node:'Nœud',loadingMetrics:'Chargement...',connectionError:'Erreur de connexion',retry:'Réessayer',checkConnectionAndRetry:'Veuillez check your connection and try again.',connectionTimeout:'Délai d\'expiration - Proxmox inaccessible',maintenance:'Maintenance',enterMaintenance:'Entrez en mode maintenance',exitMaintenance:'Sortir du mode maintenance',maintenanceMode:'Mode de maintenance',update:'Mettre à jour',startUpdate:'Commencer la mise à jour',nodeConfig:'Configuration du nœud',cpuHistory:'Historique du processeur',ramHistory:'Historique de la RAM',ramUsage:'Utilisation RAM',cpuUsage:'Utilisation CPU',diskUsage:'Utilisation du disque',showMore:'Montrer plus',showLess:'Montrer moins',// VMs & Resources virtualMachines:'Machines virtuelles',containers:'Conteneurs',vm:'VM',lxc:'LXC',lxcContainer:'Conteneur LXC',container:'Conteneur',guests:'Clients',start:'Démarrer',stop:'Arrêtez',shutdown:'Arrêter',reboot:'Redémarrer',forceStop:'Forcer l\'arrêt',forceReset:'Forcer le redémarrage',forceStopConfirm:'Forcer l\'arrêt ? Cela peut causer une perte de données !',migrate:'Migrer',migrateVm:'Migrer la VM',crossClusterMigration:'Migration inter-cluster',crossClusterMigrateDesc:'Migrer une VM vers un autre cluster Proxmox',crossClusterMigrate:'Migration entre clusters',crossClusterStarted:'La migration entre clusters a commencé.',crossClusterFailed:'La migration entre clusters a échoué.',crossClusterLB:'Équilibrage de charge inter-cluster',crossClusterLBEnabled:'Balanceur de charge entre clusters activé',crossClusterLBDisabled:'Désactivation de l\'équilibreur de charge entre clusters',crossClusterLBThreshold:'Seuil de notation',crossClusterLBInterval:'Intervalle de vérification',crossClusterLBDryRun:'Simulation (Dry Run)',crossClusterLBLastRun:'Dernière Exécution',crossClusterReplication:'Réplication Inter-Clustère',crossClusterReplicationDesc:'Répliquer les point de restauration de VM vers un autre cluster (DR)',groupOverview:'Vue d\'ensemble du groupe',groupSettings:'Paramètres du groupe',groupSettingsSaved:'Paramètres du groupe enregistrés',replicationSchedule:'Planification',replicationRetention:'Retenue',maxMigrations:'Max Migrations par Cycle',lbHistory:'Historique du Load Balancer',clusterScore:'Score du Cluster',noReplicationJobs:'Aucun Job de Réplication',createReplicationJob:'Créer une tâche de réplication',replicationStarted:'La réplication a commencé.',replicationJobCreated:'Tâche de réplication créée',replicationJobDeleted:'Tâche de réplication supprimée',recentLbActions:'Actions récentes de load balancing entre clusters',noLbEvents:'Aucun événement de load balancing inter-cluster',enableCrossClusterLB:'Activer le équilibrage de charge inter-clusters',lbDescription:'Migrer automatiquement les VMs entre les clusters lorsque les seuils de ressources sont dépassés',dryRunMode:'Mode de simulation / Exécution sèche',cpuThreshold:'Seuil de processeur (%)',lbStatus:'État du Balanceur de charge',lbExplanation:'Le bilan de charge inter-cluster surveille l\'utilisation du CPU et de la RAM sur tous les clusters de ce groupe. Lorsque un cluster dépasse le seuil configuré, les VMs sont automatiquement migrées vers un cluster moins chargé.',lbDryRunExplanation:'Activer d\'abord le mode de simulation pour examiner les actions qui seraient prises avant d\'activer les migrations en direct.',neverRun:'N\'exécutez jamais',newCrossClusterReplication:'Nouvelle Réplication Inter-Clustère',confirmDeleteXRepl:'Supprimer ce job de réplication inter-cluster ?',addDrJob:'Ajouter une tâche de réplication',scheduleCron:'Planification (cron)',targetStorageHint:'Nom du stockage sur le cluster de destination',maxMigrationsHint:'Limitez le nombre de VMs déplacées chaque cycle de vérification (1-5)',proxlbCredit:'ProxLB par gyptazy',proxlbCreditDesc:'Notre fonctionnalité de balanceur de charge est basée sur le travail exceptionnel de ProxLB. Nous tenons à remercier gyptazy pour avoir créé et open-sourced cet outil incroyable !',xReplCreated:'Réplication entre clusters créée',xReplDeleted:'La réplication entre clusters a été supprimée.',xReplStarted:'La réplication entre clusters a commencé.',xReplCreateFailed:'Échec de la création du job',xReplDeleteFailed:'Suppression échouée',xReplStartFailed:'Échec du démarrage',lastRunPrefix:'Dernier',runNow:'Exécuter maintenant',loadingCrossClusterResources:'Chargement du stockage/réseau pour tous les clusters…',noCommonStorages:'Aucun stockage commun trouvé dans tous les clusters',noCommonBridges:'Aucun pont commun trouvé dans tous les clusters',selectBridge:'Sélectionnez le pont...',crossClusterThresholdDesc:'Seuil de CPU pour l\'équilibre du cluster (10-80%)',crossClusterIntervalDesc:'Intervalle entre les cycles de vérification',crossClusterMaxMigrationsDesc:'Nombre maximal de migrations par cycle de vérification',commonStorageHint:'Seulement les stockages disponibles sur tous les clusters de ce groupe',commonBridgeHint:'Seulement les ponts disponibles sur tous les clusters de ce groupe',includeContainers:'Inclure les Conteneurs',includeContainersDesc:'Inclure les conteneurs (LXC) dans l\'équilibrage inter-cluster',containerMigrationWarning:'Les conteneurs sont redémarrés pendant la migration (temps d\'arrêt)',excludedVMsCrossCluster:'VMs/Conteneurs exclus',excludedVMsCrossClusterDesc:'Les VMs et les conteneurs exclus de l\'équilibrage automatique entre les clusters',clusterExcludedVMs:'VMs exclues',noExcludedVMsInGroup:'Aucune VM exclue',selectCluster:'Sélectionnez un cluster de stockage',selectClusterFirst:'Veuillez sélectionner d\'abord un cluster cible.',simulationMode:'Mode de simulation',sourceVm:'Source VM',targetCluster:'Cluster cible',targetNode:'Nœud cible',targetStorage:'Stockage cible',targetBridge:'Réseau cible (Bridge)',moveDisk:'Déplacer le disque dur',resizeDisk:'Redimensionner le disque dur',diskResized:'Disque redimensionné',deleteSourceDisk:'Supprimer la source après le déplacement',deleteSourceDiskWarning:'Le disque original restera sur le stockage source. Vous pouvez le supprimer manuellement plus tard.',move:'Déplacer',from:'de',noNetworkInterfaces:'Aucune interface réseau',nameRequired:'Nom requis',newVmid:'Nouvel ID de machine virtuelle (facultatif)',sameIdPlaceholder:'Vide = même ID',liveMigrationOption:'Migration en direct (la VM continue d\'exécuter)',deleteSourceAfter:'Supprimer la VM source après la migration',largeDiskWarning:'Disque de grande taille détecté',largeDiskExplanation:'La migration en direct des disques >100GB peut échouer avec "401 Unauthorized" en raison du timeout du ticket WebSocket de Proxmox. Le serveur utilisera automatiquement la migration hors ligne sauf s\'il est forcé.',forceOnlineMigration:'Forcer la migration en ligne même si elle échoue',autoTokenInfo:'PegaProx crée automatiquement des jetons API temporaires pour la migration et les supprime après la complétion.',clusterReachableInfo:'Les clusters doivent pouvoir se connecter entre eux au réseau. L\'utilisateur configuré doit avoir les autorisations pour créer des jetons API.',selectCluster:'Sélectionnez un cluster de stockage',selectNode:'-- Sélectionnez un nœud --',selectStorage:'Sélectionnez un stockage pour afficher le contenu',loadingNodes:'Chargement de la node (noeud) cible…',loadingStorageNetwork:'Chargement du stockage/réseau…',liveMigration:'Migration en direct (pas d\'arrêt)',on:'sur',targetStorage:'Stockage cible',sameAsSource:'Même que la source',loadingStorages:'Chargement des stockages',free:'libre',withLocalDisks:'Avec les disques locaux',withLocalDisksDesc:'Migrer les disques locaux vers le stockage cible',localDisksDetected:'Cette VM a des disques locaux. La migration nécessite la copie des données de disque.',requiredForThisVm:'Nécessaire pour cette VM',cdDvdMounted:'Disque CD/DVD Monté',cdDvdMigrationWarning:'La migration en direct n\'est pas possible avec un CD/DVD monté. Veuillez éjecter le CD/DVD d\'abord ou utilisez la migration hors ligne.',isoMounted:'ISO/CD-ROM Monté',isoEjected:'CD-ROM éjecté',isoMigrationWarning:'La migration peut échouer si l\'ISO n\'est pas disponible sur le nœud cible. Éjectez le CD/DVD ou assurez-vous que l\'ISO existe sur un stockage partagé.',localStorage:'Local',bootOrderIssue:'Problème d\'ordre de démarrage',bootOrderWarning:'L\'ordre de démarrage fait référence à des disques qui n\'existent pas.',bootOrder:'Ordre de démarrage',dragToReorder:'Cliquez pour basculer, utilisez les flèches pour réorganiser',noBootDevices:'Aucun dispositif de démarrage trouvé',resizeDiskHint:'Augmenter de (par exemple +10G) ou nouvelle taille',// SMBIOS Settings smbiosSettings:'Paramètres SMBIOS',smbiosHint:'Gestion du système BIOS - utile pour la licence Windows et l\'identification des VMs',applySmbiosFromClusterConfig:'Appliquer les paramètres SMBIOS de la configuration du cluster',requiresRestart:'REDEMARRER',readOnly:'lecture seule',autoGenerated:'auto-généré',smbiosFormatHint:'Seulement les lettres et chiffres sont autorisés (A-Za-z0-9)',preview:'Aperçu',currentValue:'Valeur Actuelle',managedByProxmox:'géré par Proxmox',willBeAutoGenerated:'Serait généré automatiquement à la sauvegarde',forceConntrack:'Forcer l\'état de Conntrack',forceConntrackDesc:'Forcer la migration même si des entrées conntrack existent',containerNoLiveMigration:'Les conteneurs ne prennent pas en charge une migration de vie réelle véritable.',startingMigration:'Démarrage de la migration de',migrationStarted:'La migration a commencé!',migrationFailed:'La migration a échoué.',clone:'Cloner',console:'Console',openConsole:'Ouvrir la console',metrics:'Métriques',config:'Configuration',configuration:'Configuration',createVm:'Créer une nouvelle VM',createContainer:'Créer un nouveau conteneur',newVm:'Nouvelle VM',newContainer:'Nouveau conteneur',power:'Alimentation',snapshot:'Instantané',editSettings:'Paramètres',refreshData:'Actualiser',sshConsole:'Console SSH',noNodesAvailable:'Aucun nœud disponible. Veuillez patienter tandis que les données du cluster se chargent.',loadingStorage:'Chargement de la liste de stockage…',noIsoAvailable:'Aucune image ISO trouvée',noTemplateAvailable:'Aucun modèle trouvé',noStorageAvailable:'Aucun stockage disponible',noIsoStorage:'Aucun stockage avec du contenu ISO trouvé',ram:'Mémoire vive',cpu:'CPU',disk:'Disque',network:'Réseau',// VM Creation Wizard @@ -2312,7 +2312,7 @@ poolPermissions:'Permisos de pool',poolPermissionsDesc:'Asigne acceso a usuario o grupos a pools de recursos de Proxmos. Los permisos aplican para todas la VMs en el pool.',managePools:'Gestionar pools',poolManagerDesc:'Crear, editar y remover pools de recursos. Asigne VMs a pools para la gestión organizada de los permisos.',createPool:'Crear pool',editPool:'Editar pool',deletePool:'Remover pool',poolId:'ID de pool',poolIdRequired:'El ID de pool es requerimiento',poolIdHint:'Solo letras, números, guiones y guiones bajos',poolIdCannotChange:'No es posible cambiar el ID de pool',poolCreated:'Pool creado con éxito',poolUpdated:'Pool acualizado con éxito',poolDeleted:'Pool removido con éxito',confirmDeletePool:'¿Está seguro de querer remover este pool? ¡No podrá deshacerlo!',noPoolsYet:'Aún no hay pools definidos',createFirstPool:'Cree un primer pool para organizar las VMs',poolMembers:'Miembros',members:'miembros',addVmToPool:'Agregar VM al pool',assignToPool:"Asignar a pool",removeFromPool:'Remover del pool',vmAddedToPool:'VM agregada al pool',vmRemovedFromPool:'VM removida del pool',confirmRemoveVmFromPool:'¿Remover la VM del pool?',selectVmToAdd:'Seleccione una VM para agregarla a este pool',allVmsInPools:'Ya están en pools todas las VMs',optionalDescription:'Descripción opcional...',userPermissions:'Permisos de usuario',selectPool:'Seleccione el pool',noPools:'No se hallaron recursos de pool',noPoolPerms:'No hay permisos configurados en este pool',selectPoolFirst:'Seleccione un cluster y un pool para gestionar los permisos',addPoolPerm:'Agregar permisos de pool',editPoolPerm:'Editar permisos de pool',poolPermSaved:'Permisos de pool guardados',poolPermDeleted:'Permisos de pool removidos',refreshPools:'Refrescar información de pools de Proxmox',poolCacheRefreshed:'Caché de pools refrescado',confirmDeletePoolPerm:'¿Remover permisos?',subjectType:'Tipo',permissionsFor:'Permisos para',addPermission:'Agregar permisos',groupName:'Nombre de grupo',auditLog:'Registros de auditoría',auditLogDescription:'Todas las acciones de usuario de los últimos 90 días',noAuditLogs:'No hay registros de auditoría disponibles',action:'Acción',details:'Detalles',timestamp:'Marca de tiempo',ipAddress:'Dirección IP',userCreated:'Usuario creado',userUpdated:'Usuario actualizado',userDeleted:'Usuario removido',userLogin:'Entrar',userLogout:'Cerrar sesión',passwordChanged:'Contraseña cambiada',clusterAdded:'Cluster agregado',apiTokenWarningTitle:'Autenticación por token de API',apiTokenWarningDesc:'Sin una contraseña, las características de SSH no funcionan (HA, actualizaciones )" "Without a password, SSH features won\'t work (HA, actualizaciones graduales, SMBIOS, indicador de nodo). Recomendado: usar root@pam + contraseña — PegaProx crea un token de API automáticamente para compatibilidad con 2FA.',apiTokenRecommended:'Se recomienda cuando se activa 2FA.',apiTokenCreated:'Se creó un token de API con PVE — Se activó 2FA de manera segura',sshPasswordStillNeeded:'SSH continúa usando contraseña (HA, mantenimiento) - no la cambie',authModeToken:'API: Token (seguro para 2FA)',authModePassword:'API: Contraseña',sshAuthMode:'SSH: Contraseña',dontChangePvePassword:'No cambie la contraseña de PVE sin actualizarla aquí',clusterDeleted:'Cluster removido',clusterConfigChanged:'Configuración de cluster cambiada',vmStarted:'VM iniciada',vmStopped:'VM detenida',vmRestarted:'VM reiniciada',vmCreated:'VM creada',vmDeleted:'VM removida',vmCloned:'VM clonada',vmMigrated:'VM migrada',vmUnlocked:'VM desbloqueada',vmLocked:'VM bloqueada',unlockVm:'Desbloquear VM',lockReason:'Razón para el bloqueo',unlockWarning:'Desbloquear una VM durante una operación activa puede causar corrupción u otros efectos. Proceda solo si está seguro de que la operación ha sido cancelada o ha fallado.',vmBulkMigrated:'Migración en lote',vmConfigChanged:'Configuración de VM cambiada',vmSuspended:'VM suspendida',vmResumed:'VM recontinuada',vmDiskAdded:'Disco agregado',vmDiskRemoved:'Disco removido',vmDiskResized:'Disco cambiado de tamaño',vmDiskMoved:'Disco movido',vmNetworkAdded:'Network agregada',vmNetworkRemoved:'Network removida',vmNetworkUpdated:'Network actualizada',removeDiskConfirm:'Realmente remover el disco',detachDisk:'Desconectar',importDisk:'Importar disco',importDiskDesc:'Importar una imagen de disco preexistente desde el almacenamiento',selectImportStorage:'Seleccionar el almacenamiento fuente',selectDiskImage:'Seleccionar la imagen de disco',targetBus:'Bus destino',reassignOwner:'Reasignar dueño',reassignOwnerDesc:'Asignar el disco a una VM diferente',targetVm:'VM destino',diskReassigned:'Disco reasignado',diskImported:'Disco importado',noImportableDisks:'No se encontraron imágenes de disco importables',reassign:'Reasignar',import:'Importar',detachDiskConfirm:'¿Realmente desconectar el disco? Se hará inusable el disco.',diskDetached:'Disco desconectado',diskDeleted:'Disco removido',diskAdded:'Disco agregado',dataWillBeDeleted:'¡Los datos se removerán permanentemente!',removeNetworkConfirm:'Remover realmente la red',snapshotCreated:'Instantánea creada',snapshotDeleted:'Instantánea removida',snapshotRestored:'Instantánea restaurada',snapshotDesc:"Enfoque de clonar + migrar. Funciona con cualquier almacenamiento (LVM, dir, etc).",snapshotName:"Nombre de la instantánea",snapshotReplNote:"Crea un clon completo de la VM y lo migra al nodo destino. La réplica anterior se reemplaza en cada ejecución.",rollbackConfirm:'¿Reversar realmente a esta instantánea? ¡Esto no podría deshacerse!',rollbackStarted:"Reversión (Rollback) iniciada",rpo:"RPO",rpoDesc:"Objetivo de punto de recuperación (RPO) - tiempo desde la última replicación",replicationCreated:'Replicación creada',replicationDeleted:'Replicación removida',replicationTriggered:'Replicación disparada',replicationHint:"Cree una tarea de replicación para mantener los datos de las VMs sincronizados entre los nodos",replicationInfoDesc:"Mantenga los datos de las VMs sincronizados entre los nodos para failover y recuperación de desastres. Dos modos disponibles:",replicationInfoTitle:"Replicación de VMs",haEnabled:'HA activada',haDisabled:'HA desactivada',noFallbackHosts:'No se encontraron máquinas para retornar (¿Cluster de un único nodo?)',haVmAdded:'VM agregada a HA',haVmRemoved:'VM removida de HA',nodeMaintenanceEntered:'Se entró a modo mantenimiento',nodeMaintenanceExited:'Se ha salido de modo mantenimiento',nodeUpdateStarted:'Inició la actualización de nodo',twoFactorAuth:'Autenticación de dos factores (2FA)',twoFactorEnabled:'2FA activada',twoFactorDisabled:'2FA desactivada',enable2FA:'Activar 2FA',disable2FA:'Desactivar 2FA',setup2FA:'Configurar 2FA',scan2FACode:'Escanee el código QR con una app de autenticación',enter2FACode:'Entre el código de 6 dígitos',verify2FA:'Verificar',secretKey:'Llave secreta',twoFARequired:'Se requiere código 2FA',invalid2FACode:'Código 2FA inválido',force2FA:'Forzar 2FA',force2FADesc:'Requerir que todos los usuarios tengan 2FA para poder usar PegaProx.',force2FAHint:'Usuarios OIDC/Entra son exentos (usan la MFA de su proveedor de identidad). Los usuarios sin 2FA verán un díalogo de configuración al entrar.',force2FAExcludeAdmins:'Excluir las cuentas de administrador',force2FAExcludeAdminsDesc:'Los administradores pueden usar PegaProx sin 2FA',force2FASetupTitle:'Se requiere configuración de 2FA',force2FASetupDesc:'Su administrador ha activado autenticación con dos factores de manera obligatoria para todos los usuarios. Favor configure 2FA para continuar',resetPassword:'Resetear contraseña',passwordManagedExternally:'Su contraseña es administrada externamente.',passwordManagedExternallyHint:'Cambie su contraseña directamente en su servicio de directorio.',newPassword:'Nueva contraseña',confirmPassword:'Confirmar contraseña',currentPassword:'Contraseña actual',passwordsDoNotMatch:'Las contraseñas no concuerdan',passwordTooShort:'Las contraseñas deben ser de al menos 4 caracteres',passwordResetSuccess:'Contraseña cambiada con éxito',myProfile:'Mi perfil',security:'Seguridad',snapshotNotSupported:'Las instantáneas no están soportadas',snapshotWarnings:'Advertencias por instantáneas',filterByUser:'Filtrado por usuario',filterByAction:'Filtrado por acción',allUsers:'Todos los usuarios',allActions:'Todas las acciones',exportAuditLog:'Exportar',refreshAuditLog:'Refrescar',// Header / Encabezado addCluster:'Agregar cluster',pveClusterDesc:'Máquinas virtuales y contenedores',pbsDesc:'Gestión de los respaldos',vmwareDesc:'Infraestructura ESXi',addConnection:'Agregar conexión',connectionType:'Tipo de conexión',clusterManagement:'PegaProx Cluster Management for Proxmox VE',// Tabs / Pestañas overview:'Vista general',resources:'Recursos',datacenter:'Datacenter',settings:'Valores',of:'de',showing:'Mostrando',perPage:'Por página',loadingDatacenter:'Cargando datos de datacenter...',// Cluster -clusters:'Clústeres',noClusterSelected:'No hay un cluster seleccionado',allClustersOverview:'Vista general de los clústeres',allClusters:'Todos los clústeres',multiClusterSummary:'Resumen de todos los clústeres gestionados',clustersConnected:'Clústeres conectados',clusterOverview:'Vista general de cluster',clusterCpu:"CPU en el cluster",clusterMemory:"Memoria en el cluster",clusterServices:"Servicios de cluster",clusterStorage:"Almacenamiento en el cluster",vmsRunning:'VMs corriendo',vmsStopped:'VMs detenidas',noClustersConfigured:'No hay clústeres configurados',addClusterToStart:'Agregar un cluster para comenzar',clickClusterTip:'Seleccionar la fila de un cluster o seleccionar de la barra lateral para ver información detallada y manejar los recursos',health:'Salud',average:'Promedio',noDataAvailable:'No hay datos disponibles',clickToManage:'Clic para gestionar',sortBy:'Ordenar por',ungrouped:'Sin agrupar',alerts:'Alertas',updated:'Actualizado',justNow:'justo ahora',topResources:'Recursos más',highestCpuUsage:'Consumo más alto de CPU y de RAM en todos los clústeres',clickToOpenVm:'Clic para abrir la VM',selectCluster:'Seleccionar un cluster',addFirstCluster:'Agregar el primer cluster',connectCluster:'Conectar un cluster a PegaProx',clusterName:'Nombre del cluster',host:'Máquina',username:'Usuario',password:'Contraseña',passwordOrToken:'Contraseña / Token',apiTokenHint:'Para tokes de API: usuario@dominio!idtoken',sslVerification:'Verificación de SSL',connecting:'Conectando...',addNewCluster:'Agregar un nuevo cluster',testConnection:'Probar la conexión',testCleanup:"Limpiar prueba",deleteCluster:'Remover cluster',deleteClusterConfirm:'¿Remover realmente el cluster?',reconfigureCluster:'Reconfigurar cluster',reconfigureHint:'Ingrese su contraseña de PegaProx para verificar su identidad.',clusterReconfigured:'Cluster reconfigurado exitosamente',reconfigure:'Reconfigurar',clusterHealth:'Salud del cluster',clusterHealthTooltip:'Salud = 100 − (CPU×30% + RAM×30% + Almacenamiento×20% + Nodos fuera de línea×20%)\n\n80+: Excelente\n60–79: Buena\n40–59: Advertencia\n< 40: Crítica',nodeScoreTooltip:'Puntuación nodo = CPU% + RAM% (menor es mejor)\n\n< 100: Bueno (verde)\n100–150: Elevado (amarillo)\n> 150: Crítico (rojo)',excellent:'Excelente',good:'Buena',warning:'Advertencia',critical:'Crítica',nodesOnline:'Nodos en línea',nodeJoinHint:'Para agregar un nuevo nodo, ejecute en el nodo nuevo:',avgScore:'Puntos promedio',avgStorage:'Almac. promedio',avgCpu:'CPU promedio',avgRam:'RAM promedio',// Nodes / Nodos +clusters:'Clústeres',noClusterSelected:'No hay un cluster seleccionado',allClustersOverview:'Vista general de los clústeres',allClusters:'Todos los clústeres',multiClusterSummary:'Resumen de todos los clústeres gestionados',clustersConnected:'Clústeres conectados',clusterOverview:'Vista general de cluster',clusterCpu:"CPU en el cluster",clusterMemory:"Memoria en el cluster",clusterServices:"Servicios de cluster",clusterStorage:"Almacenamiento en el cluster",vmsRunning:'VMs corriendo',vmsStopped:'VMs detenidas',noClustersConfigured:'No hay clústeres configurados',addClusterToStart:'Agregar un cluster para comenzar',clickClusterTip:'Seleccionar la fila de un cluster o seleccionar de la barra lateral para ver información detallada y manejar los recursos',health:'Salud',average:'Promedio',noDataAvailable:'No hay datos disponibles',clickToManage:'Clic para gestionar',sortBy:'Ordenar por',ungrouped:'Sin agrupar',alerts:'Alertas',updated:'Actualizado',justNow:'justo ahora',topResources:'Recursos más',highestCpuUsage:'Consumo más alto de CPU y de RAM en todos los clústeres',clickToOpenVm:'Clic para abrir la VM',selectCluster:'Seleccionar un cluster',addFirstCluster:'Agregar el primer cluster',connectCluster:'Conectar un cluster a PegaProx',clusterName:'Nombre del cluster',host:'Máquina',username:'Usuario',password:'Contraseña',passwordOrToken:'Contraseña / Token',apiTokenHint:'Para tokes de API: usuario@dominio!idtoken',sslVerification:'Verificación de SSL',connecting:'Conectando...',addNewCluster:'Agregar un nuevo cluster',testConnection:'Probar la conexión',testCleanup:"Limpiar prueba",deleteCluster:'Remover cluster',deleteClusterConfirm:'¿Remover realmente el cluster?',reconfigureCluster:'Reconfigurar cluster',reconfigureHint:'Ingrese su contraseña de PegaProx para verificar su identidad.',clusterReconfigured:'Cluster reconfigurado exitosamente',reconfigure:'Reconfigurar',clusterHealth:'Salud del cluster',clusterHealthTooltip:'Salud = 100 − (CPU×30% + RAM×30% + Almacenamiento×20% + Nodos fuera de línea×20%)\nSin datos de almacenamiento: CPU×37,5% + RAM×37,5% + Fuera de línea×25%\n\n80+: Excelente\n60–79: Buena\n40–59: Advertencia\n< 40: Crítica',nodeScoreTooltip:'Puntuación nodo = CPU% + RAM% (menor es mejor)\n\n< 100: Bueno (verde)\n100–150: Elevado (amarillo)\n> 150: Crítico (rojo)',excellent:'Excelente',good:'Buena',warning:'Advertencia',critical:'Crítica',nodesOnline:'Nodos en línea',nodeJoinHint:'Para agregar un nuevo nodo, ejecute en el nodo nuevo:',avgScore:'Puntos promedio',avgStorage:'Almac. promedio',avgCpu:'CPU promedio',avgRam:'RAM promedio',// Nodes / Nodos nodes:'Nodos',node:'Nodo',loadingMetrics:'Cargando métricas...',connectionError:'Error de conexión',retry:'Reintentar',checkConnectionAndRetry:'Favor revise su conexión e intente nuevamente.',connectionTimeout:'Timeout - Proxmox inalcanzable',maintenance:'Mantenimiento',enterMaintenance:'Enter a modo mantenimiento',exitMaintenance:'Salir de modo mantenimiento',maintenanceMode:'Modo mantenimiento',update:'Actualizar',startUpdate:'Iniciar actualización',nodeConfig:'Configuración de nodo',cpuHistory:'Historia de CPU',ramHistory:'Historia de RAM',ramUsage:'Consumo de RAM',cpuUsage:'Consumo de CPU',diskUsage:'Consumo de disco',allocated:'asignado',showMore:'Mostrar más',showLess:'Mostrar menos',fixAvailable:"actualización disponible",installing:"Instalando...",inventoryOverview:"Vista General de Inventario",items:"ítems",recentItems:"Recientes",rename:"Renombrar",// VMs & Resources / VMs y recursos virtualMachines:'Máquinas virtuales',containers:'Contenedores',vm:'VM',lxc:'LXC',lxcContainer:'Contenedor LXC',container:'Contenedor',guests:'Huéspedes',start:'Iniciar',stop:'Detener',shutdown:'Concluir',reboot:'Reiniciar',forceStop:'Detener forzado',forceReset:'Reset forzado',forceStopConfirm:'¿forzar realmente la parada? ¡puede perder datos!',migrate:'Migrar',migrateVm:'Migrar VM',crossClusterMigration:'Migración Cross-Cluster',crossClusterMigrateDesc:'Migrar una VM a otro cluster Proxmox',crossClusterMigrate:'Migración Cross-Cluster',crossClusterStarted:'Migración Cross-Cluster iniciada',crossClusterFailed:'Migración Cross-Cluster fallida',crossClusterLB:'Balanceo de cargas (LB) Cross-Cluster',crossClusterLBEnabled:'Cross-Cluster LB activado',crossClusterLBDisabled:'Cross-Cluster LB desactivado',crossClusterLBThreshold:'Límite de puntaje',crossClusterLBInterval:'Intervalo de chequeo',crossClusterLBDryRun:'Simular (prueba en vacío)',crossClusterLBLastRun:'Última ejecución',crossClusterReplication:'Replicación Cross-Cluster',crossClusterReplicationDesc:'Replicar instantáneas de VM a otro cluster (DR)',groupOverview:'Vista general de group',groupSettings:'Valores de grupo',groupSettingsSaved:'Valores de grupo guardados',replicationSchedule:'Agendar',replicationRetention:'Retención',maxMigrations:'Migraciones máximas por ciclo',lbHistory:'Historia de balanceo',clusterScore:'Puntaje de cluster',noReplicationJobs:'No hay tareas de replicación',createReplicationJob:'Crear tarea de replicación',replicationStarted:'Replicación iniciada',replicationJobCreated:'Tarea de replicación creada',replicationJobDeleted:'Tarea de replicación removida',recentLbActions:'Acciones recientes de LB cross-cluster',noLbEvents:'No hay eventos de LB Cross-Cluster',enableCrossClusterLB:'Activar balanceo (LB) Cross-Cluster',lbDescription:'Migrar automáticamente VMs entre clústeres cuando los umbrales se excedan',dryRunMode:'Ejecución seca / Modo simulación',cpuThreshold:'Límite (%) de CPU',lbStatus:'Estado de balanceo',lbExplanation:'El balanceo de cargas Cross-Cluster monitorea el consumo de CPU y de RAM a través de todos los clústeres del grupo. Cuando un cluster excede el umbral configurado las VMs son migradas automáticamente a un cluster con menos carga',lbDryRunExplanation:'Active primero el modo de ejecución en vacío para revisar primero cuáles acciones deberán tomarse antes de activar migraciones en vivo',neverRun:'Nunca ejecutar',newCrossClusterReplication:'Nueva replicación Cross-Cluster',confirmDeleteXRepl:'¿remover la tarea de replicación?',addDrJob:'Agregar una tarea de DR',scheduleCron:'Agendar (cron)',targetStorageHint:'Nombre del almacenamiento en el cluster destino',maxMigrationsHint:'Límite de cuántas VMs se pueden mover por ciclo (1-5)',proxlbCredit:'ProxLB by gyptazy',proxlbCreditDesc:'Nuestra funcionalidad de balanceo de cargas se base en el excelente trabajo de ProxLB ¡Gracias especiales a gyptazy por crear y liberar el código de esta asombrosa herramienta!',xReplCreated:'Replicación Cross-Cluster creada',xReplDeleted:'Replicación Cross-Cluster removida',xReplStarted:'Replicación Cross-Cluster iniciada',xReplCreateFailed:'Falló la creación de la tarea',xReplDeleteFailed:'Falló la remoción de la tarea',xReplStartFailed:'Falló el iniciar',lastRunPrefix:'Última',runNow:'Ejecutar ahora',loadingCrossClusterResources:'Cargando el almacenamiento y las redes de todos los clústeres...',noCommonStorages:'No se encontró almacenamiento común a todos los clústeres',noCommonBridges:'No se encontró un puente común a todos los clústeres',selectBridge:'Seleccionar el puente...',crossClusterThresholdDesc:'Límite de CPU para el desbalance de cluster',crossClusterIntervalDesc:'Tiempo entre ciclos de chequeo',crossClusterMaxMigrationsDesc:'Máximo de migraciones por ciclo',commonStorageHint:'Solo los almacenamientos comunes al grupo',commonBridgeHint:'Solo los puentes comunes al grupo',includeContainers:'Incluir contenedores',includeContainersDesc:'Incluir los contenedores LXC en el balanceo Cross-Cluster',containerMigrationWarning:'Los contenedores son reiniciados durante las migraciones (interrupciones)',excludedVMsCrossCluster:'Excluir VMs/Contenedores',excludedVMsCrossClusterDesc:'VMs y contenedores excluidos del balanceo automático Cross-Cluster',clusterExcludedVMs:'VMs excluidas',noExcludedVMsInGroup:'No hay VMs excluidas',selectCluster:'Seleccionar el cluster...',selectClusterFirst:'Primero seleccione un cluster destino',simulationMode:'Modo simulación',sourceVm:'VM origen',sourceBridge:"Red (puente) origen",sourceStorage:"Almacenamiento origen",targetCluster:'Cluster destino',targetNode:'Nodo destino',targetStorage:'Almacenamiento destino',targetBridge:'Red (puente) destino',moveDisk:'Mover disco',resizeDisk:'Cambiar tamaño de disco',diskResized:'Disco cambiado',deleteSourceDisk:'Remover la fuente luego del movimiento',deleteSourceDiskWarning:'El disco original se mantendrá en el almacenamiento origen. Puede removerlo manualmente después.',move:'Mover',from:'desde',nativeProxmoxReplication:"Replicación Nativa (ZFS) de Proxmox",noNetworkInterfaces:'No hay interfaces de red',nameRequired:'Se requiere el nombre',newVmid:'Nuevo VM ID (opcional)',sameIdPlaceholder:'Vacío = mismo ID',liveMigrationOption:'Migración en vivo (la VM sigue corriendo)',deleteSourceAfter:'Remover la fuente luego de la migración',largeDiskWarning:'Se detectó un disco grande',largeDiskExplanation:'La migración de discos >100GB puede fallar con un código "401 Unauthorized" debido a timeouts del ticket WebSocket de Proxmox. El servidor usará migración fuera de línea de manera automática a menos que se force otra cosa.',forceOnlineMigration:'Forzar la migración en línea (puede fallar)',autoTokenInfo:'PegaProx crea tokens de API temporales automáticamente para la migración y los borra luego de completar',clusterReachableInfo:'Los clústeres deben poderse alcanzar entre ellos a través de la red. El usuario seleccionado debe tener permisos para crear tokens de API.',selectCluster:'Seleccionar el cluster...',selectNode:'Seleccionar el nodo',selectStorage:'Seleccionar el almacenamiento...',loadingNodes:'Cargando los nodos destino...',loadingStorageNetwork:'Cargando la red y el almacenamiento...',liveMigration:'Migración en vivo (sin interrupción)',on:'en',targetStorage:'Almacenamiento destino',sameAsSource:'Lo mismo que en la fuente',loadingStorages:'Cargando almacenamiento',free:'libre',withLocalDisks:'Con discos locales',withLocalDisksDesc:'Migrar discos locales al almacenamiento destino',localDisksDetected:'Esta VM tiene discos locales. La migración así requiere copiar datos de discos.',requiredForThisVm:'Se requiere para esta VM',cdDvdMounted:'La unidad CD/DVD está montada',cdDvdMigrationWarning:'La migración en vivo no es posible con un CD/DVD montado. Favor expulse el CD/DVD antes o intente una migración en frío',isoMounted:'ISO/CD-ROM montado',isoEjected:'CD-ROM expulsado',isoMigrationWarning:'La migración puede fallar si el ISO no está disponible en el nodo destino. Expulse el CD/DVD o asegúrese de que el ISO exista en almacenamiento compartido.',localStorage:'Local',bootOrderIssue:'Asunto de orden de arranque (boot)',bootOrderWarning:'La lista de arranque (boot) menciona discos no existentes',bootOrder:'Lista de inicio (boot)',dragToReorder:'Clic para alternar, arrastre para reorganizar',noBootDevices:'No se encontraron dispositivos de arranque',resizeDiskHint:'Aumentar en (p.ej. +10G) o un nuevo tamaño',// SMBIOS Settings / Valores de SMBIOS smbiosSettings:'Valores de SMBIOS',smbiosHint:'System Management BIOS - útil para el licenciamiento de Windows y para identificación de VMs',applySmbiosFromClusterConfig:'Aplicar valores de SMBIOS para configuración de cluster',requiresRestart:'REINICIO',readOnly:'solo-lectura',autoGenerated:'auto-generado',smbiosFormatHint:'Solo se permiten letras y números (A-Za-z0-9)',preview:'Vista previa',currentValue:'Valor actual',managedByProxmox:'gestionado por Proxmox',willBeAutoGenerated:'Se autogenerará al guardar',forceConntrack:'Forzar (estado Conntrack)',forceConntrackDesc:'Forzar la migración aún si se hallan entradas Conntrack',containerNoLiveMigration:'Los contenedores no soportan migración en vivo real',startingMigration:'Iniciando la migración de',migrationStarted:'Migración iniciada:',migrationFailed:'Migración fallida',clone:'Clonar',console:'Consola',openConsole:'Abrir la consola',metrics:'Métricas',config:'Configuración',configuration:'Configuración',createVm:'Crear una nueva VM',createContainer:'Crear un nuevo contenedor',newVm:'Nueva VM',newContainer:'Nuevo contenedor',power:'Energía',snapshot:'Instantánea',editSettings:'Valores',refreshData:'Refrescar',sshConsole:'Consola SSH',noNodesAvailable:'No hay nodos disponibles. Favor esperar a que carguen los valores de cluster',loadingStorage:'Cargando la lista de almacenamiento...',noIsoAvailable:'No se encontraron imágenes ISO',noTemplateAvailable:'No hay plantillas disponibles',noStorageAvailable:'No hay almacenamienot disponible',noIsoStorage:'No se encontró almacenamiento conteniendo ISOs',ram:'RAM',cpu:'CPU',disk:'Disco',network:'Red',networkMappings:"Mapeos de red",networkOverview:"Vista General de la Red",networking:"Redes",// VM Creation Wizard @@ -2402,7 +2402,7 @@ poolPermissions:'Permissões de Pool',poolPermissionsDesc:'Conceda a usuários ou grupos acesso a pools de recursos do Proxmox. As permissões se aplicam a todas as VMs dentro do pool.',managePools:'Gerenciar Pools',poolManagerDesc:'Crie, edite e exclua pools de recursos. Atribua VMs a pools para um gerenciamento de permissões organizado.',createPool:'Criar Pool',editPool:'Editar Pool',deletePool:'Excluir Pool',poolId:'ID do Pool',poolIdRequired:'O ID do Pool é obrigatório',poolIdHint:'Apenas letras, números, hifens e sublinhados',poolIdCannotChange:'O ID do Pool não pode ser alterado',poolCreated:'Pool criado com sucesso',poolUpdated:'Pool atualizado com sucesso',poolDeleted:'Pool excluído com sucesso',confirmDeletePool:'Tem certeza de que deseja excluir este pool? Isso não pode ser desfeito.',noPoolsYet:'Nenhum pool ainda',createFirstPool:'Crie seu primeiro pool para organizar as VMs',poolMembers:'Membros',members:'membros',addVmToPool:'Adicionar VM ao Pool',assignToPool:'Atribuir ao Pool',removeFromPool:'Remover do pool',vmAddedToPool:'VM adicionada ao pool',vmRemovedFromPool:'VM removida do pool',confirmRemoveVmFromPool:'Remover VM deste pool?',selectVmToAdd:'Selecione uma VM para adicionar a este pool',allVmsInPools:'Todas as VMs já estão em pools',optionalDescription:'Descrição opcional...',userPermissions:'Permissões de Usuário',selectPool:'Selecionar Pool',noPools:'Nenhum pool de recursos encontrado neste cluster',noPoolPerms:'Nenhuma permissão configurada para este pool',selectPoolFirst:'Selecione um cluster e um pool para gerenciar permissões',addPoolPerm:'Adicionar Permissão de Pool',editPoolPerm:'Editar Permissão de Pool',poolPermSaved:'Permissão de pool salva',poolPermDeleted:'Permissão de pool removida',refreshPools:'Atualizar pools do Proxmox',poolCacheRefreshed:'Cache de pools atualizado',confirmDeletePoolPerm:'Remover permissão?',subjectType:'Tipo',permissionsFor:'Permissões para',addPermission:'Adicionar Permissão',groupName:'Nome do Grupo',auditLog:'Log de Auditoria',auditLogDescription:'Todas as ações de usuários dos últimos 90 dias',noAuditLogs:'Nenhuma entrada de auditoria disponível',action:'Ação',details:'Detalhes',timestamp:'Data/Hora',ipAddress:'Endereço IP',userCreated:'Usuário criado',userUpdated:'Usuário atualizado',userDeleted:'Usuário excluído',userLogin:'Login',userLogout:'Logout',passwordChanged:'Senha alterada',clusterAdded:'Cluster adicionado',apiTokenWarningTitle:'Autenticação por Token de API',apiTokenWarningDesc:'Sem uma senha, os recursos de SSH não funcionarão (HA, Atualizações Rolantes, SMBIOS, Shell do Nó). Recomendado: Use root@pam + senha — o PegaProx cria automaticamente um token de API para compatibilidade com 2FA.',apiTokenRecommended:'Recomendado quando 2FA está habilitado',apiTokenCreated:'Token de API criado no PVE — o 2FA agora pode ser habilitado com segurança',sshPasswordStillNeeded:'O SSH ainda usa a senha (HA, manutenção) — não a altere',authModeToken:'API: Token (Seguro para 2FA)',authModePassword:'API: Senha',sshAuthMode:'SSH: Senha',dontChangePvePassword:'Não altere a senha do PVE sem atualizá-la aqui',clusterDeleted:'Cluster excluído',clusterConfigChanged:'Configuração do cluster alterada',vmStarted:'VM iniciada',vmStopped:'VM parada',vmRestarted:'VM reiniciada',vmCreated:'VM criada',vmDeleted:'VM excluída',vmCloned:'VM clonada',vmMigrated:'VM migrada',vmUnlocked:'VM desbloqueada',vmLocked:'VM bloqueada',unlockVm:'Desbloquear VM',lockReason:'Motivo do Bloqueio',unlockWarning:'Aviso: Desbloquear uma VM durante uma operação ativa pode causar corrupção de dados ou outros problemas. Prossiga apenas se tiver certeza de que a operação falhou ou foi cancelada.',vmBulkMigrated:'Migração em lote',vmConfigChanged:'Configuração da VM alterada',vmSuspended:'VM suspensa',vmResumed:'VM retomada',vmDiskAdded:'Disco adicionado',vmDiskRemoved:'Disco removido',vmDiskResized:'Disco redimensionado',vmDiskMoved:'Disco movido',vmNetworkAdded:'Rede adicionada',vmNetworkRemoved:'Rede removida',vmNetworkUpdated:'Rede atualizada',removeDiskConfirm:'Realmente remover disco',detachDisk:'Desacoplar',importDisk:'Importar Disco',importDiskDesc:'Importar imagem de disco existente do armazenamento',selectImportStorage:'Selecionar Armazenamento de Origem',selectDiskImage:'Selecionar Imagem de Disco',targetBus:'Barramento de Destino',reassignOwner:'Reatribuir Proprietário',reassignOwnerDesc:'Atribuir disco a uma VM diferente',targetVm:'VM de Destino',diskReassigned:'Disco reatribuído',diskImported:'Disco importado',noImportableDisks:'Nenhuma imagem de disco importável encontrada',reassign:'Reatribuir',import:'Importar',detachDiskConfirm:'Realmente desacoplar o disco? Ele se tornará um disco não utilizado.',diskDetached:'Disco desacoplado',diskDeleted:'Disco excluído',diskAdded:'Disco adicionado',dataWillBeDeleted:'Os dados serão excluídos permanentemente!',removeNetworkConfirm:'Realmente remover rede',snapshotCreated:'Snapshot criado',snapshotDeleted:'Snapshot excluído',snapshotRestored:'Snapshot restaurado',rollbackConfirm:'Deseja realmente reverter para este snapshot? Isso não pode ser desfeito!',replicationCreated:'Replicação criada',replicationDeleted:'Replicação excluída',replicationTriggered:'Replicação acionada',haEnabled:'HA habilitado',haDisabled:'HA desabilitado',noFallbackHosts:'Nenhum host de fallback encontrado (Cluster de Nó Único?)',haVmAdded:'VM adicionada ao HA',haVmRemoved:'VM removida do HA',nodeMaintenanceEntered:'Modo de manutenção ativado',nodeMaintenanceExited:'Modo de manutenção desativado',nodeUpdateStarted:'Atualização do nó iniciada',twoFactorAuth:'Autenticação de Dois Fatores',twoFactorEnabled:'2FA habilitado',twoFactorDisabled:'2FA desabilitado',enable2FA:'Habilitar 2FA',disable2FA:'Desabilitar 2FA',setup2FA:'Configurar 2FA',scan2FACode:'Escaneie o código QR com seu app autenticador',enter2FACode:'Digite o código de 6 dígitos',verify2FA:'Verificar',secretKey:'Chave Secreta',twoFARequired:'Código 2FA obrigatório',invalid2FACode:'Código 2FA inválido',force2FA:'Forçar 2FA',force2FADesc:'Exigir que todos os usuários configurem a Autenticação de Dois Fatores antes de poderem usar o PegaProx.',force2FAHint:'Usuários OIDC/Entra estão isentos (eles usam o MFA do Provedor de Identidade). Usuários sem 2FA verão um diálogo de configuração ao fazer login.',force2FAExcludeAdmins:'Excluir contas de administrador',force2FAExcludeAdminsDesc:'Administradores podem usar o PegaProx sem 2FA',force2FASetupTitle:'Configuração de 2FA Obrigatória',force2FASetupDesc:'Seu administrador tornou a Autenticação de Dois Fatores obrigatória para todos os usuários. Por favor, configure o 2FA para continuar.',resetPassword:'Redefinir Senha',passwordManagedExternally:'Sua senha é gerenciada externamente.',passwordManagedExternallyHint:'Altere sua senha diretamente no seu serviço de diretório.',passwordTooShort:'A senha deve ter pelo menos 4 caracteres',passwordResetSuccess:'Senha alterada com sucesso',myProfile:'Meu Perfil',security:'Segurança',snapshotNotSupported:'Snapshots não suportados',snapshotWarnings:'Avisos de snapshot',filterByUser:'Filtrar por usuário',filterByAction:'Filtrar por ação',allUsers:'Todos os Usuários',allActions:'Todas as Ações',exportAuditLog:'Exportar',refreshAuditLog:'Atualizar',// Header addCluster:'Adicionar Cluster',addXcpngPool:'Adicionar Pool XCP-ng (Tech Preview)',xcpngConnectHint:'Conecte-se ao host mestre do pool. A porta XAPI 443 é usada por padrão.',xcpngTechPreviewNote:'Alguns recursos podem ser limitados ou estar sujeitos a alterações.',pveClusterDesc:'Máquinas virtuais e containers',pbsDesc:'Gerenciamento de backup',vmwareDesc:'Infraestrutura ESXi',xcpngDesc:'XCP-ng / Xen Hypervisor (Tech Preview)',addConnection:'Adicionar Conexão',connectionType:'Tipo de Conexão',clusterManagement:'Gerenciamento de Cluster PegaProx para Proxmox VE',renameCluster:"RenombrarCluster",renameHint:"Dejar vacío para volver al nombre original",// Tabs overview:'Visão Geral',resources:'Recursos',datacenter:'Datacenter',settings:'Configurações',showing:'Mostrando',perPage:'Por página',loadingDatacenter:'Carregando dados do datacenter...',// Cluster -clusters:'Clusters',noClusterSelected:'Nenhum Cluster Selecionado',allClustersOverview:'Visão Geral de Todos os Clusters',multiClusterSummary:'Resumo de todos os clusters gerenciados',clustersConnected:'Clusters Conectados',clusterOverview:'Visão Geral do Cluster',vmsRunning:'VMs em Execução',vmsStopped:'VMs Paradas',noClustersConfigured:'Nenhum cluster configurado',addClusterToStart:'Adicione um cluster para começar',clickClusterTip:'Clique em uma linha de cluster ou selecione na barra lateral para ver informações detalhadas e gerenciar recursos.',health:'Saúde',average:'Média',noDataAvailable:'Nenhum dado disponível',clickToManage:'Clique para gerenciar',sortBy:'Ordenar por',alerts:'Alertas',updated:'Atualizado',justNow:'agora mesmo',topResources:'Principais Recursos',highestCpuUsage:'Maior uso de CPU e RAM em todos os clusters',clickToOpenVm:'Clique para abrir a VM',selectCluster:'Selecione um cluster da lista',addFirstCluster:'Adicionar Primeiro Cluster',connectCluster:'Conectar um cluster Proxmox ao PegaProx',clusterName:'Nome do Cluster',host:'Host',username:'Usuário',password:'Senha',passwordOrToken:'Senha / Token',apiTokenHint:'Para tokens de API: usuario@realm!tokenid',sslVerification:'Verificação SSL',connecting:'Conectando...',addNewCluster:'Adicionar Novo Cluster',testConnection:'Testar Conexão',deleteCluster:'Excluir Cluster',deleteClusterConfirm:'Realmente excluir cluster?',reconfigureCluster:'Reconfigurar Cluster',reconfigureHint:'Digite sua senha do PegaProx para verificar sua identidade.',clusterReconfigured:'Cluster reconfigurado com sucesso',reconfigure:'Reconfigurar',clusterHealth:'Saúde do Cluster',clusterHealthTooltip:'Saúde = 100 − (CPU×30% + RAM×30% + Armazenamento×20% + Nós offline×20%)\n\n80+: Excelente\n60–79: Bom\n40–59: Aviso\n< 40: Crítico',nodeScoreTooltip:'Pontuação nó = CPU% + RAM% (menor é melhor)\n\n< 100: Bom (verde)\n100–150: Elevado (amarelo)\n> 150: Crítico (vermelho)',excellent:'Excelente',good:'Bom',warning:'Aviso',critical:'Crítico',nodesOnline:'Nós Online',nodeJoinHint:'To add a new node, run on the new node:',// Mantido comando original +clusters:'Clusters',noClusterSelected:'Nenhum Cluster Selecionado',allClustersOverview:'Visão Geral de Todos os Clusters',multiClusterSummary:'Resumo de todos os clusters gerenciados',clustersConnected:'Clusters Conectados',clusterOverview:'Visão Geral do Cluster',vmsRunning:'VMs em Execução',vmsStopped:'VMs Paradas',noClustersConfigured:'Nenhum cluster configurado',addClusterToStart:'Adicione um cluster para começar',clickClusterTip:'Clique em uma linha de cluster ou selecione na barra lateral para ver informações detalhadas e gerenciar recursos.',health:'Saúde',average:'Média',noDataAvailable:'Nenhum dado disponível',clickToManage:'Clique para gerenciar',sortBy:'Ordenar por',alerts:'Alertas',updated:'Atualizado',justNow:'agora mesmo',topResources:'Principais Recursos',highestCpuUsage:'Maior uso de CPU e RAM em todos os clusters',clickToOpenVm:'Clique para abrir a VM',selectCluster:'Selecione um cluster da lista',addFirstCluster:'Adicionar Primeiro Cluster',connectCluster:'Conectar um cluster Proxmox ao PegaProx',clusterName:'Nome do Cluster',host:'Host',username:'Usuário',password:'Senha',passwordOrToken:'Senha / Token',apiTokenHint:'Para tokens de API: usuario@realm!tokenid',sslVerification:'Verificação SSL',connecting:'Conectando...',addNewCluster:'Adicionar Novo Cluster',testConnection:'Testar Conexão',deleteCluster:'Excluir Cluster',deleteClusterConfirm:'Realmente excluir cluster?',reconfigureCluster:'Reconfigurar Cluster',reconfigureHint:'Digite sua senha do PegaProx para verificar sua identidade.',clusterReconfigured:'Cluster reconfigurado com sucesso',reconfigure:'Reconfigurar',clusterHealth:'Saúde do Cluster',clusterHealthTooltip:'Saúde = 100 − (CPU×30% + RAM×30% + Armazenamento×20% + Nós offline×20%)\nSem dados de armazenamento: CPU×37,5% + RAM×37,5% + Offline×25%\n\n80+: Excelente\n60–79: Bom\n40–59: Aviso\n< 40: Crítico',nodeScoreTooltip:'Pontuação nó = CPU% + RAM% (menor é melhor)\n\n< 100: Bom (verde)\n100–150: Elevado (amarelo)\n> 150: Crítico (vermelho)',excellent:'Excelente',good:'Bom',warning:'Aviso',critical:'Crítico',nodesOnline:'Nós Online',nodeJoinHint:'To add a new node, run on the new node:',// Mantido comando original avgScore:'Pontuação Média',avgStorage:'Armaz. Médio',avgCpu:'CPU Média',avgRam:'RAM Média',// Nodes nodes:'Nós',loadingMetrics:'Carregando métricas...',connectionError:'Erro de Conexão',retry:'Repetir',checkConnectionAndRetry:'Por favor, verifique sua conexão e tente novamente.',connectionTimeout:'Tempo esgotado - Proxmox inacessível',maintenance:'Manutenção',enterMaintenance:'Ativar Modo de Manutenção',exitMaintenance:'Sair do Modo de Manutenção',maintenanceMode:'Modo de Manutenção',update:'Atualizar',startUpdate:'Iniciar Atualização',nodeConfig:'Configuração do Nó',cpuHistory:'Histórico de CPU',ramHistory:'Histórico de RAM',diskUsage:'Uso de Disco',allocated:'alocado',showMore:'Mostrar mais',showLess:'Mostrar menos',// VMs & Resources virtualMachines:'Máquinas Virtuais',containers:'Containers',vm:'VM',lxc:'LXC',lxcContainer:'Container LXC',container:'Container',guests:'Convidados',start:'Iniciar',stop:'Parar',shutdown:'Desligar',reboot:'Reiniciar',forceStop:'Forçar Parada',forceReset:'Forçar Reset',forceStopConfirm:'Deseja realmente forçar a parada? Isso pode causar perda de dados!',migrate:'Migrar',migrateVm:'Migrar VM',crossClusterMigration:'Migração entre Clusters',crossClusterMigrateDesc:'Migrar VM para outro cluster Proxmox',crossClusterMigrate:'Migrar entre Clusters',crossClusterStarted:'Migração entre clusters iniciada',crossClusterFailed:'Migração entre clusters falhou',crossClusterLB:'Balanceamento de Carga entre Clusters',crossClusterLBEnabled:'LB entre clusters habilitado',crossClusterLBDisabled:'LB entre clusters desabilitado',crossClusterLBThreshold:'Limiar de Pontuação',crossClusterLBInterval:'Intervalo de Verificação',crossClusterLBDryRun:'Simulação (Dry Run)',crossClusterLBLastRun:'Última Execução',crossClusterReplication:'Replicação entre Clusters',crossClusterReplicationDesc:'Replicar snapshots de VM para outro cluster (DR)',groupOverview:'Visão Geral do Grupo',groupSettings:'Configurações do Grupo',groupSettingsSaved:'Configurações do grupo salvas',replicationSchedule:'Agendamento',replicationRetention:'Retenção',maxMigrations:'Máx. Migrações por Ciclo',lbHistory:'Histórico de LB',clusterScore:'Pontuação do Cluster',noReplicationJobs:'Nenhum job de replicação entre clusters',createReplicationJob:'Criar Job de DR',replicationStarted:'Replicação iniciada',replicationJobCreated:'Job de replicação criado',replicationJobDeleted:'Job de replicação excluído',recentLbActions:'Ações recentes de LB entre clusters',noLbEvents:'Nenhum evento de LB entre clusters ainda',enableCrossClusterLB:'Habilitar Balanceamento de Carga entre Clusters',lbDescription:'Migrar automaticamente VMs entre clusters quando os limites de recursos forem excedidos',dryRunMode:'Modo Dry Run / Simulação',cpuThreshold:'Limiar de CPU (%)',lbStatus:'Status do Balanceamento de Carga',lbExplanation:'O Balanceamento de Carga entre Clusters monitora o uso de CPU e RAM em todos os clusters deste grupo. Quando um cluster excede o limiar configurado, as VMs são migradas automaticamente para um cluster menos carregado.',lbDryRunExplanation:'Habilite o modo Dry Run primeiro para revisar quais ações seriam tomadas antes de habilitar as migrações reais.',neverRun:'Nunca executado',newCrossClusterReplication:'Nova Replicação entre Clusters',confirmDeleteXRepl:'Excluir este job de replicação entre clusters?',addDrJob:'Adicionar Job de DR',scheduleCron:'Agendamento (cron)',targetStorageHint:'Nome do armazenamento no cluster de destino',maxMigrationsHint:'Limite de quantas VMs são movidas a cada ciclo de verificação (1-5)',proxlbCredit:'ProxLB por gyptazy',proxlbCreditDesc:'Nossa funcionalidade de balanceamento de carga é baseada no excelente trabalho do ProxLB. Agradecimentos especiais ao gyptazy por criar e abrir o código desta ferramenta incrível!',xReplCreated:'Replicação entre clusters criada',xReplDeleted:'Replicação entre clusters excluída',xReplStarted:'Replicação entre clusters iniciada',xReplCreateFailed:'Falha ao criar job',xReplDeleteFailed:'Falha ao excluir',xReplStartFailed:'Falha ao iniciar',lastRunPrefix:'Última',runNow:'Executar agora',loadingCrossClusterResources:'Carregando armazenamento/rede de todos os clusters...',noCommonStorages:'Nenhum armazenamento comum encontrado em todos os clusters',noCommonBridges:'Nenhuma bridge comum encontrada em todos os clusters',selectBridge:'Selecionar bridge...',crossClusterThresholdDesc:'Limiar de CPU para desequilíbrio de cluster (10-80%)',crossClusterIntervalDesc:'Tempo entre ciclos de verificação',crossClusterMaxMigrationsDesc:'Máx. migrações por ciclo de verificação',commonStorageHint:'Apenas armazenamentos disponíveis em todos os clusters deste grupo',commonBridgeHint:'Apenas bridges disponíveis em todos os clusters deste grupo',includeContainers:'Incluir Containers',includeContainersDesc:'Incluir containers (LXC) no balanceamento entre clusters',containerMigrationWarning:'Containers são reiniciados durante a migração (tempo de inatividade)',excludedVMsCrossCluster:'VMs/Containers Excluídos',excludedVMsCrossClusterDesc:'VMs e containers excluídos do balanceamento automático entre clusters',clusterExcludedVMs:'VMs Excluídas',noExcludedVMsInGroup:'Nenhuma VM excluída',selectClusterFirst:'Selecione um cluster de destino primeiro',simulationMode:'Modo de Simulação',sourceVm:'VM de Origem',targetCluster:'Cluster de Destino',targetNode:'Nó de Destino',targetStorage:'Armazenamento de Destino',targetBridge:'Rede de Destino (Bridge)',moveDisk:'Mover Disco',resizeDisk:'Redimensionar Disco',deleteSourceDisk:'Excluir origem após mover',deleteSourceDiskWarning:'O disco original permanecerá no armazenamento de origem. Você pode removê-lo manualmente depois.',move:'Mover',from:'de',noNetworkInterfaces:'Sem interfaces de rede',nameRequired:'Nome obrigatório',newVmid:'Novo VMID (opcional)',sameIdPlaceholder:'Vazio = mesmo ID',liveMigrationOption:'Live Migration (a VM continua rodando)',deleteSourceAfter:'Excluir VM de origem após migração',largeDiskWarning:'Disco Grande Detectado',largeDiskExplanation:'Live migration para discos >100GB pode falhar com "401 Unauthorized" devido ao timeout do ticket WebSocket do Proxmox. O servidor usará automaticamente a migração offline, a menos que seja forçado.',forceOnlineMigration:'Forçar migração online mesmo assim (pode falhar)',autoTokenInfo:'O PegaProx cria automaticamente tokens de API temporários para migração e os exclui após a conclusão.',clusterReachableInfo:'Os clusters devem ser capazes de se alcançar pela rede. O usuário configurado deve ter permissões para criar tokens de API.',selectNode:'Selecionar Nó',selectStorage:'Selecionar armazenamento...',loadingNodes:'Carregando nós de destino...',loadingStorageNetwork:'Carregando armazenamento/rede...',liveMigration:'Live Migration (sem tempo de inatividade)',on:'em',sameAsSource:'Mesmo que a origem',loadingStorages:'Carregando armazenamentos',free:'livre',withLocalDisks:'Com Discos Locais',withLocalDisksDesc:'Migrar discos locais para o armazenamento de destino',localDisksDetected:'Esta VM possui discos locais. A migração requer a cópia dos dados do disco.',requiredForThisVm:'Obrigatório para esta VM',cdDvdMounted:'Drive de CD/DVD Montado',cdDvdMigrationWarning:'Live migration não é possível com um CD/DVD montado. Por favor, ejete o CD/DVD primeiro ou use a migração offline.',isoMounted:'ISO/CD-ROM Montado',isoEjected:'CD-ROM Ejetado',isoMigrationWarning:'A migração pode falhar se a ISO não estiver disponível no nó de destino. Ejete o CD/DVD ou certifique-se de que a ISO existe em um armazenamento compartilhado.',localStorage:'Local',bootOrderIssue:'Problema na Ordem de Boot',bootOrderWarning:'A ordem de boot faz referência a discos inexistentes',bootOrder:'Ordem de Boot',dragToReorder:'Clique para alternar, use as setas para reordenar',noBootDevices:'Nenhum dispositivo de boot encontrado',resizeDiskHint:'Aumentar em (ex: +10G) ou novo tamanho',// SMBIOS Settings @@ -3277,8 +3277,9 @@ useEffect(()=>{if(!xReplForm.target_cluster||!authFetch)return;let cancelled=false;const fetchTargetResources=async()=>{setXReplLoadingResources(true);setXReplTargetStorages([]);setXReplTargetBridges([]);try{const nodesRes=await authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes`);if(!nodesRes||!nodesRes.ok||cancelled)return;const nodesData=await nodesRes.json();const onlineNode=(Array.isArray(nodesData)?nodesData:nodesData.nodes||[]).find(n=>n.status==='online');if(!onlineNode||cancelled)return;const nodeName=onlineNode.node||onlineNode.name;const[storRes,netRes]=await Promise.all([authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes/${nodeName}/storage`),authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes/${nodeName}/networks`)]);if(cancelled)return;if(storRes&&storRes.ok){const storData=await storRes.json();setXReplTargetStorages((Array.isArray(storData)?storData:storData.storages||[]).filter(s=>s.content&&(s.content.includes('images')||s.content.includes('rootdir'))));}if(netRes&&netRes.ok){const netData=await netRes.json();setXReplTargetBridges((Array.isArray(netData)?netData:netData.networks||[]).filter(n=>n.type==='bridge'||n.type==='OVSBridge'||n.source==='sdn'));}}catch(err){console.error('xrepl target resources:',err);}if(!cancelled)setXReplLoadingResources(false);};fetchTargetResources();return()=>{cancelled=true;};},[xReplForm.target_cluster]);// NS: Cross-cluster replication handlers const handleCreateXRepl=async()=>{try{const res=await authFetch(`${API_URL}/cross-cluster-replications`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({source_cluster:xReplForm.source_cluster,target_cluster:xReplForm.target_cluster,vmid:parseInt(xReplForm.vmid),vm_type:xReplForm.vm_type||'qemu',target_storage:xReplForm.target_storage,target_bridge:xReplForm.target_bridge,schedule:xReplForm.schedule,retention:parseInt(xReplForm.retention)||3})});if(res&&res.ok){if(addToast)addToast(t('xReplCreated')||'Replication job created','success');setShowCreateXRepl(false);setXReplForm({source_cluster:'',vmid:'',vm_type:'qemu',target_cluster:'',target_storage:'',target_bridge:'vmbr0',schedule:'0 */6 * * *',retention:3});await fetchXReplJobs();}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplCreateFailed')||'Failed to create replication job','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleDeleteXRepl=async jobId=>{if(!confirm(t('confirmDeleteXRepl')||'Delete this replication job?'))return;try{const res=await authFetch(`${API_URL}/cross-cluster-replications/${jobId}`,{method:'DELETE'});if(res&&res.ok){if(addToast)addToast(t('xReplDeleted')||'Replication job deleted','success');await fetchXReplJobs();}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplDeleteFailed')||'Failed to delete','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleRunXReplNow=async jobId=>{try{const res=await authFetch(`${API_URL}/cross-cluster-replications/${jobId}/run`,{method:'POST'});if(res&&res.ok){if(addToast)addToast(t('xReplStarted')||'Replication started','success');}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplStartFailed')||'Failed to start','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleSave=async()=>{if(!form.name.trim())return;setSaving(true);await onSave(form);setSaving(false);};// MK: tab config const tabs=[{id:'general',label:t('general')||'General',icon:Icons.Settings},{id:'lb',label:t('crossClusterLB')||'Cross-Cluster LB',icon:Icons.Scale},{id:'replication',label:t('crossClusterReplication')||'Replication',icon:Icons.Globe},{id:'info',label:t('info')||'Info',icon:Icons.Info}];return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4"},/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl w-full max-w-2xl max-h-[80vh] overflow-y-auto"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-5 border-b border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"w-10 h-10 rounded-lg flex items-center justify-center",style:{backgroundColor:(form.color||'#E86F2D')+'33'}},/*#__PURE__*/React.createElement(Icons.Folder,{className:"w-5 h-5",style:{color:form.color||'#E86F2D'}})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-semibold text-white"},t('groupSettings')||'Group Settings'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},group.name))),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-gray-400 hover:text-white transition-colors"},/*#__PURE__*/React.createElement(Icons.X,null))),/*#__PURE__*/React.createElement("div",{className:"flex border-b border-proxmox-border"},tabs.map(tab=>/*#__PURE__*/React.createElement("button",{key:tab.id,onClick:()=>setActiveTab(tab.id),className:`flex items-center gap-2 px-4 py-2.5 text-sm font-medium transition-colors whitespace-nowrap ${activeTab===tab.id?'text-proxmox-orange border-b-2 border-proxmox-orange bg-proxmox-dark/50':'text-gray-400 hover:text-white hover:bg-proxmox-dark/30'}`},/*#__PURE__*/React.createElement(tab.icon,{className:"w-4 h-4"}),tab.label))),/*#__PURE__*/React.createElement("div",{className:"p-5"},activeTab==='general'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('name')||'Name'," *"),/*#__PURE__*/React.createElement("input",{value:form.name,onChange:e=>setForm(p=>({...p,name:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange",placeholder:"Production Cluster Group"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('description')||'Description'),/*#__PURE__*/React.createElement("textarea",{value:form.description,onChange:e=>setForm(p=>({...p,description:e.target.value})),rows:3,className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange resize-none",placeholder:"Optional description for this group..."})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('color')||'Color'),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("input",{type:"color",value:form.color,onChange:e=>setForm(p=>({...p,color:e.target.value})),className:"w-10 h-10 rounded cursor-pointer border border-proxmox-border"}),/*#__PURE__*/React.createElement("input",{type:"text",value:form.color,onChange:e=>setForm(p=>({...p,color:e.target.value})),className:"w-28 px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm font-mono focus:outline-none focus:border-proxmox-orange",placeholder:"#E86F2D"}),/*#__PURE__*/React.createElement("div",{className:"flex gap-1.5"},['#E86F2D','#3B82F6','#22C55E','#EAB308','#8B5CF6','#EC4899'].map(c=>/*#__PURE__*/React.createElement("button",{key:c,onClick:()=>setForm(p=>({...p,color:c})),className:`w-6 h-6 rounded-full border-2 transition-all ${form.color===c?'border-white scale-110':'border-transparent hover:border-gray-500'}`,style:{backgroundColor:c}})))))),activeTab==='lb'&&/*#__PURE__*/React.createElement("div",{className:"space-y-5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white"},t('enableCrossClusterLB')||'Enable Cross-Cluster Load Balancing'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('lbDescription')||'Automatically migrate VMs between clusters when resource thresholds are exceeded')),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_lb_enabled,onChange:e=>setForm(p=>({...p,cross_cluster_lb_enabled:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-proxmox-orange rounded-full peer-focus:ring-2 peer-focus:ring-proxmox-orange/50 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),form.cross_cluster_lb_enabled&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex justify-end"},/*#__PURE__*/React.createElement("button",{onClick:handleXclbBalanceNow,disabled:xclbRunning,className:"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg bg-proxmox-orange/20 text-proxmox-orange hover:bg-proxmox-orange/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"},xclbRunning?React.createElement('span',{className:'w-3.5 h-3.5 border-2 border-proxmox-orange/40 border-t-proxmox-orange rounded-full animate-spin'}):React.createElement(Icons.RefreshCw,{className:'w-3.5 h-3.5'}),t('balanceNow')||'Balance Now')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-3 bg-yellow-500/5 border border-yellow-500/20 rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-4 h-4 text-yellow-400"}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"text-sm text-yellow-300"},t('dryRunMode')||'Dry Run / Simulation Mode'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},t('dryRunDesc')||'Log what would happen without actually migrating'))),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_dry_run,onChange:e=>setForm(p=>({...p,cross_cluster_dry_run:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-yellow-500 rounded-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),/*#__PURE__*/React.createElement(Slider,{label:t('cpuThreshold')||'CPU Threshold (%)',description:t('crossClusterThresholdDesc')||'CPU threshold for cluster imbalance (10-80%)',value:form.cross_cluster_threshold,onChange:v=>setForm(p=>({...p,cross_cluster_threshold:v})),min:10,max:80}),/*#__PURE__*/React.createElement(Slider,{label:t('checkInterval')||'Check Interval',description:t('crossClusterIntervalDesc')||'Time between check cycles',value:form.cross_cluster_interval,onChange:v=>setForm(p=>({...p,cross_cluster_interval:v})),min:300,max:3600,step:60,unit:"s"}),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetStorage')||'Target Storage'),loadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loadingCrossClusterResources')||'Loading...'):commonStorages.length>0?/*#__PURE__*/React.createElement("select",{value:form.cross_cluster_target_storage,onChange:e=>setForm(p=>({...p,cross_cluster_target_storage:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange"},/*#__PURE__*/React.createElement("option",{value:""},t('selectStorage')||'Select storage...'),commonStorages.map(s=>/*#__PURE__*/React.createElement("option",{key:s.storage,value:s.storage},s.storage," (",s.type,")"))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-yellow-400 py-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3 inline mr-1"}),t('noCommonStorages')||'No common storage found across all clusters'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-1"},t('commonStorageHint')||'Only storages available on all clusters')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetBridge')||'Target Bridge'),loadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loadingCrossClusterResources')||'Loading...'):commonBridges.length>0?/*#__PURE__*/React.createElement("select",{value:form.cross_cluster_target_bridge,onChange:e=>setForm(p=>({...p,cross_cluster_target_bridge:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange"},commonBridges.filter(b=>b.source!=='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"Local Bridges"},commonBridges.filter(b=>b.source!=='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface,b.comments?` - ${b.comments}`:''))),commonBridges.filter(b=>b.source==='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"SDN VNets"},commonBridges.filter(b=>b.source==='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface," - ",b.zone||'SDN',b.alias?` (${b.alias})`:'')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-yellow-400 py-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3 inline mr-1"}),t('noCommonBridges')||'No common bridge found across all clusters'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-1"},t('commonBridgeHint')||'Only bridges available on all clusters'))),/*#__PURE__*/React.createElement(Slider,{label:t('maxMigrations')||'Max Migrations per Cycle',description:t('crossClusterMaxMigrationsDesc')||'Max migrations per check cycle',value:form.cross_cluster_max_migrations,onChange:v=>setForm(p=>({...p,cross_cluster_max_migrations:v})),min:1,max:5,step:1,unit:""}),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-3 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"text-sm text-white"},t('includeContainers')||'Include Containers'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('includeContainersDesc')||'Include containers (LXC) in cross-cluster balancing'),form.cross_cluster_include_containers&&/*#__PURE__*/React.createElement("p",{className:"text-xs text-yellow-400 mt-1 flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3"}),t('containerMigrationWarning')||'Containers are restarted during migration (downtime)')),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer ml-4"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_include_containers,onChange:e=>setForm(p=>({...p,cross_cluster_include_containers:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-proxmox-orange rounded-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-1"},t('excludedVMsCrossCluster')||'Excluded VMs/Containers'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mb-3"},t('excludedVMsCrossClusterDesc')||'VMs and containers excluded from automatic cross-cluster balancing'),loadingExcludedVMs?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loading'),"..."):groupClusters.filter(c=>c.connected).length===0?/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-600 py-2"},t('noExcludedVMsInGroup')||'No VMs excluded'):/*#__PURE__*/React.createElement("div",{className:"space-y-2"},groupClusters.filter(c=>c.connected).map(cluster=>{const excluded=excludedVMsByCluster[cluster.id]||[];const allVMs=allVMsByCluster[cluster.id]||[];const excludedIds=excluded.map(v=>v.vmid);const available=allVMs.filter(vm=>!excludedIds.includes(vm.vmid));const isExpanded=expandedClusters[cluster.id];return/*#__PURE__*/React.createElement("div",{key:cluster.id,className:"border border-proxmox-border rounded-lg overflow-hidden"},/*#__PURE__*/React.createElement("button",{onClick:()=>setExpandedClusters(prev=>({...prev,[cluster.id]:!prev[cluster.id]})),className:"w-full flex items-center justify-between p-2.5 bg-proxmox-dark/50 hover:bg-proxmox-dark text-left"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Server,{className:"w-3.5 h-3.5 text-proxmox-orange"}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white"},cluster.name),excluded.length>0&&/*#__PURE__*/React.createElement("span",{className:"text-xs bg-red-500/20 text-red-400 px-1.5 py-0.5 rounded"},excluded.length)),/*#__PURE__*/React.createElement(Icons.ChevronDown,{className:`w-4 h-4 text-gray-500 transition-transform ${isExpanded?'rotate-180':''}`})),isExpanded&&/*#__PURE__*/React.createElement("div",{className:"p-2.5 space-y-2 border-t border-proxmox-border"},excluded.length>0?/*#__PURE__*/React.createElement("div",{className:"space-y-1"},excluded.map(vm=>/*#__PURE__*/React.createElement("div",{key:vm.vmid,className:"flex items-center justify-between bg-proxmox-dark rounded px-2.5 py-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Monitor,{className:"w-3.5 h-3.5 text-red-400"}),/*#__PURE__*/React.createElement("span",{className:"text-sm"},vm.name||`VM ${vm.vmid}`),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},"(",vm.vmid,")")),/*#__PURE__*/React.createElement("button",{onClick:()=>includeVM(cluster.id,vm.vmid),className:"text-xs text-green-400 hover:text-green-300"},t('include')||'Include')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-600 p-2"},t('noExcludedVMsInGroup')||'No VMs excluded'),available.length>0&&/*#__PURE__*/React.createElement("div",{className:"flex gap-2 mt-1"},/*#__PURE__*/React.createElement("select",{id:`xclb-exclude-${cluster.id}`,className:"flex-1 bg-proxmox-dark border border-proxmox-border rounded px-2 py-1.5 text-sm",defaultValue:""},/*#__PURE__*/React.createElement("option",{value:"",disabled:true},t('selectVMToExclude')||'Select VM to exclude...'),available.map(vm=>/*#__PURE__*/React.createElement("option",{key:vm.vmid,value:vm.vmid},vm.name||`VM ${vm.vmid}`," (",vm.vmid,") - ",vm.node))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const select=document.getElementById(`xclb-exclude-${cluster.id}`);const vmid=parseInt(select?.value);if(!vmid)return;const vm=available.find(v=>v.vmid===vmid);excludeVM(cluster.id,vmid,vm?.name);select.value='';},className:"px-2.5 py-1.5 bg-red-500/20 text-red-400 rounded hover:bg-red-500/30 text-xs flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.Ban,{className:"w-3.5 h-3.5"}),t('exclude')||'Exclude'))));}))))),activeTab==='replication'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Globe,{className:"w-4 h-4 text-proxmox-orange"}),t('crossClusterReplication')||'Cross-Cluster Replication'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('crossClusterReplicationDesc')||'Replicate VM snapshots to another cluster (DR)')),groupClusters.length>=2&&/*#__PURE__*/React.createElement("button",{onClick:()=>setShowCreateXRepl(true),className:"flex items-center gap-1.5 px-3 py-1.5 bg-proxmox-orange/10 text-proxmox-orange rounded-lg text-xs hover:bg-proxmox-orange/20 transition-colors"},/*#__PURE__*/React.createElement(Icons.Plus,{className:"w-3.5 h-3.5"}),t('addDrJob')||'Add DR Job')),groupClusters.length<2?/*#__PURE__*/React.createElement("div",{className:"text-center py-8 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-8 h-8 mx-auto mb-2 text-gray-600"}),t('needTwoClusters')||'At least 2 clusters needed for cross-cluster replication'):xReplLoading?/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 py-8 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loading'),"..."):xReplJobs.length===0&&!showCreateXRepl?/*#__PURE__*/React.createElement("div",{className:"text-center py-8 text-gray-500 text-sm"},t('noReplicationJobs')||'No replication jobs configured'):/*#__PURE__*/React.createElement("div",{className:"space-y-2"},xReplJobs.map(job=>{const srcCluster=groupClusters.find(c=>c.id===job.source_cluster);const tgtCluster=groupClusters.find(c=>c.id===job.target_cluster);return/*#__PURE__*/React.createElement("div",{key:job.id,className:"bg-proxmox-dark rounded-lg p-3 flex items-center justify-between border border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex-1 min-w-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-wrap"},/*#__PURE__*/React.createElement("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${job.enabled?'bg-green-500':'bg-gray-500'}`}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white truncate"},srcCluster?.name||job.source_cluster),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3.5 h-3.5 text-gray-500 flex-shrink-0"}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white truncate"},tgtCluster?.name||job.target_cluster),/*#__PURE__*/React.createElement("span",{className:"text-xs bg-proxmox-dark px-1.5 py-0.5 rounded text-gray-400 border border-proxmox-border"},job.vm_type==='lxc'?'CT':'VM'," ",job.vmid)),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 mt-1 flex items-center gap-2 flex-wrap"},/*#__PURE__*/React.createElement("span",null,job.schedule),/*#__PURE__*/React.createElement("span",null,"\xB7"),/*#__PURE__*/React.createElement("span",null,job.target_storage||'default'),job.last_run&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("span",null,"\xB7"),/*#__PURE__*/React.createElement("span",null,t('lastRunPrefix')||'Last',": ",new Date(job.last_run).toLocaleString())),job.last_status&&/*#__PURE__*/React.createElement("span",{className:job.last_status==='OK'?'text-green-400':'text-red-400'},job.last_status),job.last_error&&/*#__PURE__*/React.createElement("span",{className:"text-red-400"},job.last_error))),/*#__PURE__*/React.createElement("div",{className:"flex gap-1 flex-shrink-0 ml-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>handleRunXReplNow(job.id),className:"p-1.5 rounded hover:bg-green-500/10 text-gray-400 hover:text-green-400",title:t('runNow')||'Run now'},/*#__PURE__*/React.createElement(Icons.Play,{className:"w-3.5 h-3.5"})),/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteXRepl(job.id),className:"p-1.5 rounded hover:bg-red-500/10 text-gray-400 hover:text-red-400",title:t('delete')||'Delete'},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-3.5 h-3.5"}))));})),showCreateXRepl&&groupClusters.length>=2&&/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark border border-proxmox-border rounded-lg p-4"},/*#__PURE__*/React.createElement("h5",{className:"text-sm font-medium text-white mb-3"},t('newCrossClusterReplication')||'New Cross-Cluster Replication'),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('sourceCluster')||'Source Cluster'),/*#__PURE__*/React.createElement("select",{value:xReplForm.source_cluster,onChange:e=>setXReplForm(f=>({...f,source_cluster:e.target.value,vmid:'',vm_type:'qemu'})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectCluster')||'Select cluster...'),groupClusters.filter(c=>c.connected).map(c=>/*#__PURE__*/React.createElement("option",{key:c.id,value:c.id},c.name)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},"VM"),xReplLoadingVMs?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.source_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.vmid,onChange:e=>{const vmid=e.target.value;const vm=xReplSourceVMs.find(v=>String(v.vmid)===vmid);setXReplForm(f=>({...f,vmid,vm_type:vm?.type||'qemu'}));},className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectVM')||'Select VM...'),xReplSourceVMs.map(vm=>/*#__PURE__*/React.createElement("option",{key:vm.vmid,value:vm.vmid},vm.name||`VM ${vm.vmid}`," (",vm.vmid,") - ",vm.type==='lxc'?'CT':'VM'))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a source cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetCluster')||'Target Cluster'),/*#__PURE__*/React.createElement("select",{value:xReplForm.target_cluster,onChange:e=>setXReplForm(f=>({...f,target_cluster:e.target.value,target_storage:'',target_bridge:'vmbr0'})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectCluster')||'Select cluster...'),groupClusters.filter(c=>c.connected&&c.id!==xReplForm.source_cluster).map(c=>/*#__PURE__*/React.createElement("option",{key:c.id,value:c.id},c.name)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetStorage')||'Target Storage'),xReplLoadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.target_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.target_storage,onChange:e=>setXReplForm(f=>({...f,target_storage:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectStorage')||'Select storage...'),xReplTargetStorages.map(s=>/*#__PURE__*/React.createElement("option",{key:s.storage,value:s.storage},s.storage," (",s.type,")"))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a target cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetBridge')||'Target Bridge'),xReplLoadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.target_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.target_bridge,onChange:e=>setXReplForm(f=>({...f,target_bridge:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},xReplTargetBridges.filter(b=>b.source!=='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"Local Bridges"},xReplTargetBridges.filter(b=>b.source!=='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface,b.comments?` - ${b.comments}`:''))),xReplTargetBridges.filter(b=>b.source==='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"SDN VNets"},xReplTargetBridges.filter(b=>b.source==='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface," - ",b.zone||'SDN',b.alias?` (${b.alias})`:'')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a target cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('scheduleCron')||'Schedule (Cron)'),/*#__PURE__*/React.createElement("input",{type:"text",value:xReplForm.schedule,onChange:e=>setXReplForm(f=>({...f,schedule:e.target.value})),placeholder:"0 */6 * * *",className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('replicationRetention')||'Retention'),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",max:"30",value:xReplForm.retention,onChange:e=>setXReplForm(f=>({...f,retention:parseInt(e.target.value)||1})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"}))),/*#__PURE__*/React.createElement("div",{className:"flex gap-2 mt-3"},/*#__PURE__*/React.createElement("button",{onClick:handleCreateXRepl,disabled:!xReplForm.source_cluster||!xReplForm.vmid||!xReplForm.target_cluster,className:"px-3 py-1.5 bg-proxmox-orange text-white rounded-lg text-sm hover:bg-proxmox-orange/90 transition-colors disabled:opacity-50"},t('create')||'Create'),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowCreateXRepl(false),className:"px-3 py-1.5 bg-proxmox-dark border border-proxmox-border text-gray-300 rounded-lg text-sm hover:bg-proxmox-darker transition-colors"},t('cancel')||'Cancel'))),Object.keys(nativeReplByCluster).length>0&&/*#__PURE__*/React.createElement("div",{className:"mt-6"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mb-3"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 text-purple-400"}),/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white"},t('nativeProxmoxReplication')||'Native Proxmox Replication (ZFS)')),/*#__PURE__*/React.createElement("div",{className:"space-y-3"},Object.entries(nativeReplByCluster).map(([cid,jobs])=>{const cluster=groupClusters.find(c=>c.id===cid);return/*#__PURE__*/React.createElement("div",{key:cid,className:"bg-proxmox-dark rounded-lg border border-proxmox-border overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"px-3 py-2 border-b border-proxmox-border bg-purple-500/5"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-medium text-purple-300"},cluster?.name||cid)),jobs.map((job,idx)=>{const hasErr=job.fail_count>0||job.error;const lastSync=job.last_sync?new Date(job.last_sync*1000).toLocaleString():'-';return/*#__PURE__*/React.createElement("div",{key:job.id||idx,className:"px-3 py-2 flex items-center justify-between border-b border-proxmox-border/50 last:border-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-wrap min-w-0"},/*#__PURE__*/React.createElement("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${job.disable?'bg-gray-500':hasErr?'bg-red-500':'bg-green-500'}`}),/*#__PURE__*/React.createElement("span",{className:"text-xs bg-proxmox-dark px-1.5 py-0.5 rounded text-gray-400 border border-proxmox-border"},"VM ",job.guest),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},job.source||'?'),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3 h-3 text-gray-600 flex-shrink-0"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},job.target),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600 font-mono"},job.schedule)),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-shrink-0 ml-2"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600"},lastSync),job.duration!=null&&/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600 font-mono"},job.duration.toFixed(1),"s")));}));})))),activeTab==='info'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-3"},t('groupInfo')||'Group Information'),/*#__PURE__*/React.createElement("div",{className:"space-y-2 text-sm"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('groupId')||'Group ID'),/*#__PURE__*/React.createElement("span",{className:"text-white font-mono text-xs"},group.id)),/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('created')||'Created'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.created?new Date(group.created).toLocaleString():'-')),group.tenant_id&&/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('tenant')||'Tenant'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.tenant_id)))),group.cross_cluster_lb_enabled&&/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-3"},t('lbStatus')||'Load Balancing Status'),/*#__PURE__*/React.createElement("div",{className:"space-y-2 text-sm"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('lastRun')||'Last LB Run'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.last_lb_run?new Date(group.last_lb_run).toLocaleString():t('neverRun')||'Never run')),/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('mode')||'Mode'),/*#__PURE__*/React.createElement("span",{className:group.cross_cluster_dry_run?'text-yellow-400':'text-green-400'},group.cross_cluster_dry_run?t('simulation')||'Simulation':t('active')||'Active')))),/*#__PURE__*/React.createElement("div",{className:"p-4 bg-blue-500/5 border border-blue-500/20 rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-start gap-3"},/*#__PURE__*/React.createElement(Icons.Info,{className:"w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5"}),/*#__PURE__*/React.createElement("div",{className:"text-sm text-gray-400"},/*#__PURE__*/React.createElement("p",{className:"mb-2"},t('lbExplanation')||'Cross-Cluster Load Balancing monitors CPU and RAM usage across all clusters in this group. When a cluster exceeds the configured threshold, VMs are automatically migrated to a less loaded cluster.'),/*#__PURE__*/React.createElement("p",null,t('lbDryRunExplanation')||'Enable Dry Run mode first to review what actions would be taken before enabling live migrations.')))))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-end gap-3 p-5 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-sm text-gray-400 hover:text-white transition-colors"},t('cancel')||'Cancel'),/*#__PURE__*/React.createElement("button",{onClick:handleSave,disabled:saving||!form.name.trim(),className:"px-5 py-2 bg-proxmox-orange hover:bg-orange-600 disabled:opacity-50 rounded-lg text-sm font-medium text-white transition-colors flex items-center gap-2"},saving?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"})," ",t('saving')||'Saving...'):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement(Icons.Save,{className:"w-4 h-4"})," ",t('save')||'Save')))));}// Cluster Health Widget -function ClusterHealth({metrics,isCorporate}){const{t}=useTranslation();const nodes=Object.entries(metrics).filter(([k,m])=>m&&typeof m==='object'&&k!=='error'&&k!=='offline');if(nodes.length===0)return null;const avgCpu=nodes.reduce((acc,[,m])=>acc+(m.cpu_percent??0),0)/nodes.length;const avgMem=nodes.reduce((acc,[,m])=>acc+(m.mem_percent??0),0)/nodes.length;const avgDisk=nodes.reduce((acc,[,m])=>acc+(m.disk_percent??0),0)/nodes.length;const onlineNodes=nodes.filter(([,m])=>m.status==='online'&&!m.maintenance_mode).length;const maintenanceNodes=nodes.filter(([,m])=>m.maintenance_mode).length;const offlineNodes=nodes.length-onlineNodes-maintenanceNodes;const healthScore=Math.max(0,100-(avgCpu*0.3+avgMem*0.3+avgDisk*0.2+(nodes.length>0?offlineNodes/nodes.length*100*0.2:0)));const healthLabel=healthScore>=80?t('excellent'):healthScore>=60?t('good'):healthScore>=40?t('warning'):t('critical');const healthColor=healthScore>=80?'#22c55e':healthScore>=60?'#84cc16':healthScore>=40?'#eab308':'#ef4444';// LW: Feb 2026 - corporate compact variant (Clarity dark theme) -const corpHealthColor=healthScore>=80?'#60b515':healthScore>=60?'#60b515':healthScore>=40?'#efc006':'#f54f47';if(isCorporate){return/*#__PURE__*/React.createElement("div",{className:"p-3",style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)'},title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-[11px] font-semibold uppercase tracking-wider mb-2",style:{color:'var(--corp-text-muted)'}},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4 flex-wrap"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-20 h-2 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${healthScore}%`,backgroundColor:corpHealthColor,borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-medium",style:{color:'var(--color-text)'}},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-[11px]",style:{color:'var(--corp-text-muted)'}},healthLabel)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},t('nodesOnline'),": ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},onlineNodes,"/",nodes.length)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"CPU: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},avgCpu.toFixed(1),"%")),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"RAM: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},avgMem.toFixed(1),"%")),maintenanceNodes>0&&/*#__PURE__*/React.createElement("span",{className:"text-[12px] flex items-center gap-1",style:{color:'var(--color-warning)'}},/*#__PURE__*/React.createElement(Icons.Wrench,{className:"w-3 h-3"})," ",maintenanceNodes," ",t('maintenance'))));}return/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl p-5",title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4"},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center mb-6"},/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement("svg",{viewBox:"0 0 100 100",className:"w-32 h-32"},/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#30363D",strokeWidth:"8"}),/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:healthColor,strokeWidth:"8",strokeLinecap:"round",strokeDasharray:`${healthScore*2.83} 283`,transform:"rotate(-90 50 50)",className:"transition-all duration-1000"})),/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 flex flex-col items-center justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-2xl font-bold text-white"},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-400"},healthLabel)))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},onlineNodes,"/",nodes.length),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('nodesOnline'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgDisk.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgStorage'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgCpu.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgCpu'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgMem.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgRam')))),maintenanceNodes>0&&/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-4 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 text-yellow-400"},/*#__PURE__*/React.createElement(Icons.Wrench,null),/*#__PURE__*/React.createElement("span",{className:"text-sm font-medium"},maintenanceNodes," Node(s) ",t('maintenance')))));}// Create Snapshot Modal - NS: Feb 2026 enhanced with efficient mode +function ClusterHealth({metrics,isCorporate}){const{t}=useTranslation();const nodes=Object.entries(metrics).filter(([k,m])=>m&&typeof m==='object'&&k!=='error'&&k!=='offline');if(nodes.length===0)return null;const avgCpu=nodes.reduce((acc,[,m])=>acc+(m.cpu_percent??0),0)/nodes.length;const avgMem=nodes.reduce((acc,[,m])=>acc+(m.mem_percent??0),0)/nodes.length;const diskNodes=nodes.filter(([,m])=>m.disk_percent!=null);const avgDisk=diskNodes.length>0?diskNodes.reduce((acc,[,m])=>acc+m.disk_percent,0)/diskNodes.length:null;const onlineNodes=nodes.filter(([,m])=>m.status==='online'&&!m.maintenance_mode).length;const maintenanceNodes=nodes.filter(([,m])=>m.maintenance_mode).length;const offlineNodes=nodes.length-onlineNodes-maintenanceNodes;const offlineRatio=nodes.length>0?offlineNodes/nodes.length*100:0;// When disk stats unavailable (e.g. XCP-ng), re-normalize: CPU 37.5%, RAM 37.5%, Offline 25% +const healthScore=avgDisk!=null?Math.max(0,100-(avgCpu*0.3+avgMem*0.3+avgDisk*0.2+offlineRatio*0.2)):Math.max(0,100-(avgCpu*0.375+avgMem*0.375+offlineRatio*0.25));const healthLabel=healthScore>=80?t('excellent'):healthScore>=60?t('good'):healthScore>=40?t('warning'):t('critical');const healthColor=healthScore>=80?'#22c55e':healthScore>=60?'#84cc16':healthScore>=40?'#eab308':'#ef4444';// LW: Feb 2026 - corporate compact variant (Clarity dark theme) +const corpHealthColor=healthScore>=80?'#60b515':healthScore>=60?'#60b515':healthScore>=40?'#efc006':'#f54f47';if(isCorporate){return/*#__PURE__*/React.createElement("div",{className:"p-3",style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)'},title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-[11px] font-semibold uppercase tracking-wider mb-2",style:{color:'var(--corp-text-muted)'}},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4 flex-wrap"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-20 h-2 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${healthScore}%`,backgroundColor:corpHealthColor,borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-medium",style:{color:'var(--color-text)'}},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-[11px]",style:{color:'var(--corp-text-muted)'}},healthLabel)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},t('nodesOnline'),": ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},onlineNodes,"/",nodes.length)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"CPU: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},avgCpu.toFixed(1),"%")),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"RAM: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},avgMem.toFixed(1),"%")),maintenanceNodes>0&&/*#__PURE__*/React.createElement("span",{className:"text-[12px] flex items-center gap-1",style:{color:'var(--color-warning)'}},/*#__PURE__*/React.createElement(Icons.Wrench,{className:"w-3 h-3"})," ",maintenanceNodes," ",t('maintenance'))));}return/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl p-5",title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4"},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center mb-6"},/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement("svg",{viewBox:"0 0 100 100",className:"w-32 h-32"},/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#30363D",strokeWidth:"8"}),/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:healthColor,strokeWidth:"8",strokeLinecap:"round",strokeDasharray:`${healthScore*2.83} 283`,transform:"rotate(-90 50 50)",className:"transition-all duration-1000"})),/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 flex flex-col items-center justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-2xl font-bold text-white"},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-400"},healthLabel)))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},onlineNodes,"/",nodes.length),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('nodesOnline'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgDisk!=null?`${avgDisk.toFixed(1)}%`:'N/A'),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgStorage'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgCpu.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgCpu'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgMem.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgRam')))),maintenanceNodes>0&&/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-4 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 text-yellow-400"},/*#__PURE__*/React.createElement(Icons.Wrench,null),/*#__PURE__*/React.createElement("span",{className:"text-sm font-medium"},maintenanceNodes," Node(s) ",t('maintenance')))));}// Create Snapshot Modal - NS: Feb 2026 enhanced with efficient mode function CreateSnapshotModal({isQemu,onSubmit,onClose,loading,efficientInfo}){const{t}=useTranslation();const[snapname,setSnapname]=useState(`snap_${Date.now()}`);const[description,setDescription]=useState('');const[vmstate,setVmstate]=useState(false);const[mode,setMode]=useState('standard');const[snapSizeGb,setSnapSizeGb]=useState(efficientInfo?.recommended_snap_size_gb||10);const isEfficient=mode==='efficient';const canEfficient=efficientInfo?.eligible;const handleSubmit=()=>{if(!snapname.trim())return;onSubmit(snapname.trim(),description,vmstate,isEfficient?{mode:'efficient',snap_size_gb:snapSizeGb}:{mode:'standard'});};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:`w-full ${canEfficient?'max-w-lg':'max-w-md'} bg-proxmox-card border border-proxmox-border rounded-xl p-6 animate-scale-in`},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold text-white mb-4"},t('createSnapshot')),/*#__PURE__*/React.createElement("div",{className:"space-y-4"},canEfficient&&/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-2"},t('snapshotMode')),/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setMode('standard'),className:`flex-1 px-3 py-2 rounded-lg text-sm border transition-colors ${!isEfficient?'bg-blue-600/20 border-blue-500 text-blue-400':'bg-proxmox-dark border-proxmox-border text-gray-400 hover:border-gray-500'}`},t('normalMode')),/*#__PURE__*/React.createElement("button",{onClick:()=>setMode('efficient'),className:`flex-1 px-3 py-2 rounded-lg text-sm border transition-colors ${isEfficient?'bg-green-600/20 border-green-500 text-green-400':'bg-proxmox-dark border-proxmox-border text-gray-400 hover:border-gray-500'}`},/*#__PURE__*/React.createElement("span",{className:"flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement(Icons.Zap,null),t('efficientMode'))))),isEfficient&&efficientInfo&&/*#__PURE__*/React.createElement("div",{className:"p-3 bg-proxmox-dark rounded-lg border border-green-500/30 space-y-3"},/*#__PURE__*/React.createElement("div",{className:"text-sm font-medium text-green-400 flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.Zap,null),t('spaceSavings'),": ",efficientInfo.savings_percent,"%"),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs text-gray-400 mb-1"},/*#__PURE__*/React.createElement("span",null,t('normalSnapshotSize')),/*#__PURE__*/React.createElement("span",null,efficientInfo.total_disk_size_gb?.toFixed(1)," GB")),/*#__PURE__*/React.createElement("div",{className:"w-full h-3 bg-gray-700 rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"h-full bg-red-500 rounded-full",style:{width:'100%'}}))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs text-gray-400 mb-1"},/*#__PURE__*/React.createElement("span",null,t('efficientSnapshotSize')),/*#__PURE__*/React.createElement("span",null,"~",snapSizeGb?.toFixed(1)," GB")),/*#__PURE__*/React.createElement("div",{className:"w-full h-3 bg-gray-700 rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"h-full bg-green-500 rounded-full",style:{width:`${Math.max(3,snapSizeGb/efficientInfo.total_disk_size_gb*100)}%`}}))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('snapshotSizeGb')," (",t('recommended'),": ",efficientInfo.recommended_snap_size_gb?.toFixed(1)," GB)"),/*#__PURE__*/React.createElement("input",{type:"number",value:snapSizeGb,onChange:e=>setSnapSizeGb(parseFloat(e.target.value)||1),min:"1",max:efficientInfo.vg_free_gb-2,step:"1",className:"w-full px-3 py-1.5 bg-proxmox-card border border-proxmox-border rounded-lg text-white text-sm"})),/*#__PURE__*/React.createElement("div",{className:`text-xs flex items-center gap-1 ${efficientInfo.has_guest_agent?'text-green-400':'text-yellow-400'}`},efficientInfo.has_guest_agent?/*#__PURE__*/React.createElement(Icons.CheckCircle,null):/*#__PURE__*/React.createElement(Icons.AlertTriangle,null),efficientInfo.has_guest_agent?t('guestAgentDetected'):t('noGuestAgent')),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 italic"},t('managedByPegaprox'))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('name')),/*#__PURE__*/React.createElement("input",{type:"text",value:snapname,onChange:e=>setSnapname(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:"snapshot-name"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('description')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"text",value:description,onChange:e=>setDescription(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('snapshotDescription')})),isQemu&&!isEfficient&&/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-2 text-sm text-gray-300"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:vmstate,onChange:e=>setVmstate(e.target.checked),className:"rounded"}),t('saveRamState'))),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-3 mt-6"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-gray-300 hover:text-white"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:handleSubmit,disabled:loading||!snapname.trim(),className:"flex items-center gap-2 px-4 py-2 rounded-lg text-white disabled:opacity-50 bg-green-600 hover:bg-green-700"},loading&&/*#__PURE__*/React.createElement(Icons.RotateCw,null),isEfficient&&/*#__PURE__*/React.createElement(Icons.Zap,null),t('create')))));}// Create Replication Modal function CreateReplicationModal({nodes,onSubmit,onClose,loading}){const{t}=useTranslation();const[target,setTarget]=useState(nodes[0]||'');const[schedule,setSchedule]=useState('*/15');const[rate,setRate]=useState('');const[comment,setComment]=useState('');const scheduleOptions=[{value:'*/5',label:t('every5min')},{value:'*/15',label:t('every15min')},{value:'*/30',label:t('every30min')},{value:'0 *',label:t('hourly')},{value:'0 */2',label:t('every2hours')},{value:'0 */4',label:t('every4hours')},{value:'0 */6',label:t('every6hours')},{value:'0 */12',label:t('every12hours')},{value:'0 0',label:t('daily')}];const handleSubmit=()=>{if(!target)return;onSubmit(target,schedule,rate?parseInt(rate):null,comment);};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-md bg-proxmox-card border border-proxmox-border rounded-xl p-6 animate-scale-in"},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold text-white mb-4"},t('createReplicationJob')),/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetNode')),/*#__PURE__*/React.createElement("select",{value:target,onChange:e=>setTarget(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white"},nodes.map(n=>/*#__PURE__*/React.createElement("option",{key:n,value:n},n)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},"Schedule"),/*#__PURE__*/React.createElement("select",{value:schedule,onChange:e=>setSchedule(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white"},scheduleOptions.map(opt=>/*#__PURE__*/React.createElement("option",{key:opt.value,value:opt.value},opt.label)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('rateLimit')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"number",value:rate,onChange:e=>setRate(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('unlimited'),min:"1"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('comment')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"text",value:comment,onChange:e=>setComment(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('commentPlaceholder')}))),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-3 mt-6"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-gray-300 hover:text-white"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:handleSubmit,disabled:loading||!target,className:"flex items-center gap-2 px-4 py-2 bg-green-600 rounded-lg text-white hover:bg-green-700 disabled:opacity-50"},loading&&/*#__PURE__*/React.createElement(Icons.RotateCw,null),t('create')))));}// Shared form components (defined outside ConfigModal to prevent re-creation) // ns: could use a form library but this is fine for now diff --git a/web/src/translations.js b/web/src/translations.js index 51dc32b..e8b16d1 100644 --- a/web/src/translations.js +++ b/web/src/translations.js @@ -614,7 +614,7 @@ clusterReconfigured: 'Cluster erfolgreich neu konfiguriert', reconfigure: 'Neu konfigurieren', clusterHealth: 'Cluster Gesundheit', - clusterHealthTooltip: 'Gesundheit = 100 − (CPU×30% + RAM×30% + Speicher×20% + Offline-Nodes×20%)\n\n80+: Ausgezeichnet\n60–79: Gut\n40–59: Warnung\n< 40: Kritisch', + clusterHealthTooltip: 'Gesundheit = 100 − (CPU×30% + RAM×30% + Speicher×20% + Offline-Nodes×20%)\nOhne Speicherdaten: CPU×37,5% + RAM×37,5% + Offline×25%\n\n80+: Ausgezeichnet\n60–79: Gut\n40–59: Warnung\n< 40: Kritisch', nodeScoreTooltip: 'Node-Score = CPU% + RAM% (niedriger ist besser)\n\n< 100: Gut (grün)\n100–150: Erhöht (gelb)\n> 150: Kritisch (rot)', excellent: 'Ausgezeichnet', good: 'Gut', @@ -3716,7 +3716,7 @@ clusterReconfigured: 'Cluster re-configured successfully', reconfigure: 'Re-configure', clusterHealth: 'Cluster Health', - clusterHealthTooltip: 'Health = 100 − (CPU×30% + RAM×30% + Storage×20% + Offline Nodes×20%)\n\n80+: Excellent\n60–79: Good\n40–59: Warning\n< 40: Critical', + clusterHealthTooltip: 'Health = 100 − (CPU×30% + RAM×30% + Storage×20% + Offline Nodes×20%)\nWithout storage data: CPU×37.5% + RAM×37.5% + Offline×25%\n\n80+: Excellent\n60–79: Good\n40–59: Warning\n< 40: Critical', nodeScoreTooltip: 'Node score = CPU% + RAM% (lower is better)\n\n< 100: Good (green)\n100–150: Elevated (yellow)\n> 150: Critical (red)', excellent: 'Excellent', good: 'Good', @@ -6737,7 +6737,7 @@ clusterReconfigured: 'Cluster reconfiguré avec succès', reconfigure: 'Reconfigurer', clusterHealth: 'Santé du Cluster', - clusterHealthTooltip: 'Santé = 100 − (CPU×30% + RAM×30% + Stockage×20% + Nœuds hors ligne×20%)\n\n80+ : Excellente\n60–79 : Bien\n40–59 : Avertissement\n< 40 : Critique', + clusterHealthTooltip: 'Santé = 100 − (CPU×30% + RAM×30% + Stockage×20% + Nœuds hors ligne×20%)\nSans données stockage : CPU×37,5% + RAM×37,5% + Hors ligne×25%\n\n80+ : Excellente\n60–79 : Bien\n40–59 : Avertissement\n< 40 : Critique', nodeScoreTooltip: 'Score nœud = CPU% + RAM% (plus bas est mieux)\n\n< 100 : Bon (vert)\n100–150 : Élevé (jaune)\n> 150 : Critique (rouge)', excellent: 'Excellente', good: 'Bien', @@ -9613,7 +9613,7 @@ clusterReconfigured: 'Cluster reconfigurado exitosamente', reconfigure: 'Reconfigurar', clusterHealth: 'Salud del cluster', - clusterHealthTooltip: 'Salud = 100 − (CPU×30% + RAM×30% + Almacenamiento×20% + Nodos fuera de línea×20%)\n\n80+: Excelente\n60–79: Buena\n40–59: Advertencia\n< 40: Crítica', + clusterHealthTooltip: 'Salud = 100 − (CPU×30% + RAM×30% + Almacenamiento×20% + Nodos fuera de línea×20%)\nSin datos de almacenamiento: CPU×37,5% + RAM×37,5% + Fuera de línea×25%\n\n80+: Excelente\n60–79: Buena\n40–59: Advertencia\n< 40: Crítica', nodeScoreTooltip: 'Puntuación nodo = CPU% + RAM% (menor es mejor)\n\n< 100: Bueno (verde)\n100–150: Elevado (amarillo)\n> 150: Crítico (rojo)', excellent: 'Excelente', good: 'Buena', @@ -12646,7 +12646,7 @@ clusterReconfigured: 'Cluster reconfigurado com sucesso', reconfigure: 'Reconfigurar', clusterHealth: 'Saúde do Cluster', - clusterHealthTooltip: 'Saúde = 100 − (CPU×30% + RAM×30% + Armazenamento×20% + Nós offline×20%)\n\n80+: Excelente\n60–79: Bom\n40–59: Aviso\n< 40: Crítico', + clusterHealthTooltip: 'Saúde = 100 − (CPU×30% + RAM×30% + Armazenamento×20% + Nós offline×20%)\nSem dados de armazenamento: CPU×37,5% + RAM×37,5% + Offline×25%\n\n80+: Excelente\n60–79: Bom\n40–59: Aviso\n< 40: Crítico', nodeScoreTooltip: 'Pontuação nó = CPU% + RAM% (menor é melhor)\n\n< 100: Bom (verde)\n100–150: Elevado (amarelo)\n> 150: Crítico (vermelho)', excellent: 'Excelente', good: 'Bom', diff --git a/web/src/vm_modals.js b/web/src/vm_modals.js index b551b2a..1a6daa7 100644 --- a/web/src/vm_modals.js +++ b/web/src/vm_modals.js @@ -6153,12 +6153,17 @@ const avgCpu = nodes.reduce((acc, [, m]) => acc + (m.cpu_percent ?? 0), 0) / nodes.length; const avgMem = nodes.reduce((acc, [, m]) => acc + (m.mem_percent ?? 0), 0) / nodes.length; - const avgDisk = nodes.reduce((acc, [, m]) => acc + (m.disk_percent ?? 0), 0) / nodes.length; + const diskNodes = nodes.filter(([, m]) => m.disk_percent != null); + const avgDisk = diskNodes.length > 0 ? diskNodes.reduce((acc, [, m]) => acc + m.disk_percent, 0) / diskNodes.length : null; const onlineNodes = nodes.filter(([, m]) => m.status === 'online' && !m.maintenance_mode).length; const maintenanceNodes = nodes.filter(([, m]) => m.maintenance_mode).length; const offlineNodes = nodes.length - onlineNodes - maintenanceNodes; + const offlineRatio = nodes.length > 0 ? (offlineNodes / nodes.length) * 100 : 0; - const healthScore = Math.max(0, 100 - (avgCpu * 0.3 + avgMem * 0.3 + avgDisk * 0.2 + (nodes.length > 0 ? (offlineNodes / nodes.length) * 100 * 0.2 : 0))); + // When disk stats unavailable (e.g. XCP-ng), re-normalize: CPU 37.5%, RAM 37.5%, Offline 25% + const healthScore = avgDisk != null + ? Math.max(0, 100 - (avgCpu * 0.3 + avgMem * 0.3 + avgDisk * 0.2 + offlineRatio * 0.2)) + : Math.max(0, 100 - (avgCpu * 0.375 + avgMem * 0.375 + offlineRatio * 0.25)); const healthLabel = healthScore >= 80 ? t('excellent') : healthScore >= 60 ? t('good') : healthScore >= 40 ? t('warning') : t('critical'); const healthColor = healthScore >= 80 ? '#22c55e' : healthScore >= 60 ? '#84cc16' : healthScore >= 40 ? '#eab308' : '#ef4444'; @@ -6223,7 +6228,7 @@
{t('nodesOnline')}
-
{avgDisk.toFixed(1)}%
+
{avgDisk != null ? `${avgDisk.toFixed(1)}%` : 'N/A'}
{t('avgStorage')}
From d2407a1b2c8c4f913e3f3e9f9312fdb15c20f3f3 Mon Sep 17 00:00:00 2001 From: Lukas Alstrup Date: Mon, 6 Apr 2026 22:58:21 +0200 Subject: [PATCH 4/6] fix: add missing Korean translations for health tooltips and avg storage --- web/index.html | 2 +- web/src/translations.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/web/index.html b/web/index.html index e41d9f9..218389c 100644 --- a/web/index.html +++ b/web/index.html @@ -2493,7 +2493,7 @@ poolPermissions:'풀 권한',poolPermissionsDesc:'사용자 또는 그룹에 Proxmox 리소스 풀에 대한 접근 권한을 부여합니다. 권한은 풀 내의 모든 VM에 적용됩니다.',managePools:'풀 관리',poolManagerDesc:'리소스 풀을 생성, 편집 및 삭제합니다. 체계적인 권한 관리를 위해 VM을 풀에 할당합니다.',createPool:'풀 생성',editPool:'풀 편집',deletePool:'풀 삭제',poolId:'풀 ID',poolIdRequired:'풀 ID가 필요합니다',poolIdHint:'문자, 숫자, 대시 및 밑줄만 사용 가능',poolIdCannotChange:'풀 ID는 변경할 수 없습니다',poolCreated:'풀이 성공적으로 생성되었습니다',poolUpdated:'풀이 성공적으로 업데이트되었습니다',poolDeleted:'풀이 성공적으로 삭제되었습니다',confirmDeletePool:'정말 이 풀을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.',noPoolsYet:'아직 풀이 없습니다',createFirstPool:'VM을 구성하기 위한 첫 번째 풀을 생성하세요',poolMembers:'멤버',members:'멤버',addVmToPool:'풀에 VM 추가',assignToPool:'풀에 할당',removeFromPool:'풀에서 제거',vmAddedToPool:'VM이 풀에 추가되었습니다',vmRemovedFromPool:'VM이 풀에서 제거되었습니다',confirmRemoveVmFromPool:'이 풀에서 VM을 제거하시겠습니까?',selectVmToAdd:'이 풀에 추가할 VM을 선택하세요',allVmsInPools:'모든 VM이 이미 풀에 있습니다',optionalDescription:'설명 (선택 사항)...',userPermissions:'사용자 권한',selectPool:'풀 선택',noPools:'이 클러스터에서 리소스 풀을 찾을 수 없습니다',noPoolPerms:'이 풀에 구성된 권한이 없습니다',selectPoolFirst:'권한을 관리할 클러스터와 풀을 선택하세요',addPoolPerm:'풀 권한 추가',editPoolPerm:'풀 권한 편집',poolPermSaved:'풀 권한이 저장되었습니다',poolPermDeleted:'풀 권한이 제거되었습니다',refreshPools:'Proxmox에서 풀 새로고침',poolCacheRefreshed:'풀 캐시가 새로고침되었습니다',confirmDeletePoolPerm:'권한을 제거하시겠습니까?',subjectType:'유형',permissionsFor:'권한 대상:',addPermission:'권한 추가',groupName:'그룹 이름',auditLog:'감사 로그',auditLogDescription:'최근 90일간의 모든 사용자 작업',noAuditLogs:'감사 항목이 없습니다',action:'작업',details:'세부 정보',timestamp:'타임스탬프',ipAddress:'IP 주소',userCreated:'사용자 생성됨',userUpdated:'사용자 업데이트됨',userDeleted:'사용자 삭제됨',userLogin:'로그인',userLogout:'로그아웃',passwordChanged:'비밀번호 변경됨',clusterAdded:'클러스터 추가됨',apiTokenWarningTitle:'API 토큰 인증',apiTokenWarningDesc:'비밀번호 없이는 SSH 기능이 작동하지 않습니다 (HA, 순차 업데이트, SMBIOS, 노드 셸). 권장: root@pam + 비밀번호 사용 — PegaProx가 2FA 호환을 위해 API 토큰을 자동 생성합니다.',apiTokenRecommended:'2FA 활성화 시 권장',apiTokenCreated:'PVE에 API 토큰이 생성되었습니다 — 이제 2FA를 안전하게 활성화할 수 있습니다',sshPasswordStillNeeded:'SSH는 여전히 비밀번호를 사용합니다 (HA, 유지보수) — 변경하지 마세요',authModeToken:'API: 토큰 (2FA 안전)',authModePassword:'API: 비밀번호',sshAuthMode:'SSH: 비밀번호',dontChangePvePassword:'여기서 업데이트하지 않고 PVE 비밀번호를 변경하지 마세요',clusterDeleted:'클러스터 삭제됨',clusterConfigChanged:'클러스터 구성 변경됨',vmStarted:'VM 시작됨',vmStopped:'VM 중지됨',vmRestarted:'VM 재시작됨',vmCreated:'VM 생성됨',vmDeleted:'VM 삭제됨',vmCloned:'VM 복제됨',vmMigrated:'VM 마이그레이션됨',vmUnlocked:'VM 잠금 해제됨',vmLocked:'VM 잠금됨',unlockVm:'VM 잠금 해제',lockReason:'잠금 사유',unlockWarning:'경고: 활성 작업 중 VM 잠금을 해제하면 데이터 손상 또는 기타 문제가 발생할 수 있습니다. 작업이 실패했거나 취소된 것이 확실한 경우에만 진행하세요.',vmBulkMigrated:'대량 마이그레이션',vmConfigChanged:'VM 구성 변경됨',vmSuspended:'VM 일시 중지됨',vmResumed:'VM 재개됨',vmDiskAdded:'디스크 추가됨',vmDiskRemoved:'디스크 제거됨',vmDiskResized:'디스크 크기 변경됨',vmDiskMoved:'디스크 이동됨',vmNetworkAdded:'네트워크 추가됨',vmNetworkRemoved:'네트워크 제거됨',vmNetworkUpdated:'네트워크 업데이트됨',removeDiskConfirm:'정말 디스크를 제거하시겠습니까',detachDisk:'분리',importDisk:'디스크 가져오기',importDiskDesc:'스토리지에서 기존 디스크 이미지 가져오기',selectImportStorage:'소스 스토리지 선택',selectDiskImage:'디스크 이미지 선택',targetBus:'대상 버스',reassignOwner:'소유자 재할당',reassignOwnerDesc:'디스크를 다른 VM에 할당',targetVm:'대상 VM',diskReassigned:'디스크가 재할당되었습니다',diskImported:'디스크가 가져오기되었습니다',noImportableDisks:'가져올 수 있는 디스크 이미지가 없습니다',reassign:'재할당',import:'가져오기',detachDiskConfirm:'정말 디스크를 분리하시겠습니까? 미사용 디스크가 됩니다.',diskDetached:'디스크가 분리되었습니다',diskDeleted:'디스크가 삭제되었습니다',diskAdded:'디스크가 추가되었습니다',dataWillBeDeleted:'데이터가 영구적으로 삭제됩니다!',removeNetworkConfirm:'정말 네트워크를 제거하시겠습니까',snapshotCreated:'스냅샷이 생성되었습니다',snapshotDeleted:'스냅샷이 삭제되었습니다',snapshotRestored:'스냅샷이 복원되었습니다',rollbackStarted:'롤백이 시작되었습니다',includeRam:'RAM 포함',snapshotName:'스냅샷 이름',rollbackConfirm:'정말 이 스냅샷으로 롤백하시겠습니까? 이 작업은 되돌릴 수 없습니다!',replicationCreated:'복제가 생성되었습니다',replicationDeleted:'복제가 삭제되었습니다',replicationTriggered:'복제가 트리거되었습니다',haEnabled:'HA 활성화됨',haDisabled:'HA 비활성화됨',noFallbackHosts:'대체 호스트를 찾을 수 없습니다 (단일 노드 클러스터?)',haVmAdded:'VM이 HA에 추가되었습니다',haVmRemoved:'VM이 HA에서 제거되었습니다',nodeMaintenanceEntered:'유지보수 모드가 시작되었습니다',nodeMaintenanceExited:'유지보수 모드가 해제되었습니다',nodeUpdateStarted:'노드 업데이트가 시작되었습니다',twoFactorAuth:'2단계 인증',twoFactorEnabled:'2FA 활성화됨',twoFactorDisabled:'2FA 비활성화됨',enable2FA:'2FA 활성화',disable2FA:'2FA 비활성화',setup2FA:'2FA 설정',scan2FACode:'인증 앱으로 QR 코드를 스캔하세요',enter2FACode:'6자리 코드를 입력하세요',verify2FA:'확인',secretKey:'비밀 키',twoFARequired:'2FA 코드가 필요합니다',invalid2FACode:'잘못된 2FA 코드',force2FA:'2FA 강제 적용',force2FADesc:'모든 사용자가 PegaProx를 사용하기 전에 2단계 인증을 설정하도록 요구합니다.',force2FAHint:'OIDC/Entra 사용자는 면제됩니다 (ID 공급자의 MFA를 사용). 2FA가 없는 사용자는 로그인 시 설정 대화상자가 표시됩니다.',force2FAExcludeAdmins:'관리자 계정 제외',force2FAExcludeAdminsDesc:'관리자는 2FA 없이 PegaProx를 사용할 수 있습니다',force2FASetupTitle:'2FA 설정 필요',force2FASetupDesc:'관리자가 모든 사용자에게 2단계 인증을 필수로 설정했습니다. 계속하려면 2FA를 설정하세요.',resetPassword:'비밀번호 재설정',passwordManagedExternally:'비밀번호가 외부에서 관리됩니다.',passwordManagedExternallyHint:'디렉터리 서비스에서 직접 비밀번호를 변경하세요.',newPassword:'새 비밀번호',confirmPassword:'비밀번호 확인',currentPassword:'현재 비밀번호',passwordsDoNotMatch:'비밀번호가 일치하지 않습니다',passwordTooShort:'비밀번호는 최소 4자 이상이어야 합니다',passwordResetSuccess:'비밀번호가 성공적으로 변경되었습니다',myProfile:'내 프로필',security:'보안',snapshotNotSupported:'스냅샷이 지원되지 않습니다',snapshotWarnings:'스냅샷 경고',filterByUser:'사용자별 필터',filterByAction:'작업별 필터',allUsers:'모든 사용자',allActions:'모든 작업',exportAuditLog:'내보내기',refreshAuditLog:'새로고침',// Header addCluster:'클러스터 추가',addXcpngPool:'XCP-ng 풀 추가 (기술 미리보기)',xcpngConnectHint:'풀 마스터 호스트에 연결합니다. 기본적으로 XAPI 포트 443이 사용됩니다.',xcpngTechPreviewNote:'일부 기능이 제한되거나 변경될 수 있습니다.',pveClusterDesc:'가상 머신 및 컨테이너',pbsDesc:'백업 관리',vmwareDesc:'ESXi 인프라',xcpngDesc:'XCP-ng / Xen Hypervisor (기술 미리보기)',addConnection:'연결 추가',connectionType:'연결 유형',clusterManagement:'Proxmox VE를 위한 PegaProx 클러스터 관리',// Tabs overview:'개요',resources:'리소스',datacenter:'데이터센터',settings:'설정',of:'/',showing:'표시',perPage:'페이지당',loadingDatacenter:'데이터센터 데이터 로딩 중...',// Cluster -clusters:'클러스터',noClusterSelected:'선택된 클러스터 없음',allClustersOverview:'모든 클러스터 개요',allClusters:'모든 클러스터',multiClusterSummary:'모든 관리 클러스터 요약',clustersConnected:'연결된 클러스터',clusterOverview:'클러스터 개요',vmsRunning:'실행 중인 VM',vmsStopped:'중지된 VM',noClustersConfigured:'구성된 클러스터 없음',addClusterToStart:'시작하려면 클러스터를 추가하세요',clickClusterTip:'클러스터 행을 클릭하거나 사이드바에서 선택하여 상세 정보를 보고 리소스를 관리하세요.',health:'상태',average:'평균',noDataAvailable:'사용 가능한 데이터 없음',clickToManage:'관리하려면 클릭',sortBy:'정렬 기준',ungrouped:'그룹 없음',alerts:'알림',updated:'업데이트됨',justNow:'방금 전',topResources:'상위 리소스',highestCpuUsage:'모든 클러스터에서 가장 높은 CPU 및 RAM 사용률',clickToOpenVm:'VM을 열려면 클릭',selectCluster:'목록에서 클러스터를 선택하세요',addFirstCluster:'첫 번째 클러스터 추가',connectCluster:'Proxmox 클러스터를 PegaProx에 연결',clusterName:'클러스터 이름',host:'호스트',username:'사용자명',password:'비밀번호',passwordOrToken:'비밀번호 / 토큰',apiTokenHint:'API 토큰의 경우: user@realm!tokenid',sslVerification:'SSL 확인',connecting:'연결 중...',addNewCluster:'새 클러스터 추가',testConnection:'연결 테스트',deleteCluster:'클러스터 삭제',deleteClusterConfirm:'정말 클러스터를 삭제하시겠습니까?',reconfigureCluster:'클러스터 재구성',reconfigureHint:'본인 확인을 위해 PegaProx 비밀번호를 입력하세요.',clusterReconfigured:'클러스터가 성공적으로 재구성되었습니다',reconfigure:'재구성',clusterHealth:'클러스터 상태',excellent:'우수',good:'양호',warning:'경고',critical:'심각',nodesOnline:'온라인 노드',nodeJoinHint:'새 노드를 추가하려면 새 노드에서 실행하세요:',avgScore:'평균 점수',avgCpu:'평균 CPU',avgRam:'평균 RAM',// Nodes +clusters:'클러스터',noClusterSelected:'선택된 클러스터 없음',allClustersOverview:'모든 클러스터 개요',allClusters:'모든 클러스터',multiClusterSummary:'모든 관리 클러스터 요약',clustersConnected:'연결된 클러스터',clusterOverview:'클러스터 개요',vmsRunning:'실행 중인 VM',vmsStopped:'중지된 VM',noClustersConfigured:'구성된 클러스터 없음',addClusterToStart:'시작하려면 클러스터를 추가하세요',clickClusterTip:'클러스터 행을 클릭하거나 사이드바에서 선택하여 상세 정보를 보고 리소스를 관리하세요.',health:'상태',average:'평균',noDataAvailable:'사용 가능한 데이터 없음',clickToManage:'관리하려면 클릭',sortBy:'정렬 기준',ungrouped:'그룹 없음',alerts:'알림',updated:'업데이트됨',justNow:'방금 전',topResources:'상위 리소스',highestCpuUsage:'모든 클러스터에서 가장 높은 CPU 및 RAM 사용률',clickToOpenVm:'VM을 열려면 클릭',selectCluster:'목록에서 클러스터를 선택하세요',addFirstCluster:'첫 번째 클러스터 추가',connectCluster:'Proxmox 클러스터를 PegaProx에 연결',clusterName:'클러스터 이름',host:'호스트',username:'사용자명',password:'비밀번호',passwordOrToken:'비밀번호 / 토큰',apiTokenHint:'API 토큰의 경우: user@realm!tokenid',sslVerification:'SSL 확인',connecting:'연결 중...',addNewCluster:'새 클러스터 추가',testConnection:'연결 테스트',deleteCluster:'클러스터 삭제',deleteClusterConfirm:'정말 클러스터를 삭제하시겠습니까?',reconfigureCluster:'클러스터 재구성',reconfigureHint:'본인 확인을 위해 PegaProx 비밀번호를 입력하세요.',clusterReconfigured:'클러스터가 성공적으로 재구성되었습니다',reconfigure:'재구성',clusterHealth:'클러스터 상태',clusterHealthTooltip:'상태 = 100 − (CPU×30% + RAM×30% + 스토리지×20% + 오프라인 노드×20%)\n스토리지 데이터 없음: CPU×37.5% + RAM×37.5% + 오프라인×25%\n\n80+: 우수\n60–79: 양호\n40–59: 경고\n< 40: 심각',nodeScoreTooltip:'노드 점수 = CPU% + RAM% (낮을수록 좋음)\n\n< 100: 양호 (녹색)\n100–150: 주의 (노란색)\n> 150: 심각 (빨간색)',excellent:'우수',good:'양호',warning:'경고',critical:'심각',nodesOnline:'온라인 노드',nodeJoinHint:'새 노드를 추가하려면 새 노드에서 실행하세요:',avgScore:'평균 점수',avgStorage:'평균 스토리지',avgCpu:'평균 CPU',avgRam:'평균 RAM',// Nodes nodes:'노드',node:'노드',loadingMetrics:'메트릭 로딩 중...',connectionError:'연결 오류',retry:'재시도',checkConnectionAndRetry:'연결을 확인하고 다시 시도하세요.',connectionTimeout:'시간 초과 - Proxmox에 접근할 수 없음',maintenance:'유지보수',enterMaintenance:'유지보수 모드 시작',exitMaintenance:'유지보수 모드 해제',maintenanceMode:'유지보수 모드',update:'업데이트',startUpdate:'업데이트 시작',nodeConfig:'노드 구성',cpuHistory:'CPU 기록',ramHistory:'RAM 기록',ramUsage:'RAM 사용률',cpuUsage:'CPU 사용률',diskUsage:'디스크 사용률',allocated:'할당됨',showMore:'더 보기',showLess:'접기',// VMs & Resources virtualMachines:'가상 머신',containers:'컨테이너',vm:'VM',lxc:'LXC',lxcContainer:'LXC 컨테이너',container:'컨테이너',guests:'게스트',start:'시작',stop:'중지',shutdown:'종료',reboot:'재부팅',forceStop:'강제 중지',forceReset:'강제 리셋',forceStopConfirm:'정말 강제 중지하시겠습니까? 데이터 손실이 발생할 수 있습니다!',migrate:'마이그레이션',migrateVm:'VM 마이그레이션',crossClusterMigration:'클러스터 간 마이그레이션',crossClusterMigrateDesc:'VM을 다른 Proxmox 클러스터로 마이그레이션',crossClusterMigrate:'클러스터 간 마이그레이션',crossClusterStarted:'클러스터 간 마이그레이션이 시작되었습니다',crossClusterFailed:'클러스터 간 마이그레이션 실패',crossClusterLB:'클러스터 간 로드 밸런싱',crossClusterLBEnabled:'클러스터 간 LB 활성화됨',crossClusterLBDisabled:'클러스터 간 LB 비활성화됨',crossClusterLBThreshold:'점수 임계값',crossClusterLBInterval:'확인 간격',crossClusterLBDryRun:'시뮬레이션 (테스트 실행)',crossClusterLBLastRun:'마지막 실행',crossClusterReplication:'클러스터 간 복제',crossClusterReplicationDesc:'VM 스냅샷을 다른 클러스터로 복제 (DR)',groupOverview:'그룹 개요',groupSettings:'그룹 설정',groupSettingsSaved:'그룹 설정이 저장되었습니다',replicationSchedule:'일정',replicationRetention:'보존',maxMigrations:'사이클당 최대 마이그레이션 수',lbHistory:'LB 기록',clusterScore:'클러스터 점수',noReplicationJobs:'클러스터 간 복제 작업 없음',createReplicationJob:'DR 작업 생성',replicationStarted:'복제가 시작되었습니다',replicationJobCreated:'복제 작업이 생성되었습니다',replicationJobDeleted:'복제 작업이 삭제되었습니다',recentLbActions:'최근 클러스터 간 LB 작업',noLbEvents:'아직 클러스터 간 LB 이벤트 없음',enableCrossClusterLB:'클러스터 간 로드 밸런싱 활성화',lbDescription:'리소스 임계값을 초과하면 클러스터 간에 VM을 자동으로 마이그레이션합니다',dryRunMode:'테스트 실행 / 시뮬레이션 모드',cpuThreshold:'CPU 임계값 (%)',lbStatus:'로드 밸런싱 상태',lbExplanation:'클러스터 간 로드 밸런싱은 이 그룹의 모든 클러스터에서 CPU 및 RAM 사용률을 모니터링합니다. 클러스터가 구성된 임계값을 초과하면 VM이 부하가 적은 클러스터로 자동 마이그레이션됩니다.',lbDryRunExplanation:'라이브 마이그레이션을 활성화하기 전에 어떤 조치가 취해질지 검토하려면 먼저 테스트 실행 모드를 활성화하세요.',neverRun:'실행된 적 없음',newCrossClusterReplication:'새 클러스터 간 복제',confirmDeleteXRepl:'이 클러스터 간 복제 작업을 삭제하시겠습니까?',addDrJob:'DR 작업 추가',scheduleCron:'일정 (cron)',targetStorageHint:'대상 클러스터의 스토리지 이름',maxMigrationsHint:'각 확인 주기에 이동할 최대 VM 수 제한 (1-5)',proxlbCredit:'ProxLB by gyptazy',proxlbCreditDesc:'로드 밸런싱 기능은 ProxLB의 뛰어난 작업을 기반으로 합니다. 이 훌륭한 도구를 만들고 오픈소스로 공개해 주신 gyptazy에게 감사드립니다!',xReplCreated:'클러스터 간 복제가 생성되었습니다',xReplDeleted:'클러스터 간 복제가 삭제되었습니다',xReplStarted:'클러스터 간 복제가 시작되었습니다',xReplCreateFailed:'작업 생성 실패',xReplDeleteFailed:'삭제 실패',xReplStartFailed:'시작 실패',lastRunPrefix:'마지막',runNow:'지금 실행',loadingCrossClusterResources:'모든 클러스터의 스토리지/네트워크 로딩 중...',noCommonStorages:'모든 클러스터에서 공통 스토리지를 찾을 수 없습니다',noCommonBridges:'모든 클러스터에서 공통 브릿지를 찾을 수 없습니다',selectBridge:'브릿지 선택...',crossClusterThresholdDesc:'클러스터 불균형에 대한 CPU 임계값 (10-80%)',crossClusterIntervalDesc:'확인 주기 간격',crossClusterMaxMigrationsDesc:'확인 주기당 최대 마이그레이션 수',commonStorageHint:'이 그룹의 모든 클러스터에서 사용 가능한 스토리지만 표시',commonBridgeHint:'이 그룹의 모든 클러스터에서 사용 가능한 브릿지만 표시',includeContainers:'컨테이너 포함',includeContainersDesc:'클러스터 간 밸런싱에 컨테이너 (LXC) 포함',containerMigrationWarning:'컨테이너는 마이그레이션 중 재시작됩니다 (다운타임 발생)',excludedVMsCrossCluster:'제외된 VM/컨테이너',excludedVMsCrossClusterDesc:'자동 클러스터 간 밸런싱에서 제외된 VM 및 컨테이너',clusterExcludedVMs:'제외된 VM',noExcludedVMsInGroup:'제외된 VM 없음',selectCluster:'클러스터 선택...',selectClusterFirst:'먼저 대상 클러스터를 선택하세요',simulationMode:'시뮬레이션 모드',sourceVm:'소스 VM',targetCluster:'대상 클러스터',targetNode:'대상 노드',targetStorage:'대상 스토리지',targetBridge:'대상 네트워크 (브릿지)',moveDisk:'디스크 이동',resizeDisk:'디스크 크기 변경',diskResized:'디스크 크기가 변경되었습니다',deleteSourceDisk:'이동 후 소스 삭제',deleteSourceDiskWarning:'원본 디스크가 소스 스토리지에 남아 있습니다. 나중에 수동으로 제거할 수 있습니다.',move:'이동',from:'에서',noNetworkInterfaces:'네트워크 인터페이스 없음',nameRequired:'이름이 필요합니다',newVmid:'새 VMID (선택 사항)',sameIdPlaceholder:'빈 값 = 동일 ID',liveMigrationOption:'라이브 마이그레이션 (VM이 계속 실행됨)',deleteSourceAfter:'마이그레이션 후 소스 VM 삭제',largeDiskWarning:'대용량 디스크 감지',largeDiskExplanation:'100GB 이상의 디스크에 대한 라이브 마이그레이션은 Proxmox WebSocket 티켓 시간 초과로 인해 "401 Unauthorized" 오류로 실패할 수 있습니다. 강제하지 않는 한 서버가 자동으로 오프라인 마이그레이션을 사용합니다.',forceOnlineMigration:'온라인 마이그레이션 강제 실행 (실패할 수 있음)',autoTokenInfo:'PegaProx가 마이그레이션을 위한 임시 API 토큰을 자동 생성하고 완료 후 삭제합니다.',clusterReachableInfo:'클러스터가 네트워크를 통해 서로 접근할 수 있어야 합니다. 구성된 사용자에게 API 토큰 생성 권한이 있어야 합니다.',selectCluster:'클러스터 선택...',selectNode:'노드 선택',selectStorage:'스토리지 선택...',loadingNodes:'대상 노드 로딩 중...',loadingStorageNetwork:'스토리지/네트워크 로딩 중...',liveMigration:'라이브 마이그레이션 (다운타임 없음)',on:'에',targetStorage:'대상 스토리지',sameAsSource:'소스와 동일',loadingStorages:'스토리지 로딩 중',free:'여유',withLocalDisks:'로컬 디스크 포함',withLocalDisksDesc:'로컬 디스크를 대상 스토리지로 마이그레이션',localDisksDetected:'이 VM에 로컬 디스크가 있습니다. 마이그레이션에는 디스크 데이터 복사가 필요합니다.',requiredForThisVm:'이 VM에 필요합니다',cdDvdMounted:'CD/DVD 드라이브 마운트됨',cdDvdMigrationWarning:'CD/DVD가 마운트된 상태에서는 라이브 마이그레이션이 불가능합니다. 먼저 CD/DVD를 꺼내거나 오프라인 마이그레이션을 사용하세요.',isoMounted:'ISO/CD-ROM 마운트됨',isoEjected:'CD-ROM 꺼내짐',isoMigrationWarning:'ISO가 대상 노드에서 사용 가능하지 않으면 마이그레이션이 실패할 수 있습니다. CD/DVD를 꺼내거나 공유 스토리지에 ISO가 있는지 확인하세요.',localStorage:'로컬',bootOrderIssue:'부팅 순서 문제',bootOrderWarning:'부팅 순서가 존재하지 않는 디스크를 참조합니다',bootOrder:'부팅 순서',dragToReorder:'클릭하여 전환, 화살표로 순서 변경',noBootDevices:'부팅 장치를 찾을 수 없습니다',resizeDiskHint:'증가량 (예: +10G) 또는 새 크기',// SMBIOS Settings smbiosSettings:'SMBIOS 설정',smbiosHint:'System Management BIOS - Windows 라이선스 및 VM 식별에 유용',applySmbiosFromClusterConfig:'클러스터 구성에서 SMBIOS 설정 적용',requiresRestart:'재시작 필요',readOnly:'읽기 전용',autoGenerated:'자동 생성',smbiosFormatHint:'문자와 숫자만 허용됩니다 (A-Za-z0-9)',preview:'미리보기',currentValue:'현재 값',managedByProxmox:'Proxmox에서 관리',willBeAutoGenerated:'저장 시 자동 생성됩니다',forceConntrack:'강제 (Conntrack 상태)',forceConntrackDesc:'conntrack 항목이 있어도 강제 마이그레이션',containerNoLiveMigration:'컨테이너는 진정한 라이브 마이그레이션을 지원하지 않습니다',startingMigration:'마이그레이션 시작:',migrationStarted:'마이그레이션 시작됨:',migrationFailed:'마이그레이션이 실패했습니다',clone:'복제',console:'콘솔',openConsole:'콘솔 열기',metrics:'메트릭',config:'구성',configuration:'구성',createVm:'새 VM 생성',createContainer:'새 컨테이너 생성',newVm:'새 VM',newContainer:'새 컨테이너',power:'전원',snapshot:'스냅샷',editSettings:'설정',refreshData:'새로고침',sshConsole:'SSH 콘솔',noNodesAvailable:'사용 가능한 노드가 없습니다. 클러스터 데이터가 로드될 때까지 기다려 주세요.',loadingStorage:'스토리지 목록 로딩 중...',noIsoAvailable:'ISO 이미지를 찾을 수 없습니다',noTemplateAvailable:'템플릿을 찾을 수 없습니다',noStorageAvailable:'사용 가능한 스토리지 없음',noIsoStorage:'ISO 콘텐츠가 있는 스토리지를 찾을 수 없습니다',ram:'RAM',cpu:'CPU',disk:'디스크',network:'네트워크',// VM Creation Wizard diff --git a/web/src/translations.js b/web/src/translations.js index e8b16d1..a7ac8a3 100644 --- a/web/src/translations.js +++ b/web/src/translations.js @@ -15478,6 +15478,8 @@ clusterReconfigured: '클러스터가 성공적으로 재구성되었습니다', reconfigure: '재구성', clusterHealth: '클러스터 상태', + clusterHealthTooltip: '상태 = 100 − (CPU×30% + RAM×30% + 스토리지×20% + 오프라인 노드×20%)\n스토리지 데이터 없음: CPU×37.5% + RAM×37.5% + 오프라인×25%\n\n80+: 우수\n60–79: 양호\n40–59: 경고\n< 40: 심각', + nodeScoreTooltip: '노드 점수 = CPU% + RAM% (낮을수록 좋음)\n\n< 100: 양호 (녹색)\n100–150: 주의 (노란색)\n> 150: 심각 (빨간색)', excellent: '우수', good: '양호', warning: '경고', @@ -15485,6 +15487,7 @@ nodesOnline: '온라인 노드', nodeJoinHint: '새 노드를 추가하려면 새 노드에서 실행하세요:', avgScore: '평균 점수', + avgStorage: '평균 스토리지', avgCpu: '평균 CPU', avgRam: '평균 RAM', From da0957a500a9491fbe02b4caebbd5d41a9892879 Mon Sep 17 00:00:00 2001 From: Lukas Alstrup Date: Tue, 7 Apr 2026 11:55:28 +0200 Subject: [PATCH 5/6] fix: unify ClusterHealth widget with badge/overview health score --- web/index.html | 25 +++++++++------- web/src/dashboard.js | 2 +- web/src/translations.js | 12 ++++---- web/src/vm_modals.js | 64 ++++++++++++++++++++++++++++------------- 4 files changed, 66 insertions(+), 37 deletions(-) diff --git a/web/index.html b/web/index.html index 218389c..030cd1c 100644 --- a/web/index.html +++ b/web/index.html @@ -2048,7 +2048,7 @@ poolPermissions:'Pool-Berechtigungen',poolPermissionsDesc:'Gewähren Sie Benutzern oder Gruppen Zugriff auf Proxmox Resource Pools. Berechtigungen gelten für alle VMs im Pool.',managePools:'Pools verwalten',poolManagerDesc:'Erstellen, bearbeiten und löschen Sie Resource Pools. Weisen Sie VMs zu Pools für organisierte Berechtigungsverwaltung zu.',createPool:'Pool erstellen',editPool:'Pool bearbeiten',deletePool:'Pool löschen',poolId:'Pool-ID',poolIdRequired:'Pool-ID ist erforderlich',poolIdHint:'Nur Buchstaben, Zahlen, Bindestriche und Unterstriche',poolIdCannotChange:'Pool-ID kann nicht geändert werden',poolCreated:'Pool erfolgreich erstellt',poolUpdated:'Pool erfolgreich aktualisiert',poolDeleted:'Pool erfolgreich gelöscht',confirmDeletePool:'Sind Sie sicher, dass Sie diesen Pool löschen möchten? Dies kann nicht rückgängig gemacht werden.',noPoolsYet:'Noch keine Pools',createFirstPool:'Erstellen Sie Ihren ersten Pool, um VMs zu organisieren',poolMembers:'Mitglieder',members:'Mitglieder',addVmToPool:'VM zum Pool hinzufügen',assignToPool:'Pool zuweisen',removeFromPool:'Aus Pool entfernen',vmAddedToPool:'VM zum Pool hinzugefügt',vmRemovedFromPool:'VM aus Pool entfernt',confirmRemoveVmFromPool:'VM aus diesem Pool entfernen?',selectVmToAdd:'Wählen Sie eine VM zum Hinzufügen aus',allVmsInPools:'Alle VMs sind bereits in Pools',optionalDescription:'Optionale Beschreibung...',userPermissions:'Benutzer-Berechtigungen',selectPool:'Pool auswählen',noPools:'Keine Resource Pools in diesem Cluster gefunden',noPoolPerms:'Keine Berechtigungen für diesen Pool konfiguriert',selectPoolFirst:'Wählen Sie einen Cluster und Pool zur Verwaltung',addPoolPerm:'Pool-Berechtigung hinzufügen',editPoolPerm:'Pool-Berechtigung bearbeiten',poolPermSaved:'Pool-Berechtigung gespeichert',poolPermDeleted:'Pool-Berechtigung entfernt',refreshPools:'Pools von Proxmox aktualisieren',poolCacheRefreshed:'Pool-Cache aktualisiert',confirmDeletePoolPerm:'Berechtigung entfernen?',subjectType:'Typ',permissionsFor:'Berechtigungen für',addPermission:'Berechtigung hinzufügen',groupName:'Gruppenname',auditLog:'Audit Log',auditLogDescription:'Alle Benutzeraktionen der letzten 90 Tage',noAuditLogs:'Keine Audit-Einträge vorhanden',action:'Aktion',details:'Details',timestamp:'Zeitstempel',ipAddress:'IP-Adresse',userCreated:'Benutzer erstellt',userUpdated:'Benutzer aktualisiert',userDeleted:'Benutzer gelöscht',userLogin:'Anmeldung',userLogout:'Abmeldung',passwordChanged:'Passwort geändert',clusterAdded:'Cluster hinzugefügt',apiTokenWarningTitle:'API Token Authentifizierung',apiTokenWarningDesc:'Ohne Passwort funktionieren SSH-Features nicht (HA, Rolling Updates, SMBIOS, Node Shell). Empfohlen: root@pam + Passwort verwenden — PegaProx erstellt automatisch einen API-Token für 2FA-Kompatibilität.',apiTokenRecommended:'Empfohlen bei aktivierter 2FA',apiTokenCreated:'API-Token auf PVE erstellt — 2FA kann jetzt aktiviert werden',sshPasswordStillNeeded:'SSH nutzt weiterhin das Passwort (HA, Wartung) — bitte nicht ändern',authModeToken:'API: Token (2FA-sicher)',authModePassword:'API: Passwort',sshAuthMode:'SSH: Passwort',dontChangePvePassword:'PVE-Passwort nicht ändern ohne es hier zu aktualisieren',clusterDeleted:'Cluster gelöscht',clusterConfigChanged:'Cluster-Konfiguration geändert',vmStarted:'VM gestartet',vmStopped:'VM gestoppt',vmRestarted:'VM neu gestartet',vmCreated:'VM erstellt',vmDeleted:'VM gelöscht',vmCloned:'VM geklont',vmMigrated:'VM migriert',vmUnlocked:'VM entsperrt',vmLocked:'VM gesperrt',unlockVm:'VM entsperren',lockReason:'Sperrgrund',unlockWarning:'Warnung: Das Entsperren einer VM während einer aktiven Operation kann zu Datenverlust oder anderen Problemen führen. Nur fortfahren wenn die Operation fehlgeschlagen oder abgebrochen wurde.',vmBulkMigrated:'Massen-Migration',vmConfigChanged:'VM-Konfiguration geändert',vmSuspended:'VM angehalten',vmResumed:'VM fortgesetzt',vmDiskAdded:'Festplatte hinzugefügt',vmDiskRemoved:'Festplatte entfernt',vmDiskResized:'Festplatte vergrößert',vmDiskMoved:'Festplatte verschoben',vmNetworkAdded:'Netzwerk hinzugefügt',vmNetworkRemoved:'Netzwerk entfernt',vmNetworkUpdated:'Netzwerk aktualisiert',removeDiskConfirm:'Festplatte wirklich entfernen',detachDisk:'Abklemmen',importDisk:'Festplatte importieren',importDiskDesc:'Vorhandenes Disk-Image von Storage importieren',selectImportStorage:'Quell-Storage auswählen',selectDiskImage:'Disk-Image auswählen',targetBus:'Ziel-Bus',reassignOwner:'Besitzer ändern',reassignOwnerDesc:'Festplatte einer anderen VM zuweisen',targetVm:'Ziel-VM',diskReassigned:'Festplatte neu zugewiesen',diskImported:'Festplatte importiert',noImportableDisks:'Keine importierbaren Disk-Images gefunden',reassign:'Neu zuweisen',import:'Importieren',detachDiskConfirm:'Festplatte wirklich abklemmen? Sie wird zu einer ungenutzten Festplatte.',diskDetached:'Festplatte abgeklemmt',diskDeleted:'Festplatte gelöscht',diskAdded:'Festplatte hinzugefügt',dataWillBeDeleted:'Daten werden dauerhaft gelöscht!',removeNetworkConfirm:'Netzwerk wirklich entfernen',snapshotCreated:'Snapshot erstellt',snapshotDeleted:'Snapshot gelöscht',snapshotRestored:'Snapshot wiederhergestellt',rollbackStarted:'Rollback gestartet',includeRam:'RAM einschließen',snapshotName:'Snapshot Name',rollbackConfirm:'Wirklich zu diesem Snapshot zurückrollen? Dies kann nicht rückgängig gemacht werden!',replicationCreated:'Replikation erstellt',replicationDeleted:'Replikation gelöscht',replicationTriggered:'Replikation manuell gestartet',haEnabled:'HA aktiviert',haDisabled:'HA deaktiviert',noFallbackHosts:'Keine Fallback-Hosts gefunden (Single-Node Cluster?)',haVmAdded:'VM zu HA hinzugefügt',haVmRemoved:'VM aus HA entfernt',nodeMaintenanceEntered:'Wartungsmodus aktiviert',nodeMaintenanceExited:'Wartungsmodus beendet',nodeUpdateStarted:'Node-Update gestartet',twoFactorAuth:'2-Faktor-Authentifizierung',twoFactorEnabled:'2FA aktiviert',twoFactorDisabled:'2FA deaktiviert',enable2FA:'2FA aktivieren',disable2FA:'2FA deaktivieren',setup2FA:'2FA einrichten',scan2FACode:'QR-Code mit Authenticator-App scannen',enter2FACode:'6-stelligen Code eingeben',verify2FA:'Verifizieren',secretKey:'Geheimer Schlüssel',twoFARequired:'2FA-Code erforderlich',invalid2FACode:'Ungültiger 2FA-Code',force2FA:'2FA erzwingen',force2FADesc:'Alle Benutzer müssen Zwei-Faktor-Authentifizierung einrichten bevor sie PegaProx verwenden können.',force2FAHint:'OIDC/Entra-Benutzer sind ausgenommen (nutzen MFA ihres Identity Providers). Benutzer ohne 2FA sehen beim Login einen Einrichtungs-Dialog.',force2FAExcludeAdmins:'Admin-Konten ausschließen',force2FAExcludeAdminsDesc:'Admins können PegaProx ohne 2FA nutzen',force2FASetupTitle:'2FA-Einrichtung erforderlich',force2FASetupDesc:'Ihr Administrator hat Zwei-Faktor-Authentifizierung für alle Benutzer verpflichtend gemacht. Bitte richten Sie 2FA ein um fortzufahren.',resetPassword:'Passwort zurücksetzen',passwordManagedExternally:'Ihr Passwort wird extern verwaltet.',passwordManagedExternallyHint:'Bitte ändern Sie Ihr Passwort direkt in Ihrem Verzeichnisdienst.',newPassword:'Neues Passwort',confirmPassword:'Passwort bestätigen',currentPassword:'Aktuelles Passwort',passwordsDoNotMatch:'Passwörter stimmen nicht überein',passwordTooShort:'Passwort muss mindestens 4 Zeichen haben',passwordResetSuccess:'Passwort erfolgreich geändert',myProfile:'Mein Profil',security:'Sicherheit',snapshotNotSupported:'Snapshots werden nicht unterstützt',snapshotWarnings:'Snapshot-Warnungen',filterByUser:'Nach Benutzer filtern',filterByAction:'Nach Aktion filtern',allUsers:'Alle Benutzer',allActions:'Alle Aktionen',exportAuditLog:'Exportieren',refreshAuditLog:'Aktualisieren',// Header addCluster:'Cluster hinzufügen',addXcpngPool:'XCP-ng Pool hinzufügen (Tech Preview)',xcpngConnectHint:'Verbinde dich mit dem Pool-Master. XAPI Port 443 wird standardmäßig verwendet.',xcpngTechPreviewNote:'Einige Funktionen sind eingeschränkt oder können sich ändern.',pveClusterDesc:'Virtuelle Maschinen & Container',pbsDesc:'Backup-Verwaltung',vmwareDesc:'ESXi-Infrastruktur',xcpngDesc:'XCP-ng / Xen Hypervisor (Tech Preview)',addConnection:'Verbindung hinzufügen',connectionType:'Verbindungstyp',clusterManagement:'PegaProx Cluster Management for Proxmox VE',// Tabs overview:'Übersicht',resources:'Ressourcen',datacenter:'Datacenter',settings:'Einstellungen',of:'von',showing:'Zeige',perPage:'Pro Seite',loadingDatacenter:'Lade Datacenter Daten...',// Cluster -clusters:'Cluster',noClusterSelected:'Kein Cluster ausgewählt',allClustersOverview:'Alle Cluster Übersicht',allClusters:'Alle Cluster',multiClusterSummary:'Zusammenfassung aller verwalteten Cluster',clustersConnected:'Cluster verbunden',clusterOverview:'Cluster Übersicht',vmsRunning:'VMs laufend',vmsStopped:'VMs gestoppt',noClustersConfigured:'Keine Cluster konfiguriert',addClusterToStart:'Füge einen Cluster hinzu um zu beginnen',clickClusterTip:'Klicke auf eine Cluster-Zeile oder wähle aus der Sidebar um Details anzuzeigen und Ressourcen zu verwalten.',health:'Zustand',average:'Durchschnitt',noDataAvailable:'Keine Daten verfügbar',clickToManage:'Klicken zum Verwalten',sortBy:'Sortieren nach',ungrouped:'Nicht gruppiert',alerts:'Warnungen',updated:'Aktualisiert',justNow:'gerade eben',topResources:'Top Ressourcen',highestCpuUsage:'Höchste CPU- und RAM-Auslastung über alle Cluster',clickToOpenVm:'Klicken um VM zu öffnen',selectCluster:'Wähle einen Cluster aus der Liste aus',addFirstCluster:'Ersten Cluster hinzufügen',connectCluster:'Verbinde einen Proxmox Cluster mit PegaProx',clusterName:'Cluster Name',host:'Host',username:'Benutzername',password:'Passwort',passwordOrToken:'Passwort / Token',apiTokenHint:'Für API Tokens: user@realm!tokenid',sslVerification:'SSL Verifizierung',connecting:'Verbinde...',addNewCluster:'Neuen Cluster hinzufügen',testConnection:'Verbindung testen',deleteCluster:'Cluster löschen',deleteClusterConfirm:'Cluster wirklich löschen?',reconfigureCluster:'Cluster neu konfigurieren',reconfigureHint:'Geben Sie Ihr PegaProx-Passwort ein, um Ihre Identität zu bestätigen.',clusterReconfigured:'Cluster erfolgreich neu konfiguriert',reconfigure:'Neu konfigurieren',clusterHealth:'Cluster Gesundheit',clusterHealthTooltip:'Gesundheit = 100 − (CPU×30% + RAM×30% + Speicher×20% + Offline-Nodes×20%)\nOhne Speicherdaten: CPU×37,5% + RAM×37,5% + Offline×25%\n\n80+: Ausgezeichnet\n60–79: Gut\n40–59: Warnung\n< 40: Kritisch',nodeScoreTooltip:'Node-Score = CPU% + RAM% (niedriger ist besser)\n\n< 100: Gut (grün)\n100–150: Erhöht (gelb)\n> 150: Kritisch (rot)',excellent:'Ausgezeichnet',good:'Gut',warning:'Warnung',critical:'Kritisch',nodesOnline:'Nodes Online',nodeJoinHint:'Um einen neuen Node hinzuzufügen, führe auf dem neuen Node aus:',avgScore:'Avg. Score',avgStorage:'Avg. Speicher',avgCpu:'Avg. CPU',avgRam:'Avg. RAM',// Nodes +clusters:'Cluster',noClusterSelected:'Kein Cluster ausgewählt',allClustersOverview:'Alle Cluster Übersicht',allClusters:'Alle Cluster',multiClusterSummary:'Zusammenfassung aller verwalteten Cluster',clustersConnected:'Cluster verbunden',clusterOverview:'Cluster Übersicht',vmsRunning:'VMs laufend',vmsStopped:'VMs gestoppt',noClustersConfigured:'Keine Cluster konfiguriert',addClusterToStart:'Füge einen Cluster hinzu um zu beginnen',clickClusterTip:'Klicke auf eine Cluster-Zeile oder wähle aus der Sidebar um Details anzuzeigen und Ressourcen zu verwalten.',health:'Zustand',average:'Durchschnitt',noDataAvailable:'Keine Daten verfügbar',clickToManage:'Klicken zum Verwalten',sortBy:'Sortieren nach',ungrouped:'Nicht gruppiert',alerts:'Warnungen',updated:'Aktualisiert',justNow:'gerade eben',topResources:'Top Ressourcen',highestCpuUsage:'Höchste CPU- und RAM-Auslastung über alle Cluster',clickToOpenVm:'Klicken um VM zu öffnen',selectCluster:'Wähle einen Cluster aus der Liste aus',addFirstCluster:'Ersten Cluster hinzufügen',connectCluster:'Verbinde einen Proxmox Cluster mit PegaProx',clusterName:'Cluster Name',host:'Host',username:'Benutzername',password:'Passwort',passwordOrToken:'Passwort / Token',apiTokenHint:'Für API Tokens: user@realm!tokenid',sslVerification:'SSL Verifizierung',connecting:'Verbinde...',addNewCluster:'Neuen Cluster hinzufügen',testConnection:'Verbindung testen',deleteCluster:'Cluster löschen',deleteClusterConfirm:'Cluster wirklich löschen?',reconfigureCluster:'Cluster neu konfigurieren',reconfigureHint:'Geben Sie Ihr PegaProx-Passwort ein, um Ihre Identität zu bestätigen.',clusterReconfigured:'Cluster erfolgreich neu konfiguriert',reconfigure:'Neu konfigurieren',clusterHealth:'Cluster Gesundheit',clusterHealthTooltip:'Gesundheit = 100 − (CPU×30% + RAM×30% + Speicher×20% + Offline-Nodes×20%)\n\n80+: Ausgezeichnet\n60–79: Gut\n40–59: Warnung\n< 40: Kritisch',nodeScoreTooltip:'Node-Score = CPU% + RAM% (niedriger ist besser)\n\n< 100: Gut (grün)\n100–150: Erhöht (gelb)\n> 150: Kritisch (rot)',excellent:'Ausgezeichnet',good:'Gut',warning:'Warnung',critical:'Kritisch',nodesOnline:'Nodes Online',nodeJoinHint:'Um einen neuen Node hinzuzufügen, führe auf dem neuen Node aus:',avgScore:'Avg. Score',avgStorage:'Avg. Speicher',avgCpu:'Avg. CPU',avgRam:'Avg. RAM',// Nodes nodes:'Nodes',node:'Node',loadingMetrics:'Lade Metriken...',connectionError:'Verbindungsfehler',retry:'Erneut versuchen',checkConnectionAndRetry:'Bitte überprüfen Sie Ihre Verbindung und versuchen Sie es erneut.',connectionTimeout:'Zeitüberschreitung - Proxmox nicht erreichbar',maintenance:'Wartung',enterMaintenance:'Wartungsmodus aktivieren',exitMaintenance:'Wartungsmodus beenden',maintenanceMode:'Wartungsmodus',update:'Update',startUpdate:'Update starten',nodeConfig:'Node Konfiguration',cpuHistory:'CPU Verlauf',ramHistory:'RAM Verlauf',ramUsage:'RAM Nutzung',cpuUsage:'CPU Nutzung',diskUsage:'Disk Nutzung',allocated:'zugewiesen',showMore:'Mehr anzeigen',showLess:'Weniger anzeigen',// VMs & Resources virtualMachines:'Virtuelle Maschinen',containers:'Container',vm:'VM',lxc:'LXC',lxcContainer:'LXC Container',container:'Container',guests:'Guests',start:'Starten',stop:'Stoppen',shutdown:'Herunterfahren',reboot:'Neustarten',forceStop:'Force Stop',forceReset:'Force Reset',forceStopConfirm:'wirklich hart ausschalten? Dies kann zu Datenverlust führen!',migrate:'Migrieren',migrateVm:'VM migrieren',crossClusterMigration:'Cross-Cluster Migration',crossClusterMigrateDesc:'VM zu anderem Proxmox-Cluster migrieren',crossClusterMigrate:'Cross-Cluster migrieren',crossClusterStarted:'Cross-Cluster Migration gestartet',crossClusterFailed:'Cross-Cluster Migration fehlgeschlagen',crossClusterLB:'Cross-Cluster Lastverteilung',crossClusterLBEnabled:'Cross-Cluster LB aktiviert',crossClusterLBDisabled:'Cross-Cluster LB deaktiviert',crossClusterLBThreshold:'Score-Schwellenwert',crossClusterLBInterval:'Prüfintervall',crossClusterLBDryRun:'Simulation (Dry Run)',crossClusterLBLastRun:'Letzter Lauf',crossClusterReplication:'Cross-Cluster Replikation',crossClusterReplicationDesc:'VM-Snapshots zu anderem Cluster replizieren (DR)',groupOverview:'Gruppenübersicht',groupSettings:'Gruppeneinstellungen',groupSettingsSaved:'Gruppeneinstellungen gespeichert',replicationSchedule:'Zeitplan',replicationRetention:'Aufbewahrung',maxMigrations:'Max. Migrationen pro Zyklus',lbHistory:'LB-Verlauf',clusterScore:'Cluster-Score',noReplicationJobs:'Keine Cross-Cluster Replikationsjobs',createReplicationJob:'DR-Job erstellen',replicationStarted:'Replikation gestartet',replicationJobCreated:'Replikationsjob erstellt',replicationJobDeleted:'Replikationsjob gelöscht',recentLbActions:'Letzte Cross-Cluster LB-Aktionen',noLbEvents:'Noch keine Cross-Cluster LB-Ereignisse',enableCrossClusterLB:'Cross-Cluster Lastverteilung aktivieren',lbDescription:'VMs automatisch zwischen Clustern migrieren, wenn Ressourcen-Schwellenwerte überschritten werden',dryRunMode:'Dry Run / Simulationsmodus',cpuThreshold:'CPU-Schwellenwert (%)',lbStatus:'Lastverteilungs-Status',lbExplanation:'Die Cross-Cluster Lastverteilung überwacht CPU- und RAM-Auslastung über alle Cluster in dieser Gruppe. Wenn ein Cluster den konfigurierten Schwellenwert überschreitet, werden VMs automatisch zu einem weniger ausgelasteten Cluster migriert.',lbDryRunExplanation:'Aktivieren Sie zuerst den Dry Run-Modus, um geplante Aktionen zu prüfen, bevor Sie Live-Migrationen aktivieren.',neverRun:'Noch nie ausgeführt',newCrossClusterReplication:'Neue Cross-Cluster Replikation',confirmDeleteXRepl:'Diesen Cross-Cluster Replikationsjob wirklich löschen?',addDrJob:'DR-Job hinzufügen',scheduleCron:'Zeitplan (Cron)',targetStorageHint:'Storage-Name auf dem Ziel-Cluster',maxMigrationsHint:'Maximale Anzahl VMs pro Prüfzyklus (1-5)',proxlbCredit:'ProxLB von gyptazy',proxlbCreditDesc:'Unsere Lastverteilungsfunktionalität basiert auf der hervorragenden Arbeit von ProxLB. Besonderer Dank an gyptazy für die Erstellung und Open-Source-Bereitstellung dieses großartigen Tools!',xReplCreated:'Cross-Cluster Replikation erstellt',xReplDeleted:'Cross-Cluster Replikation gelöscht',xReplStarted:'Cross-Cluster Replikation gestartet',xReplCreateFailed:'Job-Erstellung fehlgeschlagen',xReplDeleteFailed:'Löschen fehlgeschlagen',xReplStartFailed:'Start fehlgeschlagen',lastRunPrefix:'Zuletzt',runNow:'Jetzt ausführen',loadingCrossClusterResources:'Lade Storage/Netzwerk für alle Cluster...',noCommonStorages:'Kein gemeinsamer Storage über alle Cluster gefunden',noCommonBridges:'Keine gemeinsame Bridge über alle Cluster gefunden',selectBridge:'Bridge auswählen...',crossClusterThresholdDesc:'CPU-Schwellenwert für Cluster-Imbalance (10-80%)',crossClusterIntervalDesc:'Zeit zwischen Prüfzyklen',crossClusterMaxMigrationsDesc:'Maximale Migrationen pro Prüfzyklus',commonStorageHint:'Nur Storages, die auf allen Clustern in der Gruppe verfügbar sind',commonBridgeHint:'Nur Bridges, die auf allen Clustern in der Gruppe verfügbar sind',includeContainers:'Container einbeziehen',includeContainersDesc:'Container (LXC) beim Cross-Cluster-Balancing berücksichtigen',containerMigrationWarning:'Container werden beim Migrieren neu gestartet (Downtime)',excludedVMsCrossCluster:'Ausgeschlossene VMs/Container',excludedVMsCrossClusterDesc:'VMs und Container, die vom automatischen Cross-Cluster-Balancing ausgeschlossen sind',clusterExcludedVMs:'Ausgeschlossene VMs',noExcludedVMsInGroup:'Keine VMs ausgeschlossen',selectCluster:'Cluster auswählen...',selectClusterFirst:'Bitte zuerst einen Ziel-Cluster auswählen',simulationMode:'Simulationsmodus',sourceVm:'Quell-VM',targetCluster:'Ziel-Cluster',targetNode:'Ziel-Node',targetStorage:'Ziel-Storage',targetBridge:'Ziel-Netzwerk (Bridge)',moveDisk:'Festplatte verschieben',resizeDisk:'Festplatte vergrößern',diskResized:'Festplatte vergrößert',deleteSourceDisk:'Quelle nach Verschieben löschen',deleteSourceDiskWarning:'Die Original-Festplatte bleibt auf dem Quell-Storage. Sie können sie später manuell entfernen.',move:'Verschieben',from:'von',noNetworkInterfaces:'Keine Netzwerk-Interfaces',nameRequired:'Name erforderlich',newVmid:'Neue VMID (optional)',sameIdPlaceholder:'Leer = gleiche ID',liveMigrationOption:'Live-Migration (VM läuft weiter)',deleteSourceAfter:'Quell-VM nach Migration löschen',largeDiskWarning:'Große Disk erkannt',largeDiskExplanation:'Live-Migration bei Disks >100GB kann mit "401 Unauthorized" fehlschlagen (Proxmox WebSocket-Ticket Timeout). Der Server wechselt automatisch auf Offline-Migration, es sei denn, Sie erzwingen Online.',forceOnlineMigration:'Online-Migration trotzdem erzwingen (kann fehlschlagen)',autoTokenInfo:'PegaProx erstellt temporäre API-Tokens für die Migration und löscht diese automatisch nach Abschluss.',clusterReachableInfo:'Die Cluster müssen sich gegenseitig über das Netzwerk erreichen können. Der konfigurierte Benutzer muss Rechte zum Erstellen von API-Tokens haben.',selectCluster:'Cluster auswählen...',selectNode:'Node auswählen',selectStorage:'Storage auswählen...',loadingNodes:'Lade Ziel-Nodes...',loadingStorageNetwork:'Lade Storage/Netzwerk...',liveMigration:'Live-Migration (ohne Downtime)',on:'auf',targetStorage:'Ziel-Storage',sameAsSource:'Gleich wie Quelle',loadingStorages:'Lade Storages',free:'frei',withLocalDisks:'Mit lokalen Disks',withLocalDisksDesc:'Lokale Disks zum Ziel-Storage migrieren',localDisksDetected:'Diese VM hat lokale Disks. Migration erfordert Kopieren der Disk-Daten.',requiredForThisVm:'Erforderlich für diese VM',cdDvdMounted:'CD/DVD eingelegt',cdDvdMigrationWarning:'Live-Migration ist mit eingelegter CD/DVD nicht möglich. Bitte zuerst die CD/DVD auswerfen oder Offline-Migration verwenden.',isoMounted:'ISO/CD-ROM eingelegt',isoEjected:'CD-ROM ausgeworfen',isoMigrationWarning:'Migration kann fehlschlagen wenn die ISO auf dem Ziel-Node nicht verfügbar ist. CD/DVD auswerfen oder ISO auf Shared Storage sicherstellen.',localStorage:'Lokal',bootOrderIssue:'Boot Order Problem',bootOrderWarning:'Boot Order referenziert nicht-existierende Disks',bootOrder:'Boot-Reihenfolge',dragToReorder:'Klicken zum Umschalten, Pfeile zum Sortieren',noBootDevices:'Keine Boot-Geräte gefunden',resizeDiskHint:'Erhöhen um (z.B. +10G) oder neue Größe',// SMBIOS Settings smbiosSettings:'SMBIOS Einstellungen',smbiosHint:'System Management BIOS - nützlich für Windows-Lizenzierung und VM-Identifikation',applySmbiosFromClusterConfig:'SMBIOS-Einstellungen aus Cluster-Konfiguration anwenden',requiresRestart:'NEUSTART',readOnly:'schreibgeschützt',autoGenerated:'auto-generiert',smbiosFormatHint:'Nur Buchstaben und Zahlen erlaubt (A-Za-z0-9)',preview:'Vorschau',currentValue:'Aktueller Wert',managedByProxmox:'von Proxmox verwaltet',willBeAutoGenerated:'Wird beim Speichern automatisch generiert',forceConntrack:'Force (Conntrack State)',forceConntrackDesc:'Erzwingt Migration auch wenn Conntrack-Einträge existieren',containerNoLiveMigration:'Container unterstützen keine echte Live-Migration',startingMigration:'Starte Migration von',migrationStarted:'Migration gestartet:',migrationFailed:'Migration fehlgeschlagen',clone:'Klonen',console:'Konsole',openConsole:'Konsole öffnen',metrics:'Metriken',config:'Konfiguration',configuration:'Konfiguration',createVm:'Neue VM erstellen',createContainer:'Neuen Container erstellen',newVm:'Neue VM',newContainer:'Neuer Container',power:'Energieoptionen',snapshot:'Snapshot',editSettings:'Einstellungen',refreshData:'Aktualisieren',sshConsole:'SSH-Konsole',noNodesAvailable:'Keine Nodes verfügbar. Bitte warten Sie bis die Cluster-Daten geladen sind.',loadingStorage:'Lade Storage-Liste...',noIsoAvailable:'Keine ISO-Images gefunden',noTemplateAvailable:'Keine Templates gefunden',noStorageAvailable:'Keine Storage verfügbar',noIsoStorage:'Kein Storage mit ISO-Inhalt gefunden',ram:'RAM',cpu:'CPU',disk:'Disk',network:'Netzwerk',// VM Creation Wizard @@ -2136,7 +2136,7 @@ poolPermissions:'Pool Permissions',poolPermissionsDesc:'Grant users or groups access to Proxmox resource pools. Permissions apply to all VMs within the pool.',managePools:'Manage Pools',poolManagerDesc:'Create, edit, and delete resource pools. Assign VMs to pools for organized permission management.',createPool:'Create Pool',editPool:'Edit Pool',deletePool:'Delete Pool',poolId:'Pool ID',poolIdRequired:'Pool ID is required',poolIdHint:'Letters, numbers, dashes and underscores only',poolIdCannotChange:'Pool ID cannot be changed',poolCreated:'Pool created successfully',poolUpdated:'Pool updated successfully',poolDeleted:'Pool deleted successfully',confirmDeletePool:'Are you sure you want to delete this pool? This cannot be undone.',noPoolsYet:'No pools yet',createFirstPool:'Create your first pool to organize VMs',poolMembers:'Members',members:'members',addVmToPool:'Add VM to Pool',assignToPool:'Assign to Pool',removeFromPool:'Remove from pool',vmAddedToPool:'VM added to pool',vmRemovedFromPool:'VM removed from pool',confirmRemoveVmFromPool:'Remove VM from this pool?',selectVmToAdd:'Select a VM to add to this pool',allVmsInPools:'All VMs are already in pools',optionalDescription:'Optional description...',userPermissions:'User Permissions',selectPool:'Select Pool',noPools:'No resource pools found in this cluster',noPoolPerms:'No permissions configured for this pool',selectPoolFirst:'Select a cluster and pool to manage permissions',addPoolPerm:'Add Pool Permission',editPoolPerm:'Edit Pool Permission',poolPermSaved:'Pool permission saved',poolPermDeleted:'Pool permission removed',refreshPools:'Refresh pools from Proxmox',poolCacheRefreshed:'Pool cache refreshed',confirmDeletePoolPerm:'Remove permission?',subjectType:'Type',permissionsFor:'Permissions for',addPermission:'Add Permission',groupName:'Group Name',auditLog:'Audit Log',auditLogDescription:'All user actions from the last 90 days',noAuditLogs:'No audit entries available',action:'Action',details:'Details',timestamp:'Timestamp',ipAddress:'IP Address',userCreated:'User created',userUpdated:'User updated',userDeleted:'User deleted',userLogin:'Login',userLogout:'Logout',passwordChanged:'Password changed',clusterAdded:'Cluster added',apiTokenWarningTitle:'API Token Authentication',apiTokenWarningDesc:'Without a password, SSH features won\'t work (HA, Rolling Updates, SMBIOS, Node Shell). Recommended: Use root@pam + password — PegaProx auto-creates an API token for 2FA compatibility.',apiTokenRecommended:'Recommended when 2FA is enabled',apiTokenCreated:'API token created on PVE — 2FA can now be safely enabled',sshPasswordStillNeeded:'SSH still uses the password (HA, maintenance) — don\'t change it',authModeToken:'API: Token (2FA-safe)',authModePassword:'API: Password',sshAuthMode:'SSH: Password',dontChangePvePassword:'Don\'t change the PVE password without updating it here',clusterDeleted:'Cluster deleted',clusterConfigChanged:'Cluster config changed',vmStarted:'VM started',vmStopped:'VM stopped',vmRestarted:'VM restarted',vmCreated:'VM created',vmDeleted:'VM deleted',vmCloned:'VM cloned',vmMigrated:'VM migrated',vmUnlocked:'VM unlocked',vmLocked:'VM locked',unlockVm:'Unlock VM',lockReason:'Lock Reason',unlockWarning:'Warning: Unlocking a VM during an active operation may cause data corruption or other issues. Only proceed if you are sure the operation has failed or been cancelled.',vmBulkMigrated:'Bulk migration',vmConfigChanged:'VM config changed',vmSuspended:'VM suspended',vmResumed:'VM resumed',vmDiskAdded:'Disk added',vmDiskRemoved:'Disk removed',vmDiskResized:'Disk resized',vmDiskMoved:'Disk moved',vmNetworkAdded:'Network added',vmNetworkRemoved:'Network removed',vmNetworkUpdated:'Network updated',removeDiskConfirm:'Really remove disk',detachDisk:'Detach',importDisk:'Import Disk',importDiskDesc:'Import existing disk image from storage',selectImportStorage:'Select Source Storage',selectDiskImage:'Select Disk Image',targetBus:'Target Bus',reassignOwner:'Reassign Owner',reassignOwnerDesc:'Assign disk to a different VM',targetVm:'Target VM',diskReassigned:'Disk reassigned',diskImported:'Disk imported',noImportableDisks:'No importable disk images found',reassign:'Reassign',import:'Import',detachDiskConfirm:'Really detach disk? It will become an unused disk.',diskDetached:'Disk detached',diskDeleted:'Disk deleted',diskAdded:'Disk added',dataWillBeDeleted:'Data will be permanently deleted!',removeNetworkConfirm:'Really remove network',snapshotCreated:'Snapshot created',snapshotDeleted:'Snapshot deleted',snapshotRestored:'Snapshot restored',rollbackStarted:'Rollback started',includeRam:'Include RAM',snapshotName:'Snapshot name',rollbackConfirm:'Really rollback to this snapshot? This cannot be undone!',replicationCreated:'Replication created',replicationDeleted:'Replication deleted',replicationTriggered:'Replication triggered',haEnabled:'HA enabled',haDisabled:'HA disabled',noFallbackHosts:'No fallback hosts found (Single-Node Cluster?)',haVmAdded:'VM added to HA',haVmRemoved:'VM removed from HA',nodeMaintenanceEntered:'Maintenance mode entered',nodeMaintenanceExited:'Maintenance mode exited',nodeUpdateStarted:'Node update started',twoFactorAuth:'Two-Factor Authentication',twoFactorEnabled:'2FA enabled',twoFactorDisabled:'2FA disabled',enable2FA:'Enable 2FA',disable2FA:'Disable 2FA',setup2FA:'Setup 2FA',scan2FACode:'Scan QR code with authenticator app',enter2FACode:'Enter 6-digit code',verify2FA:'Verify',secretKey:'Secret Key',twoFARequired:'2FA code required',invalid2FACode:'Invalid 2FA code',force2FA:'Enforce 2FA',force2FADesc:'Require all users to set up Two-Factor Authentication before they can use PegaProx.',force2FAHint:'OIDC/Entra users are exempt (they use their Identity Provider\'s MFA). Users without 2FA will see a setup dialog on login.',force2FAExcludeAdmins:'Exclude admin accounts',force2FAExcludeAdminsDesc:'Admins can use PegaProx without 2FA',force2FASetupTitle:'2FA Setup Required',force2FASetupDesc:'Your administrator has made Two-Factor Authentication mandatory for all users. Please set up 2FA to continue.',resetPassword:'Reset Password',passwordManagedExternally:'Your password is managed externally.',passwordManagedExternallyHint:'Please change your password directly in your directory service.',newPassword:'New Password',confirmPassword:'Confirm Password',currentPassword:'Current Password',passwordsDoNotMatch:'Passwords do not match',passwordTooShort:'Password must be at least 4 characters',passwordResetSuccess:'Password changed successfully',myProfile:'My Profile',security:'Security',snapshotNotSupported:'Snapshots not supported',snapshotWarnings:'Snapshot warnings',filterByUser:'Filter by user',filterByAction:'Filter by action',allUsers:'All Users',allActions:'All Actions',exportAuditLog:'Export',refreshAuditLog:'Refresh',// Header addCluster:'Add Cluster',addXcpngPool:'Add XCP-ng Pool (Tech Preview)',xcpngConnectHint:'Connect to the pool master host. XAPI port 443 is used by default.',xcpngTechPreviewNote:'Some features may be limited or subject to change.',pveClusterDesc:'Virtual machines & containers',pbsDesc:'Backup management',vmwareDesc:'ESXi infrastructure',xcpngDesc:'XCP-ng / Xen Hypervisor (Tech Preview)',addConnection:'Add Connection',connectionType:'Connection Type',clusterManagement:'PegaProx Cluster Management for Proxmox VE',// Tabs overview:'Overview',resources:'Resources',datacenter:'Datacenter',settings:'Settings',of:'of',showing:'Showing',perPage:'Per page',loadingDatacenter:'Loading datacenter data...',// Cluster -clusters:'Clusters',noClusterSelected:'No Cluster Selected',allClustersOverview:'All Clusters Overview',allClusters:'All Clusters',multiClusterSummary:'Summary of all managed clusters',clustersConnected:'Clusters Connected',clusterOverview:'Cluster Overview',vmsRunning:'VMs Running',vmsStopped:'VMs Stopped',noClustersConfigured:'No clusters configured',addClusterToStart:'Add a cluster to get started',clickClusterTip:'Click on a cluster row or select from the sidebar to view detailed information and manage resources.',health:'Health',average:'Average',noDataAvailable:'No data available',clickToManage:'Click to manage',sortBy:'Sort by',ungrouped:'Ungrouped',alerts:'Alerts',updated:'Updated',justNow:'just now',topResources:'Top Resources',highestCpuUsage:'Highest CPU and RAM usage across all clusters',clickToOpenVm:'Click to open VM',selectCluster:'Select a cluster from the list',addFirstCluster:'Add First Cluster',connectCluster:'Connect a Proxmox cluster with PegaProx',clusterName:'Cluster Name',host:'Host',username:'Username',password:'Password',passwordOrToken:'Password / Token',apiTokenHint:'For API tokens: user@realm!tokenid',sslVerification:'SSL Verification',connecting:'Connecting...',addNewCluster:'Add New Cluster',testConnection:'Test Connection',deleteCluster:'Delete Cluster',deleteClusterConfirm:'Really delete cluster?',reconfigureCluster:'Re-configure Cluster',reconfigureHint:'Enter your PegaProx password to verify your identity.',clusterReconfigured:'Cluster re-configured successfully',reconfigure:'Re-configure',clusterHealth:'Cluster Health',clusterHealthTooltip:'Health = 100 − (CPU×30% + RAM×30% + Storage×20% + Offline Nodes×20%)\nWithout storage data: CPU×37.5% + RAM×37.5% + Offline×25%\n\n80+: Excellent\n60–79: Good\n40–59: Warning\n< 40: Critical',nodeScoreTooltip:'Node score = CPU% + RAM% (lower is better)\n\n< 100: Good (green)\n100–150: Elevated (yellow)\n> 150: Critical (red)',excellent:'Excellent',good:'Good',warning:'Warning',critical:'Critical',nodesOnline:'Nodes Online',nodeJoinHint:'To add a new node, run on the new node:',avgScore:'Avg. Score',avgStorage:'Avg. Storage',avgCpu:'Avg. CPU',avgRam:'Avg. RAM',// Nodes +clusters:'Clusters',noClusterSelected:'No Cluster Selected',allClustersOverview:'All Clusters Overview',allClusters:'All Clusters',multiClusterSummary:'Summary of all managed clusters',clustersConnected:'Clusters Connected',clusterOverview:'Cluster Overview',vmsRunning:'VMs Running',vmsStopped:'VMs Stopped',noClustersConfigured:'No clusters configured',addClusterToStart:'Add a cluster to get started',clickClusterTip:'Click on a cluster row or select from the sidebar to view detailed information and manage resources.',health:'Health',average:'Average',noDataAvailable:'No data available',clickToManage:'Click to manage',sortBy:'Sort by',ungrouped:'Ungrouped',alerts:'Alerts',updated:'Updated',justNow:'just now',topResources:'Top Resources',highestCpuUsage:'Highest CPU and RAM usage across all clusters',clickToOpenVm:'Click to open VM',selectCluster:'Select a cluster from the list',addFirstCluster:'Add First Cluster',connectCluster:'Connect a Proxmox cluster with PegaProx',clusterName:'Cluster Name',host:'Host',username:'Username',password:'Password',passwordOrToken:'Password / Token',apiTokenHint:'For API tokens: user@realm!tokenid',sslVerification:'SSL Verification',connecting:'Connecting...',addNewCluster:'Add New Cluster',testConnection:'Test Connection',deleteCluster:'Delete Cluster',deleteClusterConfirm:'Really delete cluster?',reconfigureCluster:'Re-configure Cluster',reconfigureHint:'Enter your PegaProx password to verify your identity.',clusterReconfigured:'Cluster re-configured successfully',reconfigure:'Re-configure',clusterHealth:'Cluster Health',clusterHealthTooltip:'Health = 100 − (CPU×30% + RAM×30% + Storage×20% + Offline Nodes×20%)\n\n80+: Excellent\n60–79: Good\n40–59: Warning\n< 40: Critical',nodeScoreTooltip:'Node score = CPU% + RAM% (lower is better)\n\n< 100: Good (green)\n100–150: Elevated (yellow)\n> 150: Critical (red)',excellent:'Excellent',good:'Good',warning:'Warning',critical:'Critical',nodesOnline:'Nodes Online',nodeJoinHint:'To add a new node, run on the new node:',avgScore:'Avg. Score',avgStorage:'Avg. Storage',avgCpu:'Avg. CPU',avgRam:'Avg. RAM',// Nodes nodes:'Nodes',node:'Node',loadingMetrics:'Loading metrics...',connectionError:'Connection Error',retry:'Retry',checkConnectionAndRetry:'Please check your connection and try again.',connectionTimeout:'Timeout - Proxmox unreachable',maintenance:'Maintenance',enterMaintenance:'Enter Maintenance Mode',exitMaintenance:'Exit Maintenance Mode',maintenanceMode:'Maintenance Mode',update:'Update',startUpdate:'Start Update',nodeConfig:'Node Configuration',cpuHistory:'CPU History',ramHistory:'RAM History',ramUsage:'RAM Usage',cpuUsage:'CPU Usage',diskUsage:'Disk Usage',allocated:'allocated',showMore:'Show more',showLess:'Show less',// VMs & Resources virtualMachines:'Virtual Machines',containers:'Containers',vm:'VM',lxc:'LXC',lxcContainer:'LXC Container',container:'Container',guests:'Guests',start:'Start',stop:'Stop',shutdown:'Shutdown',reboot:'Reboot',forceStop:'Force Stop',forceReset:'Force Reset',forceStopConfirm:'really force stop? This may cause data loss!',migrate:'Migrate',migrateVm:'Migrate VM',crossClusterMigration:'Cross-Cluster Migration',crossClusterMigrateDesc:'Migrate VM to another Proxmox cluster',crossClusterMigrate:'Cross-Cluster Migrate',crossClusterStarted:'Cross-Cluster Migration started',crossClusterFailed:'Cross-Cluster Migration failed',crossClusterLB:'Cross-Cluster Load Balancing',crossClusterLBEnabled:'Cross-Cluster LB enabled',crossClusterLBDisabled:'Cross-Cluster LB disabled',crossClusterLBThreshold:'Score Threshold',crossClusterLBInterval:'Check Interval',crossClusterLBDryRun:'Simulation (Dry Run)',crossClusterLBLastRun:'Last Run',crossClusterReplication:'Cross-Cluster Replication',crossClusterReplicationDesc:'Replicate VM snapshots to another cluster (DR)',groupOverview:'Group Overview',groupSettings:'Group Settings',groupSettingsSaved:'Group settings saved',replicationSchedule:'Schedule',replicationRetention:'Retention',maxMigrations:'Max Migrations per Cycle',lbHistory:'LB History',clusterScore:'Cluster Score',noReplicationJobs:'No cross-cluster replication jobs',createReplicationJob:'Create DR Job',replicationStarted:'Replication started',replicationJobCreated:'Replication job created',replicationJobDeleted:'Replication job deleted',recentLbActions:'Recent cross-cluster LB actions',noLbEvents:'No cross-cluster LB events yet',enableCrossClusterLB:'Enable Cross-Cluster Load Balancing',lbDescription:'Automatically migrate VMs between clusters when resource thresholds are exceeded',dryRunMode:'Dry Run / Simulation Mode',cpuThreshold:'CPU Threshold (%)',lbStatus:'Load Balancing Status',lbExplanation:'Cross-Cluster Load Balancing monitors CPU and RAM usage across all clusters in this group. When a cluster exceeds the configured threshold, VMs are automatically migrated to a less loaded cluster.',lbDryRunExplanation:'Enable Dry Run mode first to review what actions would be taken before enabling live migrations.',neverRun:'Never run',newCrossClusterReplication:'New Cross-Cluster Replication',confirmDeleteXRepl:'Delete this cross-cluster replication job?',addDrJob:'Add DR Job',scheduleCron:'Schedule (cron)',targetStorageHint:'Storage name on destination cluster',maxMigrationsHint:'Limit how many VMs are moved each check cycle (1-5)',proxlbCredit:'ProxLB by gyptazy',proxlbCreditDesc:'Our load balancing functionality is based on the excellent work from ProxLB. Special thanks to gyptazy for creating and open-sourcing this amazing tool!',xReplCreated:'Cross-cluster replication created',xReplDeleted:'Cross-cluster replication deleted',xReplStarted:'Cross-cluster replication started',xReplCreateFailed:'Failed to create job',xReplDeleteFailed:'Delete failed',xReplStartFailed:'Failed to start',lastRunPrefix:'Last',runNow:'Run now',loadingCrossClusterResources:'Loading storage/network for all clusters...',noCommonStorages:'No common storage found across all clusters',noCommonBridges:'No common bridge found across all clusters',selectBridge:'Select bridge...',crossClusterThresholdDesc:'CPU threshold for cluster imbalance (10-80%)',crossClusterIntervalDesc:'Time between check cycles',crossClusterMaxMigrationsDesc:'Max migrations per check cycle',commonStorageHint:'Only storages available on all clusters in this group',commonBridgeHint:'Only bridges available on all clusters in this group',includeContainers:'Include Containers',includeContainersDesc:'Include containers (LXC) in cross-cluster balancing',containerMigrationWarning:'Containers are restarted during migration (downtime)',excludedVMsCrossCluster:'Excluded VMs/Containers',excludedVMsCrossClusterDesc:'VMs and containers excluded from automatic cross-cluster balancing',clusterExcludedVMs:'Excluded VMs',noExcludedVMsInGroup:'No VMs excluded',selectCluster:'Select cluster...',selectClusterFirst:'Select a target cluster first',simulationMode:'Simulation Mode',sourceVm:'Source VM',targetCluster:'Target Cluster',targetNode:'Target Node',targetStorage:'Target Storage',targetBridge:'Target Network (Bridge)',moveDisk:'Move Disk',resizeDisk:'Resize Disk',diskResized:'Disk resized',deleteSourceDisk:'Delete source after move',deleteSourceDiskWarning:'The original disk will remain on the source storage. You can remove it manually later.',move:'Move',from:'from',noNetworkInterfaces:'No network interfaces',nameRequired:'Name required',newVmid:'New VMID (optional)',sameIdPlaceholder:'Empty = same ID',liveMigrationOption:'Live Migration (VM keeps running)',deleteSourceAfter:'Delete source VM after migration',largeDiskWarning:'Large Disk Detected',largeDiskExplanation:'Live migration for disks >100GB may fail with "401 Unauthorized" due to Proxmox WebSocket ticket timeout. The server will automatically use offline migration unless forced.',forceOnlineMigration:'Force online migration anyway (may fail)',autoTokenInfo:'PegaProx automatically creates temporary API tokens for migration and deletes them after completion.',clusterReachableInfo:'Clusters must be able to reach each other over the network. The configured user must have permissions to create API tokens.',selectCluster:'Select cluster...',selectNode:'Select Node',selectStorage:'Select storage...',loadingNodes:'Loading target nodes...',loadingStorageNetwork:'Loading storage/network...',liveMigration:'Live Migration (no downtime)',on:'on',targetStorage:'Target Storage',sameAsSource:'Same as source',loadingStorages:'Loading storages',free:'free',withLocalDisks:'With Local Disks',withLocalDisksDesc:'Migrate local disks to target storage',localDisksDetected:'This VM has local disks. Migration requires copying disk data.',requiredForThisVm:'Required for this VM',cdDvdMounted:'CD/DVD Drive Mounted',cdDvdMigrationWarning:'Live migration is not possible with a CD/DVD mounted. Please eject the CD/DVD first or use offline migration.',isoMounted:'ISO/CD-ROM Mounted',isoEjected:'CD-ROM Ejected',isoMigrationWarning:'Migration may fail if the ISO is not available on the target node. Eject the CD/DVD or ensure the ISO exists on shared storage.',localStorage:'Local',bootOrderIssue:'Boot Order Issue',bootOrderWarning:'Boot order references non-existent disks',bootOrder:'Boot Order',dragToReorder:'Click to toggle, use arrows to reorder',noBootDevices:'No boot devices found',resizeDiskHint:'Increase by (e.g. +10G) or new size',// SMBIOS Settings smbiosSettings:'SMBIOS Settings',smbiosHint:'System Management BIOS - useful for Windows licensing and VM identification',applySmbiosFromClusterConfig:'Apply SMBIOS settings from cluster configuration',requiresRestart:'RESTART',readOnly:'read-only',autoGenerated:'auto-generated',smbiosFormatHint:'Only letters and numbers allowed (A-Za-z0-9)',preview:'Preview',currentValue:'Current Value',managedByProxmox:'managed by Proxmox',willBeAutoGenerated:'Will be auto-generated on save',forceConntrack:'Force (Conntrack State)',forceConntrackDesc:'Force migration even if conntrack entries exist',containerNoLiveMigration:'Containers do not support true live migration',startingMigration:'Starting migration of',migrationStarted:'Migration started:',migrationFailed:'Migration failed',clone:'Clone',console:'Console',openConsole:'Open Console',metrics:'Metrics',config:'Configuration',configuration:'Configuration',createVm:'Create new VM',createContainer:'Create new Container',newVm:'New VM',newContainer:'New Container',power:'Power',snapshot:'Snapshot',editSettings:'Settings',refreshData:'Refresh',sshConsole:'SSH Console',noNodesAvailable:'No nodes available. Please wait for cluster data to load.',loadingStorage:'Loading storage list...',noIsoAvailable:'No ISO images found',noTemplateAvailable:'No templates found',noStorageAvailable:'No storage available',noIsoStorage:'No storage with ISO content found',ram:'RAM',cpu:'CPU',disk:'Disk',network:'Network',// VM Creation Wizard @@ -2225,7 +2225,7 @@ poolPermissions:'Permissions de Pool',poolPermissionsDesc:'Accordez aux utilisateurs ou groupes l\'accès aux pools Proxmox. Les permissions s\'appliquent à tous les VMs du groupe.',managePools:'Gérer les groupes',poolManagerDesc:'Créer, modifier et supprimer des pools. Attribuer les VMs au sein des groupes pour une gestion des autorisations organisée.',createPool:'Créer un Pool',editPool:'Modifier le Pool',deletePool:'Supprimer le pool',poolId:'ID du pool',poolIdRequired:'L\'ID du pool est requis.',poolIdHint:'Lettres, chiffres, tirets et soulignements seulement',poolIdCannotChange:'L\'ID de la piscine ne peut pas être modifié.',poolCreated:'Pool créé avec succès',poolUpdated:'Le pool a été mis à jour avec succès.',poolDeleted:'Pool supprimé avec succès',confirmDeletePool:'Êtes-vous sûr de vouloir supprimer ce pool ? Cela ne peut pas être annulé.',noPoolsYet:'Aucun pool pour l\'instant',createFirstPool:'Créez votre premier pool pour organiser les VMs',poolMembers:'Membres',members:'membres',addVmToPool:'Ajouter une VM au pool',assignToPool:'Affecter au pool',removeFromPool:'Retirer du pool',vmAddedToPool:'Une VM a été ajoutée au pool.',vmRemovedFromPool:'Une VM a été retirée du pool',confirmRemoveVmFromPool:'Retirer la VM de ce pool ?',selectVmToAdd:'Sélectionnez une VM à ajouter à ce pool',allVmsInPools:'Toutes les VMs sont déjà dans des pools',optionalDescription:'Description facultative...',userPermissions:'Permissions Utilisateur',selectPool:'Sélectionnez le Pool',noPools:'Aucun groupe de ressources trouvé dans ce cluster',noPoolPerms:'Aucune permission configurée pour ce pool',selectPoolFirst:'Sélectionnez un cluster et un pool pour gérer les autorisations',addPoolPerm:'Ajouter une permission de pool',editPoolPerm:'Modifier les permissions du pool',poolPermSaved:'Permission de groupe enregistrée',poolPermDeleted:'Permission de pool supprimée',refreshPools:'Rafraîchir les pools depuis Proxmox',poolCacheRefreshed:'Le cache du pool a été mis à jour',confirmDeletePoolPerm:'Supprimer cette permission ?',subjectType:'Type',permissionsFor:'Permissions pour',addPermission:'Ajouter une permission',groupName:'Nom du groupe',auditLog:'Journal d\'audit',auditLogDescription:'Toutes les actions des utilisateurs au cours des 90 derniers jours',noAuditLogs:'Aucune entrée d\'audit disponible',action:'Action',details:'Détails',timestamp:'Horodatage',ipAddress:'Adresse IP',userCreated:'Utilisateur créé',userUpdated:'Utilisateur mis à jour',userDeleted:'Utilisateur supprimé',userLogin:'Se connecter',userLogout:'Déconnexion',passwordChanged:'Mot de passe modifié',clusterAdded:'Cluster ajouté',apiTokenWarningTitle:'Authentification par jeton API',apiTokenWarningDesc:'Sans mot de passe, les fonctionnalités SSH ne fonctionneront pas (HA, Mises à jour enroulées, SMBIOS, Shell du nœud). Recommandé : Utilisez root@pam + mot de passe — PegaProx crée automatiquement un jeton API pour la compatibilité avec le 2FA.',apiTokenRecommended:'Recommandé lorsque l\'authentification à deux facteurs est activée',apiTokenCreated:'Un jeton API créé sur Proxmox VE — la 2FA peut maintenant être activée en toute sécurité.',sshPasswordStillNeeded:'SSH utilise toujours le mot de passe (HA, maintenance) - ne pas le changer',authModeToken:'API : Jeton (sécurisé 2FA)',authModePassword:'API : Mot de passe',sshAuthMode:'SSH : Mot de passe',dontChangePvePassword:'Ne pas changer le mot de passe PVE sans le changer ici aussi',clusterDeleted:'Cluster supprimé',clusterConfigChanged:'La configuration du cluster a changé.',vmStarted:'La VM a démarré',vmStopped:'VM arrêtée',vmRestarted:'La machine virtuelle a été redémarrée.',vmCreated:'Une VM a été créée.',vmDeleted:'VM supprimée',vmCloned:'VM clonée',vmMigrated:'VM migrée',vmUnlocked:'VM déverrouillée',vmLocked:'La machine virtuelle est verrouillée.',unlockVm:'Déverrouiller la VM',lockReason:'Raison du verrouillage',unlockWarning:'Attention : Déverrouiller une VM pendant une opération active peut entraîner des dommages aux données ou d\'autres problèmes. Ne procédez que si vous êtes sûr que l\'opération a échoué ou été annulée.',vmBulkMigrated:'Migration en masse',vmConfigChanged:'La configuration de la VM a changé.',vmSuspended:'VM suspendue',vmResumed:'La VM a été reprise.',vmDiskAdded:'Disque ajouté',vmDiskRemoved:'Disque supprimé',vmDiskResized:'Disque redimensionné',vmDiskMoved:'Disque déplacé',vmNetworkAdded:'Réseau ajouté',vmNetworkRemoved:'Réseau supprimé',vmNetworkUpdated:'Réseau mis à jour',removeDiskConfirm:'Voulez-vous vraiment supprimer le disque ?',detachDisk:'Détacher',importDisk:'Importer un disque',importDiskDesc:'Importer une image de disque existante depuis le stockage',selectImportStorage:'Sélectionner le stockage source',selectDiskImage:'Sélectionnez l\'image du disque',targetBus:'Bus cible',reassignOwner:'Transférer le propriétaire',reassignOwnerDesc:'Attribuez un disque à une autre machine virtuelle',targetVm:'VM cible',diskReassigned:'Disque réaffecté',diskImported:'Disque importé',noImportableDisks:'Aucune image de disque importable trouvée',reassign:'Réaffecter',import:'Importer',detachDiskConfirm:'Voulez-vous vraiment détacher le disque ? Il deviendra un disque inutilisé.',diskDetached:'Disque détaché',diskDeleted:'Disque supprimé',diskAdded:'Disque ajouté',dataWillBeDeleted:'Les données seront définitivement supprimées!',removeNetworkConfirm:'Voulez-vous vraiment supprimer le réseau ?',snapshotCreated:'Point de restauration créé',snapshotDeleted:'Point de restauration supprimé',snapshotRestored:'Le point de restauration a été restauré.',rollbackConfirm:'Voulez-vous vraiment revenir à ce point de restauration ? Cela ne peut pas être annulé !',replicationCreated:'Réplication créée',replicationDeleted:'La réplication a été supprimée.',replicationTriggered:'Réplication déclenchée',haEnabled:'HA Activé',haDisabled:'HA désactivé',noFallbackHosts:'Aucun hôte de secours trouvé (Cluster à un nœud ?)',haVmAdded:'Une VM a été ajoutée au HA',haVmRemoved:'VM retirée de l\'HA',nodeMaintenanceEntered:'Mode de maintenance entré',nodeMaintenanceExited:'Mode de maintenance sorti',nodeUpdateStarted:'Mise à jour du nœud démarrée',twoFactorAuth:'Authentification à deux facteurs',twoFactorEnabled:'2FA activé',twoFactorDisabled:'2FA désactivé',enable2FA:'Activer la 2FA',disable2FA:'Désactiver 2FA',setup2FA:'Configurer 2FA',scan2FACode:'Scanner le code QR avec l\'application d\'authentification',enter2FACode:'Entrez un code à 6 chiffres',verify2FA:'Vérifier',secretKey:'Clé secrète',twoFARequired:'Code 2FA requis',invalid2FACode:'Code 2FA invalide',force2FA:'Imposez la 2FA',force2FADesc:'Tous les utilisateurs doivent configurer l\'authentification à deux facteurs avant de pouvoir utiliser PegaProx.',force2FAHint:'Les utilisateurs OIDC/Entra sont exemptés (ils utilisent la MFA de leur fournisseur d\'identité). Les utilisateurs sans 2FA verront un assistant de configuration à l\'inscription.',force2FAExcludeAdmins:'Exclure les comptes administrateurs',force2FAExcludeAdminsDesc:'Les administrateurs peuvent utiliser PegaProx sans 2FA',force2FASetupTitle:'Configuration de la 2FA requise',force2FASetupDesc:'Votre administrateur a rendu l\'authentification à deux facteurs obligatoire pour tous les utilisateurs. Veuillez configurer 2FA pour continuer.',resetPassword:'Réinitialiser le mot de passe',passwordManagedExternally:'Votre mot de passe est géré en externe.',passwordManagedExternallyHint:'Veuillez modifier votre mot de passe directement dans votre service d\'annuaire.',newPassword:'Nouveau mot de passe',confirmPassword:'Confirmer le mot de passe',currentPassword:'Mot de passe actuel',passwordsDoNotMatch:'Les mots de passe ne correspondent pas.',passwordTooShort:'Le mot de passe doit comporter au moins 4 caractères.',passwordResetSuccess:'Mot de passe modifié avec succès',myProfile:'Mon profil',security:'sécurité',snapshotNotSupported:'Les point de restauration ne sont pas pris en charge.',snapshotWarnings:'Avertissements sur le point de restauration',filterByUser:'Filtrer par utilisateur',filterByAction:'Filtrer par action',allUsers:'Tous les utilisateurs',allActions:'Toutes les actions',exportAuditLog:'Exporter',refreshAuditLog:'Actualiser',// Header addCluster:'Ajouter un Cluster',addXcpngPool:'Ajouter un Pool XCP-ng (Préversion Technique)',xcpngConnectHint:'Connectez-vous au serveur maître du pool. Le port XAPI 443 est utilisé par défaut.',xcpngTechPreviewNote:'Certaines fonctionnalités peuvent être limitées ou sujettes à modification.',pveClusterDesc:'Machines virtuelles et conteneurs',pbsDesc:'Gestion des sauvegardes',vmwareDesc:'Infrastructure ESXi',xcpngDesc:'XCP-ng / Hyperviseur Xen (Prévisualisation technique)',addConnection:'Ajouter une Connexion',connectionType:'Type de connexion',clusterManagement:'Gestion de cluster',// Tabs overview:'Vue d\\',resources:'Ressources',datacenter:'Centre de données',settings:'Paramètres',of:'de',showing:'Affichant',perPage:'Par page',loadingDatacenter:'Chargement...',// Cluster -clusters:'Clusters',noClusterSelected:'Aucun cluster sélectionné',allClustersOverview:'Vue d\'ensemble de tous les clusters',allClusters:'Tous les clusters',multiClusterSummary:'Résumé de tous les clusters gérés',clustersConnected:'Clusters Connectés',clusterOverview:'Vue d\'ensemble du cluster',vmsRunning:'VMs en cours d\'exécution',vmsStopped:'Les VMs ont été arrêtées.',noClustersConfigured:'Aucun cluster configuré',addClusterToStart:'Ajoutez un cluster pour commencer',clickClusterTip:'Cliquez sur une ligne de cluster ou sélectionnez dans la barre latérale pour afficher des informations détaillées et gérer les ressources.',health:'Santé',average:'Moyenne',noDataAvailable:'Aucune donnée disponible',clickToManage:'Cliquer pour gérer',sortBy:'Trier par',ungrouped:'Non regroupé',alerts:'Alertes',updated:'Mis à jour',justNow:'Il y a quelques instants',topResources:'Ressources principales',highestCpuUsage:'Utilisation maximale de CPU et de RAM sur tous les clusters',clickToOpenVm:'Cliquez pour ouvrir la VM',selectCluster:'Sélectionnez un cluster de stockage',addFirstCluster:'Ajouter le Premier Cluster',connectCluster:'Connectez un cluster Proxmox avec PegaProx',clusterName:'Nom du cluster',host:'Serveur',username:'Nom d\\',password:'Mot de passe',passwordOrToken:'Mot de passe / Jeton',apiTokenHint:'Pour les jetons API : utilisateur@domaine!identifiant_token',sslVerification:'Vérification SSL',connecting:'Connexion...',addNewCluster:'Ajouter un Nouveau Cluster',testConnection:'Tester la Connexion',deleteCluster:'Supprimer le cluster',deleteClusterConfirm:'Supprimer vraiment le cluster ?',reconfigureCluster:'Reconfigurer le cluster',reconfigureHint:'Entrez votre mot de passe PegaProx pour vérifier votre identité.',clusterReconfigured:'Cluster reconfiguré avec succès',reconfigure:'Reconfigurer',clusterHealth:'Santé du Cluster',clusterHealthTooltip:'Santé = 100 − (CPU×30% + RAM×30% + Stockage×20% + Nœuds hors ligne×20%)\nSans données stockage : CPU×37,5% + RAM×37,5% + Hors ligne×25%\n\n80+ : Excellente\n60–79 : Bien\n40–59 : Avertissement\n< 40 : Critique',nodeScoreTooltip:'Score nœud = CPU% + RAM% (plus bas est mieux)\n\n< 100 : Bon (vert)\n100–150 : Élevé (jaune)\n> 150 : Critique (rouge)',excellent:'Excellente',good:'Bien',warning:'Avertissement',critical:'Critique',nodesOnline:'Nœuds en ligne',nodeJoinHint:'Pour ajouter un nouveau nœud, exécutez sur le nouveau nœud :',avgScore:'Moyenne des notes',avgStorage:'Moy. Stockage',avgCpu:'Moyenne CPU',avgRam:'Moyenne de la RAM',// Nodes +clusters:'Clusters',noClusterSelected:'Aucun cluster sélectionné',allClustersOverview:'Vue d\'ensemble de tous les clusters',allClusters:'Tous les clusters',multiClusterSummary:'Résumé de tous les clusters gérés',clustersConnected:'Clusters Connectés',clusterOverview:'Vue d\'ensemble du cluster',vmsRunning:'VMs en cours d\'exécution',vmsStopped:'Les VMs ont été arrêtées.',noClustersConfigured:'Aucun cluster configuré',addClusterToStart:'Ajoutez un cluster pour commencer',clickClusterTip:'Cliquez sur une ligne de cluster ou sélectionnez dans la barre latérale pour afficher des informations détaillées et gérer les ressources.',health:'Santé',average:'Moyenne',noDataAvailable:'Aucune donnée disponible',clickToManage:'Cliquer pour gérer',sortBy:'Trier par',ungrouped:'Non regroupé',alerts:'Alertes',updated:'Mis à jour',justNow:'Il y a quelques instants',topResources:'Ressources principales',highestCpuUsage:'Utilisation maximale de CPU et de RAM sur tous les clusters',clickToOpenVm:'Cliquez pour ouvrir la VM',selectCluster:'Sélectionnez un cluster de stockage',addFirstCluster:'Ajouter le Premier Cluster',connectCluster:'Connectez un cluster Proxmox avec PegaProx',clusterName:'Nom du cluster',host:'Serveur',username:'Nom d\\',password:'Mot de passe',passwordOrToken:'Mot de passe / Jeton',apiTokenHint:'Pour les jetons API : utilisateur@domaine!identifiant_token',sslVerification:'Vérification SSL',connecting:'Connexion...',addNewCluster:'Ajouter un Nouveau Cluster',testConnection:'Tester la Connexion',deleteCluster:'Supprimer le cluster',deleteClusterConfirm:'Supprimer vraiment le cluster ?',reconfigureCluster:'Reconfigurer le cluster',reconfigureHint:'Entrez votre mot de passe PegaProx pour vérifier votre identité.',clusterReconfigured:'Cluster reconfiguré avec succès',reconfigure:'Reconfigurer',clusterHealth:'Santé du Cluster',clusterHealthTooltip:'Santé = 100 − (CPU×30% + RAM×30% + Stockage×20% + Nœuds hors ligne×20%)\n\n80+ : Excellente\n60–79 : Bien\n40–59 : Avertissement\n< 40 : Critique',nodeScoreTooltip:'Score nœud = CPU% + RAM% (plus bas est mieux)\n\n< 100 : Bon (vert)\n100–150 : Élevé (jaune)\n> 150 : Critique (rouge)',excellent:'Excellente',good:'Bien',warning:'Avertissement',critical:'Critique',nodesOnline:'Nœuds en ligne',nodeJoinHint:'Pour ajouter un nouveau nœud, exécutez sur le nouveau nœud :',avgScore:'Moyenne des notes',avgStorage:'Moy. Stockage',avgCpu:'Moyenne CPU',avgRam:'Moyenne de la RAM',// Nodes nodes:'Nœuds',node:'Nœud',loadingMetrics:'Chargement...',connectionError:'Erreur de connexion',retry:'Réessayer',checkConnectionAndRetry:'Veuillez check your connection and try again.',connectionTimeout:'Délai d\'expiration - Proxmox inaccessible',maintenance:'Maintenance',enterMaintenance:'Entrez en mode maintenance',exitMaintenance:'Sortir du mode maintenance',maintenanceMode:'Mode de maintenance',update:'Mettre à jour',startUpdate:'Commencer la mise à jour',nodeConfig:'Configuration du nœud',cpuHistory:'Historique du processeur',ramHistory:'Historique de la RAM',ramUsage:'Utilisation RAM',cpuUsage:'Utilisation CPU',diskUsage:'Utilisation du disque',showMore:'Montrer plus',showLess:'Montrer moins',// VMs & Resources virtualMachines:'Machines virtuelles',containers:'Conteneurs',vm:'VM',lxc:'LXC',lxcContainer:'Conteneur LXC',container:'Conteneur',guests:'Clients',start:'Démarrer',stop:'Arrêtez',shutdown:'Arrêter',reboot:'Redémarrer',forceStop:'Forcer l\'arrêt',forceReset:'Forcer le redémarrage',forceStopConfirm:'Forcer l\'arrêt ? Cela peut causer une perte de données !',migrate:'Migrer',migrateVm:'Migrer la VM',crossClusterMigration:'Migration inter-cluster',crossClusterMigrateDesc:'Migrer une VM vers un autre cluster Proxmox',crossClusterMigrate:'Migration entre clusters',crossClusterStarted:'La migration entre clusters a commencé.',crossClusterFailed:'La migration entre clusters a échoué.',crossClusterLB:'Équilibrage de charge inter-cluster',crossClusterLBEnabled:'Balanceur de charge entre clusters activé',crossClusterLBDisabled:'Désactivation de l\'équilibreur de charge entre clusters',crossClusterLBThreshold:'Seuil de notation',crossClusterLBInterval:'Intervalle de vérification',crossClusterLBDryRun:'Simulation (Dry Run)',crossClusterLBLastRun:'Dernière Exécution',crossClusterReplication:'Réplication Inter-Clustère',crossClusterReplicationDesc:'Répliquer les point de restauration de VM vers un autre cluster (DR)',groupOverview:'Vue d\'ensemble du groupe',groupSettings:'Paramètres du groupe',groupSettingsSaved:'Paramètres du groupe enregistrés',replicationSchedule:'Planification',replicationRetention:'Retenue',maxMigrations:'Max Migrations par Cycle',lbHistory:'Historique du Load Balancer',clusterScore:'Score du Cluster',noReplicationJobs:'Aucun Job de Réplication',createReplicationJob:'Créer une tâche de réplication',replicationStarted:'La réplication a commencé.',replicationJobCreated:'Tâche de réplication créée',replicationJobDeleted:'Tâche de réplication supprimée',recentLbActions:'Actions récentes de load balancing entre clusters',noLbEvents:'Aucun événement de load balancing inter-cluster',enableCrossClusterLB:'Activer le équilibrage de charge inter-clusters',lbDescription:'Migrer automatiquement les VMs entre les clusters lorsque les seuils de ressources sont dépassés',dryRunMode:'Mode de simulation / Exécution sèche',cpuThreshold:'Seuil de processeur (%)',lbStatus:'État du Balanceur de charge',lbExplanation:'Le bilan de charge inter-cluster surveille l\'utilisation du CPU et de la RAM sur tous les clusters de ce groupe. Lorsque un cluster dépasse le seuil configuré, les VMs sont automatiquement migrées vers un cluster moins chargé.',lbDryRunExplanation:'Activer d\'abord le mode de simulation pour examiner les actions qui seraient prises avant d\'activer les migrations en direct.',neverRun:'N\'exécutez jamais',newCrossClusterReplication:'Nouvelle Réplication Inter-Clustère',confirmDeleteXRepl:'Supprimer ce job de réplication inter-cluster ?',addDrJob:'Ajouter une tâche de réplication',scheduleCron:'Planification (cron)',targetStorageHint:'Nom du stockage sur le cluster de destination',maxMigrationsHint:'Limitez le nombre de VMs déplacées chaque cycle de vérification (1-5)',proxlbCredit:'ProxLB par gyptazy',proxlbCreditDesc:'Notre fonctionnalité de balanceur de charge est basée sur le travail exceptionnel de ProxLB. Nous tenons à remercier gyptazy pour avoir créé et open-sourced cet outil incroyable !',xReplCreated:'Réplication entre clusters créée',xReplDeleted:'La réplication entre clusters a été supprimée.',xReplStarted:'La réplication entre clusters a commencé.',xReplCreateFailed:'Échec de la création du job',xReplDeleteFailed:'Suppression échouée',xReplStartFailed:'Échec du démarrage',lastRunPrefix:'Dernier',runNow:'Exécuter maintenant',loadingCrossClusterResources:'Chargement du stockage/réseau pour tous les clusters…',noCommonStorages:'Aucun stockage commun trouvé dans tous les clusters',noCommonBridges:'Aucun pont commun trouvé dans tous les clusters',selectBridge:'Sélectionnez le pont...',crossClusterThresholdDesc:'Seuil de CPU pour l\'équilibre du cluster (10-80%)',crossClusterIntervalDesc:'Intervalle entre les cycles de vérification',crossClusterMaxMigrationsDesc:'Nombre maximal de migrations par cycle de vérification',commonStorageHint:'Seulement les stockages disponibles sur tous les clusters de ce groupe',commonBridgeHint:'Seulement les ponts disponibles sur tous les clusters de ce groupe',includeContainers:'Inclure les Conteneurs',includeContainersDesc:'Inclure les conteneurs (LXC) dans l\'équilibrage inter-cluster',containerMigrationWarning:'Les conteneurs sont redémarrés pendant la migration (temps d\'arrêt)',excludedVMsCrossCluster:'VMs/Conteneurs exclus',excludedVMsCrossClusterDesc:'Les VMs et les conteneurs exclus de l\'équilibrage automatique entre les clusters',clusterExcludedVMs:'VMs exclues',noExcludedVMsInGroup:'Aucune VM exclue',selectCluster:'Sélectionnez un cluster de stockage',selectClusterFirst:'Veuillez sélectionner d\'abord un cluster cible.',simulationMode:'Mode de simulation',sourceVm:'Source VM',targetCluster:'Cluster cible',targetNode:'Nœud cible',targetStorage:'Stockage cible',targetBridge:'Réseau cible (Bridge)',moveDisk:'Déplacer le disque dur',resizeDisk:'Redimensionner le disque dur',diskResized:'Disque redimensionné',deleteSourceDisk:'Supprimer la source après le déplacement',deleteSourceDiskWarning:'Le disque original restera sur le stockage source. Vous pouvez le supprimer manuellement plus tard.',move:'Déplacer',from:'de',noNetworkInterfaces:'Aucune interface réseau',nameRequired:'Nom requis',newVmid:'Nouvel ID de machine virtuelle (facultatif)',sameIdPlaceholder:'Vide = même ID',liveMigrationOption:'Migration en direct (la VM continue d\'exécuter)',deleteSourceAfter:'Supprimer la VM source après la migration',largeDiskWarning:'Disque de grande taille détecté',largeDiskExplanation:'La migration en direct des disques >100GB peut échouer avec "401 Unauthorized" en raison du timeout du ticket WebSocket de Proxmox. Le serveur utilisera automatiquement la migration hors ligne sauf s\'il est forcé.',forceOnlineMigration:'Forcer la migration en ligne même si elle échoue',autoTokenInfo:'PegaProx crée automatiquement des jetons API temporaires pour la migration et les supprime après la complétion.',clusterReachableInfo:'Les clusters doivent pouvoir se connecter entre eux au réseau. L\'utilisateur configuré doit avoir les autorisations pour créer des jetons API.',selectCluster:'Sélectionnez un cluster de stockage',selectNode:'-- Sélectionnez un nœud --',selectStorage:'Sélectionnez un stockage pour afficher le contenu',loadingNodes:'Chargement de la node (noeud) cible…',loadingStorageNetwork:'Chargement du stockage/réseau…',liveMigration:'Migration en direct (pas d\'arrêt)',on:'sur',targetStorage:'Stockage cible',sameAsSource:'Même que la source',loadingStorages:'Chargement des stockages',free:'libre',withLocalDisks:'Avec les disques locaux',withLocalDisksDesc:'Migrer les disques locaux vers le stockage cible',localDisksDetected:'Cette VM a des disques locaux. La migration nécessite la copie des données de disque.',requiredForThisVm:'Nécessaire pour cette VM',cdDvdMounted:'Disque CD/DVD Monté',cdDvdMigrationWarning:'La migration en direct n\'est pas possible avec un CD/DVD monté. Veuillez éjecter le CD/DVD d\'abord ou utilisez la migration hors ligne.',isoMounted:'ISO/CD-ROM Monté',isoEjected:'CD-ROM éjecté',isoMigrationWarning:'La migration peut échouer si l\'ISO n\'est pas disponible sur le nœud cible. Éjectez le CD/DVD ou assurez-vous que l\'ISO existe sur un stockage partagé.',localStorage:'Local',bootOrderIssue:'Problème d\'ordre de démarrage',bootOrderWarning:'L\'ordre de démarrage fait référence à des disques qui n\'existent pas.',bootOrder:'Ordre de démarrage',dragToReorder:'Cliquez pour basculer, utilisez les flèches pour réorganiser',noBootDevices:'Aucun dispositif de démarrage trouvé',resizeDiskHint:'Augmenter de (par exemple +10G) ou nouvelle taille',// SMBIOS Settings smbiosSettings:'Paramètres SMBIOS',smbiosHint:'Gestion du système BIOS - utile pour la licence Windows et l\'identification des VMs',applySmbiosFromClusterConfig:'Appliquer les paramètres SMBIOS de la configuration du cluster',requiresRestart:'REDEMARRER',readOnly:'lecture seule',autoGenerated:'auto-généré',smbiosFormatHint:'Seulement les lettres et chiffres sont autorisés (A-Za-z0-9)',preview:'Aperçu',currentValue:'Valeur Actuelle',managedByProxmox:'géré par Proxmox',willBeAutoGenerated:'Serait généré automatiquement à la sauvegarde',forceConntrack:'Forcer l\'état de Conntrack',forceConntrackDesc:'Forcer la migration même si des entrées conntrack existent',containerNoLiveMigration:'Les conteneurs ne prennent pas en charge une migration de vie réelle véritable.',startingMigration:'Démarrage de la migration de',migrationStarted:'La migration a commencé!',migrationFailed:'La migration a échoué.',clone:'Cloner',console:'Console',openConsole:'Ouvrir la console',metrics:'Métriques',config:'Configuration',configuration:'Configuration',createVm:'Créer une nouvelle VM',createContainer:'Créer un nouveau conteneur',newVm:'Nouvelle VM',newContainer:'Nouveau conteneur',power:'Alimentation',snapshot:'Instantané',editSettings:'Paramètres',refreshData:'Actualiser',sshConsole:'Console SSH',noNodesAvailable:'Aucun nœud disponible. Veuillez patienter tandis que les données du cluster se chargent.',loadingStorage:'Chargement de la liste de stockage…',noIsoAvailable:'Aucune image ISO trouvée',noTemplateAvailable:'Aucun modèle trouvé',noStorageAvailable:'Aucun stockage disponible',noIsoStorage:'Aucun stockage avec du contenu ISO trouvé',ram:'Mémoire vive',cpu:'CPU',disk:'Disque',network:'Réseau',// VM Creation Wizard @@ -2312,7 +2312,7 @@ poolPermissions:'Permisos de pool',poolPermissionsDesc:'Asigne acceso a usuario o grupos a pools de recursos de Proxmos. Los permisos aplican para todas la VMs en el pool.',managePools:'Gestionar pools',poolManagerDesc:'Crear, editar y remover pools de recursos. Asigne VMs a pools para la gestión organizada de los permisos.',createPool:'Crear pool',editPool:'Editar pool',deletePool:'Remover pool',poolId:'ID de pool',poolIdRequired:'El ID de pool es requerimiento',poolIdHint:'Solo letras, números, guiones y guiones bajos',poolIdCannotChange:'No es posible cambiar el ID de pool',poolCreated:'Pool creado con éxito',poolUpdated:'Pool acualizado con éxito',poolDeleted:'Pool removido con éxito',confirmDeletePool:'¿Está seguro de querer remover este pool? ¡No podrá deshacerlo!',noPoolsYet:'Aún no hay pools definidos',createFirstPool:'Cree un primer pool para organizar las VMs',poolMembers:'Miembros',members:'miembros',addVmToPool:'Agregar VM al pool',assignToPool:"Asignar a pool",removeFromPool:'Remover del pool',vmAddedToPool:'VM agregada al pool',vmRemovedFromPool:'VM removida del pool',confirmRemoveVmFromPool:'¿Remover la VM del pool?',selectVmToAdd:'Seleccione una VM para agregarla a este pool',allVmsInPools:'Ya están en pools todas las VMs',optionalDescription:'Descripción opcional...',userPermissions:'Permisos de usuario',selectPool:'Seleccione el pool',noPools:'No se hallaron recursos de pool',noPoolPerms:'No hay permisos configurados en este pool',selectPoolFirst:'Seleccione un cluster y un pool para gestionar los permisos',addPoolPerm:'Agregar permisos de pool',editPoolPerm:'Editar permisos de pool',poolPermSaved:'Permisos de pool guardados',poolPermDeleted:'Permisos de pool removidos',refreshPools:'Refrescar información de pools de Proxmox',poolCacheRefreshed:'Caché de pools refrescado',confirmDeletePoolPerm:'¿Remover permisos?',subjectType:'Tipo',permissionsFor:'Permisos para',addPermission:'Agregar permisos',groupName:'Nombre de grupo',auditLog:'Registros de auditoría',auditLogDescription:'Todas las acciones de usuario de los últimos 90 días',noAuditLogs:'No hay registros de auditoría disponibles',action:'Acción',details:'Detalles',timestamp:'Marca de tiempo',ipAddress:'Dirección IP',userCreated:'Usuario creado',userUpdated:'Usuario actualizado',userDeleted:'Usuario removido',userLogin:'Entrar',userLogout:'Cerrar sesión',passwordChanged:'Contraseña cambiada',clusterAdded:'Cluster agregado',apiTokenWarningTitle:'Autenticación por token de API',apiTokenWarningDesc:'Sin una contraseña, las características de SSH no funcionan (HA, actualizaciones )" "Without a password, SSH features won\'t work (HA, actualizaciones graduales, SMBIOS, indicador de nodo). Recomendado: usar root@pam + contraseña — PegaProx crea un token de API automáticamente para compatibilidad con 2FA.',apiTokenRecommended:'Se recomienda cuando se activa 2FA.',apiTokenCreated:'Se creó un token de API con PVE — Se activó 2FA de manera segura',sshPasswordStillNeeded:'SSH continúa usando contraseña (HA, mantenimiento) - no la cambie',authModeToken:'API: Token (seguro para 2FA)',authModePassword:'API: Contraseña',sshAuthMode:'SSH: Contraseña',dontChangePvePassword:'No cambie la contraseña de PVE sin actualizarla aquí',clusterDeleted:'Cluster removido',clusterConfigChanged:'Configuración de cluster cambiada',vmStarted:'VM iniciada',vmStopped:'VM detenida',vmRestarted:'VM reiniciada',vmCreated:'VM creada',vmDeleted:'VM removida',vmCloned:'VM clonada',vmMigrated:'VM migrada',vmUnlocked:'VM desbloqueada',vmLocked:'VM bloqueada',unlockVm:'Desbloquear VM',lockReason:'Razón para el bloqueo',unlockWarning:'Desbloquear una VM durante una operación activa puede causar corrupción u otros efectos. Proceda solo si está seguro de que la operación ha sido cancelada o ha fallado.',vmBulkMigrated:'Migración en lote',vmConfigChanged:'Configuración de VM cambiada',vmSuspended:'VM suspendida',vmResumed:'VM recontinuada',vmDiskAdded:'Disco agregado',vmDiskRemoved:'Disco removido',vmDiskResized:'Disco cambiado de tamaño',vmDiskMoved:'Disco movido',vmNetworkAdded:'Network agregada',vmNetworkRemoved:'Network removida',vmNetworkUpdated:'Network actualizada',removeDiskConfirm:'Realmente remover el disco',detachDisk:'Desconectar',importDisk:'Importar disco',importDiskDesc:'Importar una imagen de disco preexistente desde el almacenamiento',selectImportStorage:'Seleccionar el almacenamiento fuente',selectDiskImage:'Seleccionar la imagen de disco',targetBus:'Bus destino',reassignOwner:'Reasignar dueño',reassignOwnerDesc:'Asignar el disco a una VM diferente',targetVm:'VM destino',diskReassigned:'Disco reasignado',diskImported:'Disco importado',noImportableDisks:'No se encontraron imágenes de disco importables',reassign:'Reasignar',import:'Importar',detachDiskConfirm:'¿Realmente desconectar el disco? Se hará inusable el disco.',diskDetached:'Disco desconectado',diskDeleted:'Disco removido',diskAdded:'Disco agregado',dataWillBeDeleted:'¡Los datos se removerán permanentemente!',removeNetworkConfirm:'Remover realmente la red',snapshotCreated:'Instantánea creada',snapshotDeleted:'Instantánea removida',snapshotRestored:'Instantánea restaurada',snapshotDesc:"Enfoque de clonar + migrar. Funciona con cualquier almacenamiento (LVM, dir, etc).",snapshotName:"Nombre de la instantánea",snapshotReplNote:"Crea un clon completo de la VM y lo migra al nodo destino. La réplica anterior se reemplaza en cada ejecución.",rollbackConfirm:'¿Reversar realmente a esta instantánea? ¡Esto no podría deshacerse!',rollbackStarted:"Reversión (Rollback) iniciada",rpo:"RPO",rpoDesc:"Objetivo de punto de recuperación (RPO) - tiempo desde la última replicación",replicationCreated:'Replicación creada',replicationDeleted:'Replicación removida',replicationTriggered:'Replicación disparada',replicationHint:"Cree una tarea de replicación para mantener los datos de las VMs sincronizados entre los nodos",replicationInfoDesc:"Mantenga los datos de las VMs sincronizados entre los nodos para failover y recuperación de desastres. Dos modos disponibles:",replicationInfoTitle:"Replicación de VMs",haEnabled:'HA activada',haDisabled:'HA desactivada',noFallbackHosts:'No se encontraron máquinas para retornar (¿Cluster de un único nodo?)',haVmAdded:'VM agregada a HA',haVmRemoved:'VM removida de HA',nodeMaintenanceEntered:'Se entró a modo mantenimiento',nodeMaintenanceExited:'Se ha salido de modo mantenimiento',nodeUpdateStarted:'Inició la actualización de nodo',twoFactorAuth:'Autenticación de dos factores (2FA)',twoFactorEnabled:'2FA activada',twoFactorDisabled:'2FA desactivada',enable2FA:'Activar 2FA',disable2FA:'Desactivar 2FA',setup2FA:'Configurar 2FA',scan2FACode:'Escanee el código QR con una app de autenticación',enter2FACode:'Entre el código de 6 dígitos',verify2FA:'Verificar',secretKey:'Llave secreta',twoFARequired:'Se requiere código 2FA',invalid2FACode:'Código 2FA inválido',force2FA:'Forzar 2FA',force2FADesc:'Requerir que todos los usuarios tengan 2FA para poder usar PegaProx.',force2FAHint:'Usuarios OIDC/Entra son exentos (usan la MFA de su proveedor de identidad). Los usuarios sin 2FA verán un díalogo de configuración al entrar.',force2FAExcludeAdmins:'Excluir las cuentas de administrador',force2FAExcludeAdminsDesc:'Los administradores pueden usar PegaProx sin 2FA',force2FASetupTitle:'Se requiere configuración de 2FA',force2FASetupDesc:'Su administrador ha activado autenticación con dos factores de manera obligatoria para todos los usuarios. Favor configure 2FA para continuar',resetPassword:'Resetear contraseña',passwordManagedExternally:'Su contraseña es administrada externamente.',passwordManagedExternallyHint:'Cambie su contraseña directamente en su servicio de directorio.',newPassword:'Nueva contraseña',confirmPassword:'Confirmar contraseña',currentPassword:'Contraseña actual',passwordsDoNotMatch:'Las contraseñas no concuerdan',passwordTooShort:'Las contraseñas deben ser de al menos 4 caracteres',passwordResetSuccess:'Contraseña cambiada con éxito',myProfile:'Mi perfil',security:'Seguridad',snapshotNotSupported:'Las instantáneas no están soportadas',snapshotWarnings:'Advertencias por instantáneas',filterByUser:'Filtrado por usuario',filterByAction:'Filtrado por acción',allUsers:'Todos los usuarios',allActions:'Todas las acciones',exportAuditLog:'Exportar',refreshAuditLog:'Refrescar',// Header / Encabezado addCluster:'Agregar cluster',pveClusterDesc:'Máquinas virtuales y contenedores',pbsDesc:'Gestión de los respaldos',vmwareDesc:'Infraestructura ESXi',addConnection:'Agregar conexión',connectionType:'Tipo de conexión',clusterManagement:'PegaProx Cluster Management for Proxmox VE',// Tabs / Pestañas overview:'Vista general',resources:'Recursos',datacenter:'Datacenter',settings:'Valores',of:'de',showing:'Mostrando',perPage:'Por página',loadingDatacenter:'Cargando datos de datacenter...',// Cluster -clusters:'Clústeres',noClusterSelected:'No hay un cluster seleccionado',allClustersOverview:'Vista general de los clústeres',allClusters:'Todos los clústeres',multiClusterSummary:'Resumen de todos los clústeres gestionados',clustersConnected:'Clústeres conectados',clusterOverview:'Vista general de cluster',clusterCpu:"CPU en el cluster",clusterMemory:"Memoria en el cluster",clusterServices:"Servicios de cluster",clusterStorage:"Almacenamiento en el cluster",vmsRunning:'VMs corriendo',vmsStopped:'VMs detenidas',noClustersConfigured:'No hay clústeres configurados',addClusterToStart:'Agregar un cluster para comenzar',clickClusterTip:'Seleccionar la fila de un cluster o seleccionar de la barra lateral para ver información detallada y manejar los recursos',health:'Salud',average:'Promedio',noDataAvailable:'No hay datos disponibles',clickToManage:'Clic para gestionar',sortBy:'Ordenar por',ungrouped:'Sin agrupar',alerts:'Alertas',updated:'Actualizado',justNow:'justo ahora',topResources:'Recursos más',highestCpuUsage:'Consumo más alto de CPU y de RAM en todos los clústeres',clickToOpenVm:'Clic para abrir la VM',selectCluster:'Seleccionar un cluster',addFirstCluster:'Agregar el primer cluster',connectCluster:'Conectar un cluster a PegaProx',clusterName:'Nombre del cluster',host:'Máquina',username:'Usuario',password:'Contraseña',passwordOrToken:'Contraseña / Token',apiTokenHint:'Para tokes de API: usuario@dominio!idtoken',sslVerification:'Verificación de SSL',connecting:'Conectando...',addNewCluster:'Agregar un nuevo cluster',testConnection:'Probar la conexión',testCleanup:"Limpiar prueba",deleteCluster:'Remover cluster',deleteClusterConfirm:'¿Remover realmente el cluster?',reconfigureCluster:'Reconfigurar cluster',reconfigureHint:'Ingrese su contraseña de PegaProx para verificar su identidad.',clusterReconfigured:'Cluster reconfigurado exitosamente',reconfigure:'Reconfigurar',clusterHealth:'Salud del cluster',clusterHealthTooltip:'Salud = 100 − (CPU×30% + RAM×30% + Almacenamiento×20% + Nodos fuera de línea×20%)\nSin datos de almacenamiento: CPU×37,5% + RAM×37,5% + Fuera de línea×25%\n\n80+: Excelente\n60–79: Buena\n40–59: Advertencia\n< 40: Crítica',nodeScoreTooltip:'Puntuación nodo = CPU% + RAM% (menor es mejor)\n\n< 100: Bueno (verde)\n100–150: Elevado (amarillo)\n> 150: Crítico (rojo)',excellent:'Excelente',good:'Buena',warning:'Advertencia',critical:'Crítica',nodesOnline:'Nodos en línea',nodeJoinHint:'Para agregar un nuevo nodo, ejecute en el nodo nuevo:',avgScore:'Puntos promedio',avgStorage:'Almac. promedio',avgCpu:'CPU promedio',avgRam:'RAM promedio',// Nodes / Nodos +clusters:'Clústeres',noClusterSelected:'No hay un cluster seleccionado',allClustersOverview:'Vista general de los clústeres',allClusters:'Todos los clústeres',multiClusterSummary:'Resumen de todos los clústeres gestionados',clustersConnected:'Clústeres conectados',clusterOverview:'Vista general de cluster',clusterCpu:"CPU en el cluster",clusterMemory:"Memoria en el cluster",clusterServices:"Servicios de cluster",clusterStorage:"Almacenamiento en el cluster",vmsRunning:'VMs corriendo',vmsStopped:'VMs detenidas',noClustersConfigured:'No hay clústeres configurados',addClusterToStart:'Agregar un cluster para comenzar',clickClusterTip:'Seleccionar la fila de un cluster o seleccionar de la barra lateral para ver información detallada y manejar los recursos',health:'Salud',average:'Promedio',noDataAvailable:'No hay datos disponibles',clickToManage:'Clic para gestionar',sortBy:'Ordenar por',ungrouped:'Sin agrupar',alerts:'Alertas',updated:'Actualizado',justNow:'justo ahora',topResources:'Recursos más',highestCpuUsage:'Consumo más alto de CPU y de RAM en todos los clústeres',clickToOpenVm:'Clic para abrir la VM',selectCluster:'Seleccionar un cluster',addFirstCluster:'Agregar el primer cluster',connectCluster:'Conectar un cluster a PegaProx',clusterName:'Nombre del cluster',host:'Máquina',username:'Usuario',password:'Contraseña',passwordOrToken:'Contraseña / Token',apiTokenHint:'Para tokes de API: usuario@dominio!idtoken',sslVerification:'Verificación de SSL',connecting:'Conectando...',addNewCluster:'Agregar un nuevo cluster',testConnection:'Probar la conexión',testCleanup:"Limpiar prueba",deleteCluster:'Remover cluster',deleteClusterConfirm:'¿Remover realmente el cluster?',reconfigureCluster:'Reconfigurar cluster',reconfigureHint:'Ingrese su contraseña de PegaProx para verificar su identidad.',clusterReconfigured:'Cluster reconfigurado exitosamente',reconfigure:'Reconfigurar',clusterHealth:'Salud del cluster',clusterHealthTooltip:'Salud = 100 − (CPU×30% + RAM×30% + Almacenamiento×20% + Nodos fuera de línea×20%)\n\n80+: Excelente\n60–79: Buena\n40–59: Advertencia\n< 40: Crítica',nodeScoreTooltip:'Puntuación nodo = CPU% + RAM% (menor es mejor)\n\n< 100: Bueno (verde)\n100–150: Elevado (amarillo)\n> 150: Crítico (rojo)',excellent:'Excelente',good:'Buena',warning:'Advertencia',critical:'Crítica',nodesOnline:'Nodos en línea',nodeJoinHint:'Para agregar un nuevo nodo, ejecute en el nodo nuevo:',avgScore:'Puntos promedio',avgStorage:'Almac. promedio',avgCpu:'CPU promedio',avgRam:'RAM promedio',// Nodes / Nodos nodes:'Nodos',node:'Nodo',loadingMetrics:'Cargando métricas...',connectionError:'Error de conexión',retry:'Reintentar',checkConnectionAndRetry:'Favor revise su conexión e intente nuevamente.',connectionTimeout:'Timeout - Proxmox inalcanzable',maintenance:'Mantenimiento',enterMaintenance:'Enter a modo mantenimiento',exitMaintenance:'Salir de modo mantenimiento',maintenanceMode:'Modo mantenimiento',update:'Actualizar',startUpdate:'Iniciar actualización',nodeConfig:'Configuración de nodo',cpuHistory:'Historia de CPU',ramHistory:'Historia de RAM',ramUsage:'Consumo de RAM',cpuUsage:'Consumo de CPU',diskUsage:'Consumo de disco',allocated:'asignado',showMore:'Mostrar más',showLess:'Mostrar menos',fixAvailable:"actualización disponible",installing:"Instalando...",inventoryOverview:"Vista General de Inventario",items:"ítems",recentItems:"Recientes",rename:"Renombrar",// VMs & Resources / VMs y recursos virtualMachines:'Máquinas virtuales',containers:'Contenedores',vm:'VM',lxc:'LXC',lxcContainer:'Contenedor LXC',container:'Contenedor',guests:'Huéspedes',start:'Iniciar',stop:'Detener',shutdown:'Concluir',reboot:'Reiniciar',forceStop:'Detener forzado',forceReset:'Reset forzado',forceStopConfirm:'¿forzar realmente la parada? ¡puede perder datos!',migrate:'Migrar',migrateVm:'Migrar VM',crossClusterMigration:'Migración Cross-Cluster',crossClusterMigrateDesc:'Migrar una VM a otro cluster Proxmox',crossClusterMigrate:'Migración Cross-Cluster',crossClusterStarted:'Migración Cross-Cluster iniciada',crossClusterFailed:'Migración Cross-Cluster fallida',crossClusterLB:'Balanceo de cargas (LB) Cross-Cluster',crossClusterLBEnabled:'Cross-Cluster LB activado',crossClusterLBDisabled:'Cross-Cluster LB desactivado',crossClusterLBThreshold:'Límite de puntaje',crossClusterLBInterval:'Intervalo de chequeo',crossClusterLBDryRun:'Simular (prueba en vacío)',crossClusterLBLastRun:'Última ejecución',crossClusterReplication:'Replicación Cross-Cluster',crossClusterReplicationDesc:'Replicar instantáneas de VM a otro cluster (DR)',groupOverview:'Vista general de group',groupSettings:'Valores de grupo',groupSettingsSaved:'Valores de grupo guardados',replicationSchedule:'Agendar',replicationRetention:'Retención',maxMigrations:'Migraciones máximas por ciclo',lbHistory:'Historia de balanceo',clusterScore:'Puntaje de cluster',noReplicationJobs:'No hay tareas de replicación',createReplicationJob:'Crear tarea de replicación',replicationStarted:'Replicación iniciada',replicationJobCreated:'Tarea de replicación creada',replicationJobDeleted:'Tarea de replicación removida',recentLbActions:'Acciones recientes de LB cross-cluster',noLbEvents:'No hay eventos de LB Cross-Cluster',enableCrossClusterLB:'Activar balanceo (LB) Cross-Cluster',lbDescription:'Migrar automáticamente VMs entre clústeres cuando los umbrales se excedan',dryRunMode:'Ejecución seca / Modo simulación',cpuThreshold:'Límite (%) de CPU',lbStatus:'Estado de balanceo',lbExplanation:'El balanceo de cargas Cross-Cluster monitorea el consumo de CPU y de RAM a través de todos los clústeres del grupo. Cuando un cluster excede el umbral configurado las VMs son migradas automáticamente a un cluster con menos carga',lbDryRunExplanation:'Active primero el modo de ejecución en vacío para revisar primero cuáles acciones deberán tomarse antes de activar migraciones en vivo',neverRun:'Nunca ejecutar',newCrossClusterReplication:'Nueva replicación Cross-Cluster',confirmDeleteXRepl:'¿remover la tarea de replicación?',addDrJob:'Agregar una tarea de DR',scheduleCron:'Agendar (cron)',targetStorageHint:'Nombre del almacenamiento en el cluster destino',maxMigrationsHint:'Límite de cuántas VMs se pueden mover por ciclo (1-5)',proxlbCredit:'ProxLB by gyptazy',proxlbCreditDesc:'Nuestra funcionalidad de balanceo de cargas se base en el excelente trabajo de ProxLB ¡Gracias especiales a gyptazy por crear y liberar el código de esta asombrosa herramienta!',xReplCreated:'Replicación Cross-Cluster creada',xReplDeleted:'Replicación Cross-Cluster removida',xReplStarted:'Replicación Cross-Cluster iniciada',xReplCreateFailed:'Falló la creación de la tarea',xReplDeleteFailed:'Falló la remoción de la tarea',xReplStartFailed:'Falló el iniciar',lastRunPrefix:'Última',runNow:'Ejecutar ahora',loadingCrossClusterResources:'Cargando el almacenamiento y las redes de todos los clústeres...',noCommonStorages:'No se encontró almacenamiento común a todos los clústeres',noCommonBridges:'No se encontró un puente común a todos los clústeres',selectBridge:'Seleccionar el puente...',crossClusterThresholdDesc:'Límite de CPU para el desbalance de cluster',crossClusterIntervalDesc:'Tiempo entre ciclos de chequeo',crossClusterMaxMigrationsDesc:'Máximo de migraciones por ciclo',commonStorageHint:'Solo los almacenamientos comunes al grupo',commonBridgeHint:'Solo los puentes comunes al grupo',includeContainers:'Incluir contenedores',includeContainersDesc:'Incluir los contenedores LXC en el balanceo Cross-Cluster',containerMigrationWarning:'Los contenedores son reiniciados durante las migraciones (interrupciones)',excludedVMsCrossCluster:'Excluir VMs/Contenedores',excludedVMsCrossClusterDesc:'VMs y contenedores excluidos del balanceo automático Cross-Cluster',clusterExcludedVMs:'VMs excluidas',noExcludedVMsInGroup:'No hay VMs excluidas',selectCluster:'Seleccionar el cluster...',selectClusterFirst:'Primero seleccione un cluster destino',simulationMode:'Modo simulación',sourceVm:'VM origen',sourceBridge:"Red (puente) origen",sourceStorage:"Almacenamiento origen",targetCluster:'Cluster destino',targetNode:'Nodo destino',targetStorage:'Almacenamiento destino',targetBridge:'Red (puente) destino',moveDisk:'Mover disco',resizeDisk:'Cambiar tamaño de disco',diskResized:'Disco cambiado',deleteSourceDisk:'Remover la fuente luego del movimiento',deleteSourceDiskWarning:'El disco original se mantendrá en el almacenamiento origen. Puede removerlo manualmente después.',move:'Mover',from:'desde',nativeProxmoxReplication:"Replicación Nativa (ZFS) de Proxmox",noNetworkInterfaces:'No hay interfaces de red',nameRequired:'Se requiere el nombre',newVmid:'Nuevo VM ID (opcional)',sameIdPlaceholder:'Vacío = mismo ID',liveMigrationOption:'Migración en vivo (la VM sigue corriendo)',deleteSourceAfter:'Remover la fuente luego de la migración',largeDiskWarning:'Se detectó un disco grande',largeDiskExplanation:'La migración de discos >100GB puede fallar con un código "401 Unauthorized" debido a timeouts del ticket WebSocket de Proxmox. El servidor usará migración fuera de línea de manera automática a menos que se force otra cosa.',forceOnlineMigration:'Forzar la migración en línea (puede fallar)',autoTokenInfo:'PegaProx crea tokens de API temporales automáticamente para la migración y los borra luego de completar',clusterReachableInfo:'Los clústeres deben poderse alcanzar entre ellos a través de la red. El usuario seleccionado debe tener permisos para crear tokens de API.',selectCluster:'Seleccionar el cluster...',selectNode:'Seleccionar el nodo',selectStorage:'Seleccionar el almacenamiento...',loadingNodes:'Cargando los nodos destino...',loadingStorageNetwork:'Cargando la red y el almacenamiento...',liveMigration:'Migración en vivo (sin interrupción)',on:'en',targetStorage:'Almacenamiento destino',sameAsSource:'Lo mismo que en la fuente',loadingStorages:'Cargando almacenamiento',free:'libre',withLocalDisks:'Con discos locales',withLocalDisksDesc:'Migrar discos locales al almacenamiento destino',localDisksDetected:'Esta VM tiene discos locales. La migración así requiere copiar datos de discos.',requiredForThisVm:'Se requiere para esta VM',cdDvdMounted:'La unidad CD/DVD está montada',cdDvdMigrationWarning:'La migración en vivo no es posible con un CD/DVD montado. Favor expulse el CD/DVD antes o intente una migración en frío',isoMounted:'ISO/CD-ROM montado',isoEjected:'CD-ROM expulsado',isoMigrationWarning:'La migración puede fallar si el ISO no está disponible en el nodo destino. Expulse el CD/DVD o asegúrese de que el ISO exista en almacenamiento compartido.',localStorage:'Local',bootOrderIssue:'Asunto de orden de arranque (boot)',bootOrderWarning:'La lista de arranque (boot) menciona discos no existentes',bootOrder:'Lista de inicio (boot)',dragToReorder:'Clic para alternar, arrastre para reorganizar',noBootDevices:'No se encontraron dispositivos de arranque',resizeDiskHint:'Aumentar en (p.ej. +10G) o un nuevo tamaño',// SMBIOS Settings / Valores de SMBIOS smbiosSettings:'Valores de SMBIOS',smbiosHint:'System Management BIOS - útil para el licenciamiento de Windows y para identificación de VMs',applySmbiosFromClusterConfig:'Aplicar valores de SMBIOS para configuración de cluster',requiresRestart:'REINICIO',readOnly:'solo-lectura',autoGenerated:'auto-generado',smbiosFormatHint:'Solo se permiten letras y números (A-Za-z0-9)',preview:'Vista previa',currentValue:'Valor actual',managedByProxmox:'gestionado por Proxmox',willBeAutoGenerated:'Se autogenerará al guardar',forceConntrack:'Forzar (estado Conntrack)',forceConntrackDesc:'Forzar la migración aún si se hallan entradas Conntrack',containerNoLiveMigration:'Los contenedores no soportan migración en vivo real',startingMigration:'Iniciando la migración de',migrationStarted:'Migración iniciada:',migrationFailed:'Migración fallida',clone:'Clonar',console:'Consola',openConsole:'Abrir la consola',metrics:'Métricas',config:'Configuración',configuration:'Configuración',createVm:'Crear una nueva VM',createContainer:'Crear un nuevo contenedor',newVm:'Nueva VM',newContainer:'Nuevo contenedor',power:'Energía',snapshot:'Instantánea',editSettings:'Valores',refreshData:'Refrescar',sshConsole:'Consola SSH',noNodesAvailable:'No hay nodos disponibles. Favor esperar a que carguen los valores de cluster',loadingStorage:'Cargando la lista de almacenamiento...',noIsoAvailable:'No se encontraron imágenes ISO',noTemplateAvailable:'No hay plantillas disponibles',noStorageAvailable:'No hay almacenamienot disponible',noIsoStorage:'No se encontró almacenamiento conteniendo ISOs',ram:'RAM',cpu:'CPU',disk:'Disco',network:'Red',networkMappings:"Mapeos de red",networkOverview:"Vista General de la Red",networking:"Redes",// VM Creation Wizard @@ -2402,7 +2402,7 @@ poolPermissions:'Permissões de Pool',poolPermissionsDesc:'Conceda a usuários ou grupos acesso a pools de recursos do Proxmox. As permissões se aplicam a todas as VMs dentro do pool.',managePools:'Gerenciar Pools',poolManagerDesc:'Crie, edite e exclua pools de recursos. Atribua VMs a pools para um gerenciamento de permissões organizado.',createPool:'Criar Pool',editPool:'Editar Pool',deletePool:'Excluir Pool',poolId:'ID do Pool',poolIdRequired:'O ID do Pool é obrigatório',poolIdHint:'Apenas letras, números, hifens e sublinhados',poolIdCannotChange:'O ID do Pool não pode ser alterado',poolCreated:'Pool criado com sucesso',poolUpdated:'Pool atualizado com sucesso',poolDeleted:'Pool excluído com sucesso',confirmDeletePool:'Tem certeza de que deseja excluir este pool? Isso não pode ser desfeito.',noPoolsYet:'Nenhum pool ainda',createFirstPool:'Crie seu primeiro pool para organizar as VMs',poolMembers:'Membros',members:'membros',addVmToPool:'Adicionar VM ao Pool',assignToPool:'Atribuir ao Pool',removeFromPool:'Remover do pool',vmAddedToPool:'VM adicionada ao pool',vmRemovedFromPool:'VM removida do pool',confirmRemoveVmFromPool:'Remover VM deste pool?',selectVmToAdd:'Selecione uma VM para adicionar a este pool',allVmsInPools:'Todas as VMs já estão em pools',optionalDescription:'Descrição opcional...',userPermissions:'Permissões de Usuário',selectPool:'Selecionar Pool',noPools:'Nenhum pool de recursos encontrado neste cluster',noPoolPerms:'Nenhuma permissão configurada para este pool',selectPoolFirst:'Selecione um cluster e um pool para gerenciar permissões',addPoolPerm:'Adicionar Permissão de Pool',editPoolPerm:'Editar Permissão de Pool',poolPermSaved:'Permissão de pool salva',poolPermDeleted:'Permissão de pool removida',refreshPools:'Atualizar pools do Proxmox',poolCacheRefreshed:'Cache de pools atualizado',confirmDeletePoolPerm:'Remover permissão?',subjectType:'Tipo',permissionsFor:'Permissões para',addPermission:'Adicionar Permissão',groupName:'Nome do Grupo',auditLog:'Log de Auditoria',auditLogDescription:'Todas as ações de usuários dos últimos 90 dias',noAuditLogs:'Nenhuma entrada de auditoria disponível',action:'Ação',details:'Detalhes',timestamp:'Data/Hora',ipAddress:'Endereço IP',userCreated:'Usuário criado',userUpdated:'Usuário atualizado',userDeleted:'Usuário excluído',userLogin:'Login',userLogout:'Logout',passwordChanged:'Senha alterada',clusterAdded:'Cluster adicionado',apiTokenWarningTitle:'Autenticação por Token de API',apiTokenWarningDesc:'Sem uma senha, os recursos de SSH não funcionarão (HA, Atualizações Rolantes, SMBIOS, Shell do Nó). Recomendado: Use root@pam + senha — o PegaProx cria automaticamente um token de API para compatibilidade com 2FA.',apiTokenRecommended:'Recomendado quando 2FA está habilitado',apiTokenCreated:'Token de API criado no PVE — o 2FA agora pode ser habilitado com segurança',sshPasswordStillNeeded:'O SSH ainda usa a senha (HA, manutenção) — não a altere',authModeToken:'API: Token (Seguro para 2FA)',authModePassword:'API: Senha',sshAuthMode:'SSH: Senha',dontChangePvePassword:'Não altere a senha do PVE sem atualizá-la aqui',clusterDeleted:'Cluster excluído',clusterConfigChanged:'Configuração do cluster alterada',vmStarted:'VM iniciada',vmStopped:'VM parada',vmRestarted:'VM reiniciada',vmCreated:'VM criada',vmDeleted:'VM excluída',vmCloned:'VM clonada',vmMigrated:'VM migrada',vmUnlocked:'VM desbloqueada',vmLocked:'VM bloqueada',unlockVm:'Desbloquear VM',lockReason:'Motivo do Bloqueio',unlockWarning:'Aviso: Desbloquear uma VM durante uma operação ativa pode causar corrupção de dados ou outros problemas. Prossiga apenas se tiver certeza de que a operação falhou ou foi cancelada.',vmBulkMigrated:'Migração em lote',vmConfigChanged:'Configuração da VM alterada',vmSuspended:'VM suspensa',vmResumed:'VM retomada',vmDiskAdded:'Disco adicionado',vmDiskRemoved:'Disco removido',vmDiskResized:'Disco redimensionado',vmDiskMoved:'Disco movido',vmNetworkAdded:'Rede adicionada',vmNetworkRemoved:'Rede removida',vmNetworkUpdated:'Rede atualizada',removeDiskConfirm:'Realmente remover disco',detachDisk:'Desacoplar',importDisk:'Importar Disco',importDiskDesc:'Importar imagem de disco existente do armazenamento',selectImportStorage:'Selecionar Armazenamento de Origem',selectDiskImage:'Selecionar Imagem de Disco',targetBus:'Barramento de Destino',reassignOwner:'Reatribuir Proprietário',reassignOwnerDesc:'Atribuir disco a uma VM diferente',targetVm:'VM de Destino',diskReassigned:'Disco reatribuído',diskImported:'Disco importado',noImportableDisks:'Nenhuma imagem de disco importável encontrada',reassign:'Reatribuir',import:'Importar',detachDiskConfirm:'Realmente desacoplar o disco? Ele se tornará um disco não utilizado.',diskDetached:'Disco desacoplado',diskDeleted:'Disco excluído',diskAdded:'Disco adicionado',dataWillBeDeleted:'Os dados serão excluídos permanentemente!',removeNetworkConfirm:'Realmente remover rede',snapshotCreated:'Snapshot criado',snapshotDeleted:'Snapshot excluído',snapshotRestored:'Snapshot restaurado',rollbackConfirm:'Deseja realmente reverter para este snapshot? Isso não pode ser desfeito!',replicationCreated:'Replicação criada',replicationDeleted:'Replicação excluída',replicationTriggered:'Replicação acionada',haEnabled:'HA habilitado',haDisabled:'HA desabilitado',noFallbackHosts:'Nenhum host de fallback encontrado (Cluster de Nó Único?)',haVmAdded:'VM adicionada ao HA',haVmRemoved:'VM removida do HA',nodeMaintenanceEntered:'Modo de manutenção ativado',nodeMaintenanceExited:'Modo de manutenção desativado',nodeUpdateStarted:'Atualização do nó iniciada',twoFactorAuth:'Autenticação de Dois Fatores',twoFactorEnabled:'2FA habilitado',twoFactorDisabled:'2FA desabilitado',enable2FA:'Habilitar 2FA',disable2FA:'Desabilitar 2FA',setup2FA:'Configurar 2FA',scan2FACode:'Escaneie o código QR com seu app autenticador',enter2FACode:'Digite o código de 6 dígitos',verify2FA:'Verificar',secretKey:'Chave Secreta',twoFARequired:'Código 2FA obrigatório',invalid2FACode:'Código 2FA inválido',force2FA:'Forçar 2FA',force2FADesc:'Exigir que todos os usuários configurem a Autenticação de Dois Fatores antes de poderem usar o PegaProx.',force2FAHint:'Usuários OIDC/Entra estão isentos (eles usam o MFA do Provedor de Identidade). Usuários sem 2FA verão um diálogo de configuração ao fazer login.',force2FAExcludeAdmins:'Excluir contas de administrador',force2FAExcludeAdminsDesc:'Administradores podem usar o PegaProx sem 2FA',force2FASetupTitle:'Configuração de 2FA Obrigatória',force2FASetupDesc:'Seu administrador tornou a Autenticação de Dois Fatores obrigatória para todos os usuários. Por favor, configure o 2FA para continuar.',resetPassword:'Redefinir Senha',passwordManagedExternally:'Sua senha é gerenciada externamente.',passwordManagedExternallyHint:'Altere sua senha diretamente no seu serviço de diretório.',passwordTooShort:'A senha deve ter pelo menos 4 caracteres',passwordResetSuccess:'Senha alterada com sucesso',myProfile:'Meu Perfil',security:'Segurança',snapshotNotSupported:'Snapshots não suportados',snapshotWarnings:'Avisos de snapshot',filterByUser:'Filtrar por usuário',filterByAction:'Filtrar por ação',allUsers:'Todos os Usuários',allActions:'Todas as Ações',exportAuditLog:'Exportar',refreshAuditLog:'Atualizar',// Header addCluster:'Adicionar Cluster',addXcpngPool:'Adicionar Pool XCP-ng (Tech Preview)',xcpngConnectHint:'Conecte-se ao host mestre do pool. A porta XAPI 443 é usada por padrão.',xcpngTechPreviewNote:'Alguns recursos podem ser limitados ou estar sujeitos a alterações.',pveClusterDesc:'Máquinas virtuais e containers',pbsDesc:'Gerenciamento de backup',vmwareDesc:'Infraestrutura ESXi',xcpngDesc:'XCP-ng / Xen Hypervisor (Tech Preview)',addConnection:'Adicionar Conexão',connectionType:'Tipo de Conexão',clusterManagement:'Gerenciamento de Cluster PegaProx para Proxmox VE',renameCluster:"RenombrarCluster",renameHint:"Dejar vacío para volver al nombre original",// Tabs overview:'Visão Geral',resources:'Recursos',datacenter:'Datacenter',settings:'Configurações',showing:'Mostrando',perPage:'Por página',loadingDatacenter:'Carregando dados do datacenter...',// Cluster -clusters:'Clusters',noClusterSelected:'Nenhum Cluster Selecionado',allClustersOverview:'Visão Geral de Todos os Clusters',multiClusterSummary:'Resumo de todos os clusters gerenciados',clustersConnected:'Clusters Conectados',clusterOverview:'Visão Geral do Cluster',vmsRunning:'VMs em Execução',vmsStopped:'VMs Paradas',noClustersConfigured:'Nenhum cluster configurado',addClusterToStart:'Adicione um cluster para começar',clickClusterTip:'Clique em uma linha de cluster ou selecione na barra lateral para ver informações detalhadas e gerenciar recursos.',health:'Saúde',average:'Média',noDataAvailable:'Nenhum dado disponível',clickToManage:'Clique para gerenciar',sortBy:'Ordenar por',alerts:'Alertas',updated:'Atualizado',justNow:'agora mesmo',topResources:'Principais Recursos',highestCpuUsage:'Maior uso de CPU e RAM em todos os clusters',clickToOpenVm:'Clique para abrir a VM',selectCluster:'Selecione um cluster da lista',addFirstCluster:'Adicionar Primeiro Cluster',connectCluster:'Conectar um cluster Proxmox ao PegaProx',clusterName:'Nome do Cluster',host:'Host',username:'Usuário',password:'Senha',passwordOrToken:'Senha / Token',apiTokenHint:'Para tokens de API: usuario@realm!tokenid',sslVerification:'Verificação SSL',connecting:'Conectando...',addNewCluster:'Adicionar Novo Cluster',testConnection:'Testar Conexão',deleteCluster:'Excluir Cluster',deleteClusterConfirm:'Realmente excluir cluster?',reconfigureCluster:'Reconfigurar Cluster',reconfigureHint:'Digite sua senha do PegaProx para verificar sua identidade.',clusterReconfigured:'Cluster reconfigurado com sucesso',reconfigure:'Reconfigurar',clusterHealth:'Saúde do Cluster',clusterHealthTooltip:'Saúde = 100 − (CPU×30% + RAM×30% + Armazenamento×20% + Nós offline×20%)\nSem dados de armazenamento: CPU×37,5% + RAM×37,5% + Offline×25%\n\n80+: Excelente\n60–79: Bom\n40–59: Aviso\n< 40: Crítico',nodeScoreTooltip:'Pontuação nó = CPU% + RAM% (menor é melhor)\n\n< 100: Bom (verde)\n100–150: Elevado (amarelo)\n> 150: Crítico (vermelho)',excellent:'Excelente',good:'Bom',warning:'Aviso',critical:'Crítico',nodesOnline:'Nós Online',nodeJoinHint:'To add a new node, run on the new node:',// Mantido comando original +clusters:'Clusters',noClusterSelected:'Nenhum Cluster Selecionado',allClustersOverview:'Visão Geral de Todos os Clusters',multiClusterSummary:'Resumo de todos os clusters gerenciados',clustersConnected:'Clusters Conectados',clusterOverview:'Visão Geral do Cluster',vmsRunning:'VMs em Execução',vmsStopped:'VMs Paradas',noClustersConfigured:'Nenhum cluster configurado',addClusterToStart:'Adicione um cluster para começar',clickClusterTip:'Clique em uma linha de cluster ou selecione na barra lateral para ver informações detalhadas e gerenciar recursos.',health:'Saúde',average:'Média',noDataAvailable:'Nenhum dado disponível',clickToManage:'Clique para gerenciar',sortBy:'Ordenar por',alerts:'Alertas',updated:'Atualizado',justNow:'agora mesmo',topResources:'Principais Recursos',highestCpuUsage:'Maior uso de CPU e RAM em todos os clusters',clickToOpenVm:'Clique para abrir a VM',selectCluster:'Selecione um cluster da lista',addFirstCluster:'Adicionar Primeiro Cluster',connectCluster:'Conectar um cluster Proxmox ao PegaProx',clusterName:'Nome do Cluster',host:'Host',username:'Usuário',password:'Senha',passwordOrToken:'Senha / Token',apiTokenHint:'Para tokens de API: usuario@realm!tokenid',sslVerification:'Verificação SSL',connecting:'Conectando...',addNewCluster:'Adicionar Novo Cluster',testConnection:'Testar Conexão',deleteCluster:'Excluir Cluster',deleteClusterConfirm:'Realmente excluir cluster?',reconfigureCluster:'Reconfigurar Cluster',reconfigureHint:'Digite sua senha do PegaProx para verificar sua identidade.',clusterReconfigured:'Cluster reconfigurado com sucesso',reconfigure:'Reconfigurar',clusterHealth:'Saúde do Cluster',clusterHealthTooltip:'Saúde = 100 − (CPU×30% + RAM×30% + Armazenamento×20% + Nós offline×20%)\n\n80+: Excelente\n60–79: Bom\n40–59: Aviso\n< 40: Crítico',nodeScoreTooltip:'Pontuação nó = CPU% + RAM% (menor é melhor)\n\n< 100: Bom (verde)\n100–150: Elevado (amarelo)\n> 150: Crítico (vermelho)',excellent:'Excelente',good:'Bom',warning:'Aviso',critical:'Crítico',nodesOnline:'Nós Online',nodeJoinHint:'To add a new node, run on the new node:',// Mantido comando original avgScore:'Pontuação Média',avgStorage:'Armaz. Médio',avgCpu:'CPU Média',avgRam:'RAM Média',// Nodes nodes:'Nós',loadingMetrics:'Carregando métricas...',connectionError:'Erro de Conexão',retry:'Repetir',checkConnectionAndRetry:'Por favor, verifique sua conexão e tente novamente.',connectionTimeout:'Tempo esgotado - Proxmox inacessível',maintenance:'Manutenção',enterMaintenance:'Ativar Modo de Manutenção',exitMaintenance:'Sair do Modo de Manutenção',maintenanceMode:'Modo de Manutenção',update:'Atualizar',startUpdate:'Iniciar Atualização',nodeConfig:'Configuração do Nó',cpuHistory:'Histórico de CPU',ramHistory:'Histórico de RAM',diskUsage:'Uso de Disco',allocated:'alocado',showMore:'Mostrar mais',showLess:'Mostrar menos',// VMs & Resources virtualMachines:'Máquinas Virtuais',containers:'Containers',vm:'VM',lxc:'LXC',lxcContainer:'Container LXC',container:'Container',guests:'Convidados',start:'Iniciar',stop:'Parar',shutdown:'Desligar',reboot:'Reiniciar',forceStop:'Forçar Parada',forceReset:'Forçar Reset',forceStopConfirm:'Deseja realmente forçar a parada? Isso pode causar perda de dados!',migrate:'Migrar',migrateVm:'Migrar VM',crossClusterMigration:'Migração entre Clusters',crossClusterMigrateDesc:'Migrar VM para outro cluster Proxmox',crossClusterMigrate:'Migrar entre Clusters',crossClusterStarted:'Migração entre clusters iniciada',crossClusterFailed:'Migração entre clusters falhou',crossClusterLB:'Balanceamento de Carga entre Clusters',crossClusterLBEnabled:'LB entre clusters habilitado',crossClusterLBDisabled:'LB entre clusters desabilitado',crossClusterLBThreshold:'Limiar de Pontuação',crossClusterLBInterval:'Intervalo de Verificação',crossClusterLBDryRun:'Simulação (Dry Run)',crossClusterLBLastRun:'Última Execução',crossClusterReplication:'Replicação entre Clusters',crossClusterReplicationDesc:'Replicar snapshots de VM para outro cluster (DR)',groupOverview:'Visão Geral do Grupo',groupSettings:'Configurações do Grupo',groupSettingsSaved:'Configurações do grupo salvas',replicationSchedule:'Agendamento',replicationRetention:'Retenção',maxMigrations:'Máx. Migrações por Ciclo',lbHistory:'Histórico de LB',clusterScore:'Pontuação do Cluster',noReplicationJobs:'Nenhum job de replicação entre clusters',createReplicationJob:'Criar Job de DR',replicationStarted:'Replicação iniciada',replicationJobCreated:'Job de replicação criado',replicationJobDeleted:'Job de replicação excluído',recentLbActions:'Ações recentes de LB entre clusters',noLbEvents:'Nenhum evento de LB entre clusters ainda',enableCrossClusterLB:'Habilitar Balanceamento de Carga entre Clusters',lbDescription:'Migrar automaticamente VMs entre clusters quando os limites de recursos forem excedidos',dryRunMode:'Modo Dry Run / Simulação',cpuThreshold:'Limiar de CPU (%)',lbStatus:'Status do Balanceamento de Carga',lbExplanation:'O Balanceamento de Carga entre Clusters monitora o uso de CPU e RAM em todos os clusters deste grupo. Quando um cluster excede o limiar configurado, as VMs são migradas automaticamente para um cluster menos carregado.',lbDryRunExplanation:'Habilite o modo Dry Run primeiro para revisar quais ações seriam tomadas antes de habilitar as migrações reais.',neverRun:'Nunca executado',newCrossClusterReplication:'Nova Replicação entre Clusters',confirmDeleteXRepl:'Excluir este job de replicação entre clusters?',addDrJob:'Adicionar Job de DR',scheduleCron:'Agendamento (cron)',targetStorageHint:'Nome do armazenamento no cluster de destino',maxMigrationsHint:'Limite de quantas VMs são movidas a cada ciclo de verificação (1-5)',proxlbCredit:'ProxLB por gyptazy',proxlbCreditDesc:'Nossa funcionalidade de balanceamento de carga é baseada no excelente trabalho do ProxLB. Agradecimentos especiais ao gyptazy por criar e abrir o código desta ferramenta incrível!',xReplCreated:'Replicação entre clusters criada',xReplDeleted:'Replicação entre clusters excluída',xReplStarted:'Replicação entre clusters iniciada',xReplCreateFailed:'Falha ao criar job',xReplDeleteFailed:'Falha ao excluir',xReplStartFailed:'Falha ao iniciar',lastRunPrefix:'Última',runNow:'Executar agora',loadingCrossClusterResources:'Carregando armazenamento/rede de todos os clusters...',noCommonStorages:'Nenhum armazenamento comum encontrado em todos os clusters',noCommonBridges:'Nenhuma bridge comum encontrada em todos os clusters',selectBridge:'Selecionar bridge...',crossClusterThresholdDesc:'Limiar de CPU para desequilíbrio de cluster (10-80%)',crossClusterIntervalDesc:'Tempo entre ciclos de verificação',crossClusterMaxMigrationsDesc:'Máx. migrações por ciclo de verificação',commonStorageHint:'Apenas armazenamentos disponíveis em todos os clusters deste grupo',commonBridgeHint:'Apenas bridges disponíveis em todos os clusters deste grupo',includeContainers:'Incluir Containers',includeContainersDesc:'Incluir containers (LXC) no balanceamento entre clusters',containerMigrationWarning:'Containers são reiniciados durante a migração (tempo de inatividade)',excludedVMsCrossCluster:'VMs/Containers Excluídos',excludedVMsCrossClusterDesc:'VMs e containers excluídos do balanceamento automático entre clusters',clusterExcludedVMs:'VMs Excluídas',noExcludedVMsInGroup:'Nenhuma VM excluída',selectClusterFirst:'Selecione um cluster de destino primeiro',simulationMode:'Modo de Simulação',sourceVm:'VM de Origem',targetCluster:'Cluster de Destino',targetNode:'Nó de Destino',targetStorage:'Armazenamento de Destino',targetBridge:'Rede de Destino (Bridge)',moveDisk:'Mover Disco',resizeDisk:'Redimensionar Disco',deleteSourceDisk:'Excluir origem após mover',deleteSourceDiskWarning:'O disco original permanecerá no armazenamento de origem. Você pode removê-lo manualmente depois.',move:'Mover',from:'de',noNetworkInterfaces:'Sem interfaces de rede',nameRequired:'Nome obrigatório',newVmid:'Novo VMID (opcional)',sameIdPlaceholder:'Vazio = mesmo ID',liveMigrationOption:'Live Migration (a VM continua rodando)',deleteSourceAfter:'Excluir VM de origem após migração',largeDiskWarning:'Disco Grande Detectado',largeDiskExplanation:'Live migration para discos >100GB pode falhar com "401 Unauthorized" devido ao timeout do ticket WebSocket do Proxmox. O servidor usará automaticamente a migração offline, a menos que seja forçado.',forceOnlineMigration:'Forçar migração online mesmo assim (pode falhar)',autoTokenInfo:'O PegaProx cria automaticamente tokens de API temporários para migração e os exclui após a conclusão.',clusterReachableInfo:'Os clusters devem ser capazes de se alcançar pela rede. O usuário configurado deve ter permissões para criar tokens de API.',selectNode:'Selecionar Nó',selectStorage:'Selecionar armazenamento...',loadingNodes:'Carregando nós de destino...',loadingStorageNetwork:'Carregando armazenamento/rede...',liveMigration:'Live Migration (sem tempo de inatividade)',on:'em',sameAsSource:'Mesmo que a origem',loadingStorages:'Carregando armazenamentos',free:'livre',withLocalDisks:'Com Discos Locais',withLocalDisksDesc:'Migrar discos locais para o armazenamento de destino',localDisksDetected:'Esta VM possui discos locais. A migração requer a cópia dos dados do disco.',requiredForThisVm:'Obrigatório para esta VM',cdDvdMounted:'Drive de CD/DVD Montado',cdDvdMigrationWarning:'Live migration não é possível com um CD/DVD montado. Por favor, ejete o CD/DVD primeiro ou use a migração offline.',isoMounted:'ISO/CD-ROM Montado',isoEjected:'CD-ROM Ejetado',isoMigrationWarning:'A migração pode falhar se a ISO não estiver disponível no nó de destino. Ejete o CD/DVD ou certifique-se de que a ISO existe em um armazenamento compartilhado.',localStorage:'Local',bootOrderIssue:'Problema na Ordem de Boot',bootOrderWarning:'A ordem de boot faz referência a discos inexistentes',bootOrder:'Ordem de Boot',dragToReorder:'Clique para alternar, use as setas para reordenar',noBootDevices:'Nenhum dispositivo de boot encontrado',resizeDiskHint:'Aumentar em (ex: +10G) ou novo tamanho',// SMBIOS Settings @@ -2493,7 +2493,7 @@ poolPermissions:'풀 권한',poolPermissionsDesc:'사용자 또는 그룹에 Proxmox 리소스 풀에 대한 접근 권한을 부여합니다. 권한은 풀 내의 모든 VM에 적용됩니다.',managePools:'풀 관리',poolManagerDesc:'리소스 풀을 생성, 편집 및 삭제합니다. 체계적인 권한 관리를 위해 VM을 풀에 할당합니다.',createPool:'풀 생성',editPool:'풀 편집',deletePool:'풀 삭제',poolId:'풀 ID',poolIdRequired:'풀 ID가 필요합니다',poolIdHint:'문자, 숫자, 대시 및 밑줄만 사용 가능',poolIdCannotChange:'풀 ID는 변경할 수 없습니다',poolCreated:'풀이 성공적으로 생성되었습니다',poolUpdated:'풀이 성공적으로 업데이트되었습니다',poolDeleted:'풀이 성공적으로 삭제되었습니다',confirmDeletePool:'정말 이 풀을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.',noPoolsYet:'아직 풀이 없습니다',createFirstPool:'VM을 구성하기 위한 첫 번째 풀을 생성하세요',poolMembers:'멤버',members:'멤버',addVmToPool:'풀에 VM 추가',assignToPool:'풀에 할당',removeFromPool:'풀에서 제거',vmAddedToPool:'VM이 풀에 추가되었습니다',vmRemovedFromPool:'VM이 풀에서 제거되었습니다',confirmRemoveVmFromPool:'이 풀에서 VM을 제거하시겠습니까?',selectVmToAdd:'이 풀에 추가할 VM을 선택하세요',allVmsInPools:'모든 VM이 이미 풀에 있습니다',optionalDescription:'설명 (선택 사항)...',userPermissions:'사용자 권한',selectPool:'풀 선택',noPools:'이 클러스터에서 리소스 풀을 찾을 수 없습니다',noPoolPerms:'이 풀에 구성된 권한이 없습니다',selectPoolFirst:'권한을 관리할 클러스터와 풀을 선택하세요',addPoolPerm:'풀 권한 추가',editPoolPerm:'풀 권한 편집',poolPermSaved:'풀 권한이 저장되었습니다',poolPermDeleted:'풀 권한이 제거되었습니다',refreshPools:'Proxmox에서 풀 새로고침',poolCacheRefreshed:'풀 캐시가 새로고침되었습니다',confirmDeletePoolPerm:'권한을 제거하시겠습니까?',subjectType:'유형',permissionsFor:'권한 대상:',addPermission:'권한 추가',groupName:'그룹 이름',auditLog:'감사 로그',auditLogDescription:'최근 90일간의 모든 사용자 작업',noAuditLogs:'감사 항목이 없습니다',action:'작업',details:'세부 정보',timestamp:'타임스탬프',ipAddress:'IP 주소',userCreated:'사용자 생성됨',userUpdated:'사용자 업데이트됨',userDeleted:'사용자 삭제됨',userLogin:'로그인',userLogout:'로그아웃',passwordChanged:'비밀번호 변경됨',clusterAdded:'클러스터 추가됨',apiTokenWarningTitle:'API 토큰 인증',apiTokenWarningDesc:'비밀번호 없이는 SSH 기능이 작동하지 않습니다 (HA, 순차 업데이트, SMBIOS, 노드 셸). 권장: root@pam + 비밀번호 사용 — PegaProx가 2FA 호환을 위해 API 토큰을 자동 생성합니다.',apiTokenRecommended:'2FA 활성화 시 권장',apiTokenCreated:'PVE에 API 토큰이 생성되었습니다 — 이제 2FA를 안전하게 활성화할 수 있습니다',sshPasswordStillNeeded:'SSH는 여전히 비밀번호를 사용합니다 (HA, 유지보수) — 변경하지 마세요',authModeToken:'API: 토큰 (2FA 안전)',authModePassword:'API: 비밀번호',sshAuthMode:'SSH: 비밀번호',dontChangePvePassword:'여기서 업데이트하지 않고 PVE 비밀번호를 변경하지 마세요',clusterDeleted:'클러스터 삭제됨',clusterConfigChanged:'클러스터 구성 변경됨',vmStarted:'VM 시작됨',vmStopped:'VM 중지됨',vmRestarted:'VM 재시작됨',vmCreated:'VM 생성됨',vmDeleted:'VM 삭제됨',vmCloned:'VM 복제됨',vmMigrated:'VM 마이그레이션됨',vmUnlocked:'VM 잠금 해제됨',vmLocked:'VM 잠금됨',unlockVm:'VM 잠금 해제',lockReason:'잠금 사유',unlockWarning:'경고: 활성 작업 중 VM 잠금을 해제하면 데이터 손상 또는 기타 문제가 발생할 수 있습니다. 작업이 실패했거나 취소된 것이 확실한 경우에만 진행하세요.',vmBulkMigrated:'대량 마이그레이션',vmConfigChanged:'VM 구성 변경됨',vmSuspended:'VM 일시 중지됨',vmResumed:'VM 재개됨',vmDiskAdded:'디스크 추가됨',vmDiskRemoved:'디스크 제거됨',vmDiskResized:'디스크 크기 변경됨',vmDiskMoved:'디스크 이동됨',vmNetworkAdded:'네트워크 추가됨',vmNetworkRemoved:'네트워크 제거됨',vmNetworkUpdated:'네트워크 업데이트됨',removeDiskConfirm:'정말 디스크를 제거하시겠습니까',detachDisk:'분리',importDisk:'디스크 가져오기',importDiskDesc:'스토리지에서 기존 디스크 이미지 가져오기',selectImportStorage:'소스 스토리지 선택',selectDiskImage:'디스크 이미지 선택',targetBus:'대상 버스',reassignOwner:'소유자 재할당',reassignOwnerDesc:'디스크를 다른 VM에 할당',targetVm:'대상 VM',diskReassigned:'디스크가 재할당되었습니다',diskImported:'디스크가 가져오기되었습니다',noImportableDisks:'가져올 수 있는 디스크 이미지가 없습니다',reassign:'재할당',import:'가져오기',detachDiskConfirm:'정말 디스크를 분리하시겠습니까? 미사용 디스크가 됩니다.',diskDetached:'디스크가 분리되었습니다',diskDeleted:'디스크가 삭제되었습니다',diskAdded:'디스크가 추가되었습니다',dataWillBeDeleted:'데이터가 영구적으로 삭제됩니다!',removeNetworkConfirm:'정말 네트워크를 제거하시겠습니까',snapshotCreated:'스냅샷이 생성되었습니다',snapshotDeleted:'스냅샷이 삭제되었습니다',snapshotRestored:'스냅샷이 복원되었습니다',rollbackStarted:'롤백이 시작되었습니다',includeRam:'RAM 포함',snapshotName:'스냅샷 이름',rollbackConfirm:'정말 이 스냅샷으로 롤백하시겠습니까? 이 작업은 되돌릴 수 없습니다!',replicationCreated:'복제가 생성되었습니다',replicationDeleted:'복제가 삭제되었습니다',replicationTriggered:'복제가 트리거되었습니다',haEnabled:'HA 활성화됨',haDisabled:'HA 비활성화됨',noFallbackHosts:'대체 호스트를 찾을 수 없습니다 (단일 노드 클러스터?)',haVmAdded:'VM이 HA에 추가되었습니다',haVmRemoved:'VM이 HA에서 제거되었습니다',nodeMaintenanceEntered:'유지보수 모드가 시작되었습니다',nodeMaintenanceExited:'유지보수 모드가 해제되었습니다',nodeUpdateStarted:'노드 업데이트가 시작되었습니다',twoFactorAuth:'2단계 인증',twoFactorEnabled:'2FA 활성화됨',twoFactorDisabled:'2FA 비활성화됨',enable2FA:'2FA 활성화',disable2FA:'2FA 비활성화',setup2FA:'2FA 설정',scan2FACode:'인증 앱으로 QR 코드를 스캔하세요',enter2FACode:'6자리 코드를 입력하세요',verify2FA:'확인',secretKey:'비밀 키',twoFARequired:'2FA 코드가 필요합니다',invalid2FACode:'잘못된 2FA 코드',force2FA:'2FA 강제 적용',force2FADesc:'모든 사용자가 PegaProx를 사용하기 전에 2단계 인증을 설정하도록 요구합니다.',force2FAHint:'OIDC/Entra 사용자는 면제됩니다 (ID 공급자의 MFA를 사용). 2FA가 없는 사용자는 로그인 시 설정 대화상자가 표시됩니다.',force2FAExcludeAdmins:'관리자 계정 제외',force2FAExcludeAdminsDesc:'관리자는 2FA 없이 PegaProx를 사용할 수 있습니다',force2FASetupTitle:'2FA 설정 필요',force2FASetupDesc:'관리자가 모든 사용자에게 2단계 인증을 필수로 설정했습니다. 계속하려면 2FA를 설정하세요.',resetPassword:'비밀번호 재설정',passwordManagedExternally:'비밀번호가 외부에서 관리됩니다.',passwordManagedExternallyHint:'디렉터리 서비스에서 직접 비밀번호를 변경하세요.',newPassword:'새 비밀번호',confirmPassword:'비밀번호 확인',currentPassword:'현재 비밀번호',passwordsDoNotMatch:'비밀번호가 일치하지 않습니다',passwordTooShort:'비밀번호는 최소 4자 이상이어야 합니다',passwordResetSuccess:'비밀번호가 성공적으로 변경되었습니다',myProfile:'내 프로필',security:'보안',snapshotNotSupported:'스냅샷이 지원되지 않습니다',snapshotWarnings:'스냅샷 경고',filterByUser:'사용자별 필터',filterByAction:'작업별 필터',allUsers:'모든 사용자',allActions:'모든 작업',exportAuditLog:'내보내기',refreshAuditLog:'새로고침',// Header addCluster:'클러스터 추가',addXcpngPool:'XCP-ng 풀 추가 (기술 미리보기)',xcpngConnectHint:'풀 마스터 호스트에 연결합니다. 기본적으로 XAPI 포트 443이 사용됩니다.',xcpngTechPreviewNote:'일부 기능이 제한되거나 변경될 수 있습니다.',pveClusterDesc:'가상 머신 및 컨테이너',pbsDesc:'백업 관리',vmwareDesc:'ESXi 인프라',xcpngDesc:'XCP-ng / Xen Hypervisor (기술 미리보기)',addConnection:'연결 추가',connectionType:'연결 유형',clusterManagement:'Proxmox VE를 위한 PegaProx 클러스터 관리',// Tabs overview:'개요',resources:'리소스',datacenter:'데이터센터',settings:'설정',of:'/',showing:'표시',perPage:'페이지당',loadingDatacenter:'데이터센터 데이터 로딩 중...',// Cluster -clusters:'클러스터',noClusterSelected:'선택된 클러스터 없음',allClustersOverview:'모든 클러스터 개요',allClusters:'모든 클러스터',multiClusterSummary:'모든 관리 클러스터 요약',clustersConnected:'연결된 클러스터',clusterOverview:'클러스터 개요',vmsRunning:'실행 중인 VM',vmsStopped:'중지된 VM',noClustersConfigured:'구성된 클러스터 없음',addClusterToStart:'시작하려면 클러스터를 추가하세요',clickClusterTip:'클러스터 행을 클릭하거나 사이드바에서 선택하여 상세 정보를 보고 리소스를 관리하세요.',health:'상태',average:'평균',noDataAvailable:'사용 가능한 데이터 없음',clickToManage:'관리하려면 클릭',sortBy:'정렬 기준',ungrouped:'그룹 없음',alerts:'알림',updated:'업데이트됨',justNow:'방금 전',topResources:'상위 리소스',highestCpuUsage:'모든 클러스터에서 가장 높은 CPU 및 RAM 사용률',clickToOpenVm:'VM을 열려면 클릭',selectCluster:'목록에서 클러스터를 선택하세요',addFirstCluster:'첫 번째 클러스터 추가',connectCluster:'Proxmox 클러스터를 PegaProx에 연결',clusterName:'클러스터 이름',host:'호스트',username:'사용자명',password:'비밀번호',passwordOrToken:'비밀번호 / 토큰',apiTokenHint:'API 토큰의 경우: user@realm!tokenid',sslVerification:'SSL 확인',connecting:'연결 중...',addNewCluster:'새 클러스터 추가',testConnection:'연결 테스트',deleteCluster:'클러스터 삭제',deleteClusterConfirm:'정말 클러스터를 삭제하시겠습니까?',reconfigureCluster:'클러스터 재구성',reconfigureHint:'본인 확인을 위해 PegaProx 비밀번호를 입력하세요.',clusterReconfigured:'클러스터가 성공적으로 재구성되었습니다',reconfigure:'재구성',clusterHealth:'클러스터 상태',clusterHealthTooltip:'상태 = 100 − (CPU×30% + RAM×30% + 스토리지×20% + 오프라인 노드×20%)\n스토리지 데이터 없음: CPU×37.5% + RAM×37.5% + 오프라인×25%\n\n80+: 우수\n60–79: 양호\n40–59: 경고\n< 40: 심각',nodeScoreTooltip:'노드 점수 = CPU% + RAM% (낮을수록 좋음)\n\n< 100: 양호 (녹색)\n100–150: 주의 (노란색)\n> 150: 심각 (빨간색)',excellent:'우수',good:'양호',warning:'경고',critical:'심각',nodesOnline:'온라인 노드',nodeJoinHint:'새 노드를 추가하려면 새 노드에서 실행하세요:',avgScore:'평균 점수',avgStorage:'평균 스토리지',avgCpu:'평균 CPU',avgRam:'평균 RAM',// Nodes +clusters:'클러스터',noClusterSelected:'선택된 클러스터 없음',allClustersOverview:'모든 클러스터 개요',allClusters:'모든 클러스터',multiClusterSummary:'모든 관리 클러스터 요약',clustersConnected:'연결된 클러스터',clusterOverview:'클러스터 개요',vmsRunning:'실행 중인 VM',vmsStopped:'중지된 VM',noClustersConfigured:'구성된 클러스터 없음',addClusterToStart:'시작하려면 클러스터를 추가하세요',clickClusterTip:'클러스터 행을 클릭하거나 사이드바에서 선택하여 상세 정보를 보고 리소스를 관리하세요.',health:'상태',average:'평균',noDataAvailable:'사용 가능한 데이터 없음',clickToManage:'관리하려면 클릭',sortBy:'정렬 기준',ungrouped:'그룹 없음',alerts:'알림',updated:'업데이트됨',justNow:'방금 전',topResources:'상위 리소스',highestCpuUsage:'모든 클러스터에서 가장 높은 CPU 및 RAM 사용률',clickToOpenVm:'VM을 열려면 클릭',selectCluster:'목록에서 클러스터를 선택하세요',addFirstCluster:'첫 번째 클러스터 추가',connectCluster:'Proxmox 클러스터를 PegaProx에 연결',clusterName:'클러스터 이름',host:'호스트',username:'사용자명',password:'비밀번호',passwordOrToken:'비밀번호 / 토큰',apiTokenHint:'API 토큰의 경우: user@realm!tokenid',sslVerification:'SSL 확인',connecting:'연결 중...',addNewCluster:'새 클러스터 추가',testConnection:'연결 테스트',deleteCluster:'클러스터 삭제',deleteClusterConfirm:'정말 클러스터를 삭제하시겠습니까?',reconfigureCluster:'클러스터 재구성',reconfigureHint:'본인 확인을 위해 PegaProx 비밀번호를 입력하세요.',clusterReconfigured:'클러스터가 성공적으로 재구성되었습니다',reconfigure:'재구성',clusterHealth:'클러스터 상태',clusterHealthTooltip:'상태 = 100 − (CPU×30% + RAM×30% + 스토리지×20% + 오프라인 노드×20%)\n\n80+: 우수\n60–79: 양호\n40–59: 경고\n< 40: 심각',nodeScoreTooltip:'노드 점수 = CPU% + RAM% (낮을수록 좋음)\n\n< 100: 양호 (녹색)\n100–150: 주의 (노란색)\n> 150: 심각 (빨간색)',excellent:'우수',good:'양호',warning:'경고',critical:'심각',nodesOnline:'온라인 노드',nodeJoinHint:'새 노드를 추가하려면 새 노드에서 실행하세요:',avgScore:'평균 점수',avgStorage:'평균 스토리지',avgCpu:'평균 CPU',avgRam:'평균 RAM',// Nodes nodes:'노드',node:'노드',loadingMetrics:'메트릭 로딩 중...',connectionError:'연결 오류',retry:'재시도',checkConnectionAndRetry:'연결을 확인하고 다시 시도하세요.',connectionTimeout:'시간 초과 - Proxmox에 접근할 수 없음',maintenance:'유지보수',enterMaintenance:'유지보수 모드 시작',exitMaintenance:'유지보수 모드 해제',maintenanceMode:'유지보수 모드',update:'업데이트',startUpdate:'업데이트 시작',nodeConfig:'노드 구성',cpuHistory:'CPU 기록',ramHistory:'RAM 기록',ramUsage:'RAM 사용률',cpuUsage:'CPU 사용률',diskUsage:'디스크 사용률',allocated:'할당됨',showMore:'더 보기',showLess:'접기',// VMs & Resources virtualMachines:'가상 머신',containers:'컨테이너',vm:'VM',lxc:'LXC',lxcContainer:'LXC 컨테이너',container:'컨테이너',guests:'게스트',start:'시작',stop:'중지',shutdown:'종료',reboot:'재부팅',forceStop:'강제 중지',forceReset:'강제 리셋',forceStopConfirm:'정말 강제 중지하시겠습니까? 데이터 손실이 발생할 수 있습니다!',migrate:'마이그레이션',migrateVm:'VM 마이그레이션',crossClusterMigration:'클러스터 간 마이그레이션',crossClusterMigrateDesc:'VM을 다른 Proxmox 클러스터로 마이그레이션',crossClusterMigrate:'클러스터 간 마이그레이션',crossClusterStarted:'클러스터 간 마이그레이션이 시작되었습니다',crossClusterFailed:'클러스터 간 마이그레이션 실패',crossClusterLB:'클러스터 간 로드 밸런싱',crossClusterLBEnabled:'클러스터 간 LB 활성화됨',crossClusterLBDisabled:'클러스터 간 LB 비활성화됨',crossClusterLBThreshold:'점수 임계값',crossClusterLBInterval:'확인 간격',crossClusterLBDryRun:'시뮬레이션 (테스트 실행)',crossClusterLBLastRun:'마지막 실행',crossClusterReplication:'클러스터 간 복제',crossClusterReplicationDesc:'VM 스냅샷을 다른 클러스터로 복제 (DR)',groupOverview:'그룹 개요',groupSettings:'그룹 설정',groupSettingsSaved:'그룹 설정이 저장되었습니다',replicationSchedule:'일정',replicationRetention:'보존',maxMigrations:'사이클당 최대 마이그레이션 수',lbHistory:'LB 기록',clusterScore:'클러스터 점수',noReplicationJobs:'클러스터 간 복제 작업 없음',createReplicationJob:'DR 작업 생성',replicationStarted:'복제가 시작되었습니다',replicationJobCreated:'복제 작업이 생성되었습니다',replicationJobDeleted:'복제 작업이 삭제되었습니다',recentLbActions:'최근 클러스터 간 LB 작업',noLbEvents:'아직 클러스터 간 LB 이벤트 없음',enableCrossClusterLB:'클러스터 간 로드 밸런싱 활성화',lbDescription:'리소스 임계값을 초과하면 클러스터 간에 VM을 자동으로 마이그레이션합니다',dryRunMode:'테스트 실행 / 시뮬레이션 모드',cpuThreshold:'CPU 임계값 (%)',lbStatus:'로드 밸런싱 상태',lbExplanation:'클러스터 간 로드 밸런싱은 이 그룹의 모든 클러스터에서 CPU 및 RAM 사용률을 모니터링합니다. 클러스터가 구성된 임계값을 초과하면 VM이 부하가 적은 클러스터로 자동 마이그레이션됩니다.',lbDryRunExplanation:'라이브 마이그레이션을 활성화하기 전에 어떤 조치가 취해질지 검토하려면 먼저 테스트 실행 모드를 활성화하세요.',neverRun:'실행된 적 없음',newCrossClusterReplication:'새 클러스터 간 복제',confirmDeleteXRepl:'이 클러스터 간 복제 작업을 삭제하시겠습니까?',addDrJob:'DR 작업 추가',scheduleCron:'일정 (cron)',targetStorageHint:'대상 클러스터의 스토리지 이름',maxMigrationsHint:'각 확인 주기에 이동할 최대 VM 수 제한 (1-5)',proxlbCredit:'ProxLB by gyptazy',proxlbCreditDesc:'로드 밸런싱 기능은 ProxLB의 뛰어난 작업을 기반으로 합니다. 이 훌륭한 도구를 만들고 오픈소스로 공개해 주신 gyptazy에게 감사드립니다!',xReplCreated:'클러스터 간 복제가 생성되었습니다',xReplDeleted:'클러스터 간 복제가 삭제되었습니다',xReplStarted:'클러스터 간 복제가 시작되었습니다',xReplCreateFailed:'작업 생성 실패',xReplDeleteFailed:'삭제 실패',xReplStartFailed:'시작 실패',lastRunPrefix:'마지막',runNow:'지금 실행',loadingCrossClusterResources:'모든 클러스터의 스토리지/네트워크 로딩 중...',noCommonStorages:'모든 클러스터에서 공통 스토리지를 찾을 수 없습니다',noCommonBridges:'모든 클러스터에서 공통 브릿지를 찾을 수 없습니다',selectBridge:'브릿지 선택...',crossClusterThresholdDesc:'클러스터 불균형에 대한 CPU 임계값 (10-80%)',crossClusterIntervalDesc:'확인 주기 간격',crossClusterMaxMigrationsDesc:'확인 주기당 최대 마이그레이션 수',commonStorageHint:'이 그룹의 모든 클러스터에서 사용 가능한 스토리지만 표시',commonBridgeHint:'이 그룹의 모든 클러스터에서 사용 가능한 브릿지만 표시',includeContainers:'컨테이너 포함',includeContainersDesc:'클러스터 간 밸런싱에 컨테이너 (LXC) 포함',containerMigrationWarning:'컨테이너는 마이그레이션 중 재시작됩니다 (다운타임 발생)',excludedVMsCrossCluster:'제외된 VM/컨테이너',excludedVMsCrossClusterDesc:'자동 클러스터 간 밸런싱에서 제외된 VM 및 컨테이너',clusterExcludedVMs:'제외된 VM',noExcludedVMsInGroup:'제외된 VM 없음',selectCluster:'클러스터 선택...',selectClusterFirst:'먼저 대상 클러스터를 선택하세요',simulationMode:'시뮬레이션 모드',sourceVm:'소스 VM',targetCluster:'대상 클러스터',targetNode:'대상 노드',targetStorage:'대상 스토리지',targetBridge:'대상 네트워크 (브릿지)',moveDisk:'디스크 이동',resizeDisk:'디스크 크기 변경',diskResized:'디스크 크기가 변경되었습니다',deleteSourceDisk:'이동 후 소스 삭제',deleteSourceDiskWarning:'원본 디스크가 소스 스토리지에 남아 있습니다. 나중에 수동으로 제거할 수 있습니다.',move:'이동',from:'에서',noNetworkInterfaces:'네트워크 인터페이스 없음',nameRequired:'이름이 필요합니다',newVmid:'새 VMID (선택 사항)',sameIdPlaceholder:'빈 값 = 동일 ID',liveMigrationOption:'라이브 마이그레이션 (VM이 계속 실행됨)',deleteSourceAfter:'마이그레이션 후 소스 VM 삭제',largeDiskWarning:'대용량 디스크 감지',largeDiskExplanation:'100GB 이상의 디스크에 대한 라이브 마이그레이션은 Proxmox WebSocket 티켓 시간 초과로 인해 "401 Unauthorized" 오류로 실패할 수 있습니다. 강제하지 않는 한 서버가 자동으로 오프라인 마이그레이션을 사용합니다.',forceOnlineMigration:'온라인 마이그레이션 강제 실행 (실패할 수 있음)',autoTokenInfo:'PegaProx가 마이그레이션을 위한 임시 API 토큰을 자동 생성하고 완료 후 삭제합니다.',clusterReachableInfo:'클러스터가 네트워크를 통해 서로 접근할 수 있어야 합니다. 구성된 사용자에게 API 토큰 생성 권한이 있어야 합니다.',selectCluster:'클러스터 선택...',selectNode:'노드 선택',selectStorage:'스토리지 선택...',loadingNodes:'대상 노드 로딩 중...',loadingStorageNetwork:'스토리지/네트워크 로딩 중...',liveMigration:'라이브 마이그레이션 (다운타임 없음)',on:'에',targetStorage:'대상 스토리지',sameAsSource:'소스와 동일',loadingStorages:'스토리지 로딩 중',free:'여유',withLocalDisks:'로컬 디스크 포함',withLocalDisksDesc:'로컬 디스크를 대상 스토리지로 마이그레이션',localDisksDetected:'이 VM에 로컬 디스크가 있습니다. 마이그레이션에는 디스크 데이터 복사가 필요합니다.',requiredForThisVm:'이 VM에 필요합니다',cdDvdMounted:'CD/DVD 드라이브 마운트됨',cdDvdMigrationWarning:'CD/DVD가 마운트된 상태에서는 라이브 마이그레이션이 불가능합니다. 먼저 CD/DVD를 꺼내거나 오프라인 마이그레이션을 사용하세요.',isoMounted:'ISO/CD-ROM 마운트됨',isoEjected:'CD-ROM 꺼내짐',isoMigrationWarning:'ISO가 대상 노드에서 사용 가능하지 않으면 마이그레이션이 실패할 수 있습니다. CD/DVD를 꺼내거나 공유 스토리지에 ISO가 있는지 확인하세요.',localStorage:'로컬',bootOrderIssue:'부팅 순서 문제',bootOrderWarning:'부팅 순서가 존재하지 않는 디스크를 참조합니다',bootOrder:'부팅 순서',dragToReorder:'클릭하여 전환, 화살표로 순서 변경',noBootDevices:'부팅 장치를 찾을 수 없습니다',resizeDiskHint:'증가량 (예: +10G) 또는 새 크기',// SMBIOS Settings smbiosSettings:'SMBIOS 설정',smbiosHint:'System Management BIOS - Windows 라이선스 및 VM 식별에 유용',applySmbiosFromClusterConfig:'클러스터 구성에서 SMBIOS 설정 적용',requiresRestart:'재시작 필요',readOnly:'읽기 전용',autoGenerated:'자동 생성',smbiosFormatHint:'문자와 숫자만 허용됩니다 (A-Za-z0-9)',preview:'미리보기',currentValue:'현재 값',managedByProxmox:'Proxmox에서 관리',willBeAutoGenerated:'저장 시 자동 생성됩니다',forceConntrack:'강제 (Conntrack 상태)',forceConntrackDesc:'conntrack 항목이 있어도 강제 마이그레이션',containerNoLiveMigration:'컨테이너는 진정한 라이브 마이그레이션을 지원하지 않습니다',startingMigration:'마이그레이션 시작:',migrationStarted:'마이그레이션 시작됨:',migrationFailed:'마이그레이션이 실패했습니다',clone:'복제',console:'콘솔',openConsole:'콘솔 열기',metrics:'메트릭',config:'구성',configuration:'구성',createVm:'새 VM 생성',createContainer:'새 컨테이너 생성',newVm:'새 VM',newContainer:'새 컨테이너',power:'전원',snapshot:'스냅샷',editSettings:'설정',refreshData:'새로고침',sshConsole:'SSH 콘솔',noNodesAvailable:'사용 가능한 노드가 없습니다. 클러스터 데이터가 로드될 때까지 기다려 주세요.',loadingStorage:'스토리지 목록 로딩 중...',noIsoAvailable:'ISO 이미지를 찾을 수 없습니다',noTemplateAvailable:'템플릿을 찾을 수 없습니다',noStorageAvailable:'사용 가능한 스토리지 없음',noIsoStorage:'ISO 콘텐츠가 있는 스토리지를 찾을 수 없습니다',ram:'RAM',cpu:'CPU',disk:'디스크',network:'네트워크',// VM Creation Wizard @@ -3277,9 +3277,14 @@ useEffect(()=>{if(!xReplForm.target_cluster||!authFetch)return;let cancelled=false;const fetchTargetResources=async()=>{setXReplLoadingResources(true);setXReplTargetStorages([]);setXReplTargetBridges([]);try{const nodesRes=await authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes`);if(!nodesRes||!nodesRes.ok||cancelled)return;const nodesData=await nodesRes.json();const onlineNode=(Array.isArray(nodesData)?nodesData:nodesData.nodes||[]).find(n=>n.status==='online');if(!onlineNode||cancelled)return;const nodeName=onlineNode.node||onlineNode.name;const[storRes,netRes]=await Promise.all([authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes/${nodeName}/storage`),authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes/${nodeName}/networks`)]);if(cancelled)return;if(storRes&&storRes.ok){const storData=await storRes.json();setXReplTargetStorages((Array.isArray(storData)?storData:storData.storages||[]).filter(s=>s.content&&(s.content.includes('images')||s.content.includes('rootdir'))));}if(netRes&&netRes.ok){const netData=await netRes.json();setXReplTargetBridges((Array.isArray(netData)?netData:netData.networks||[]).filter(n=>n.type==='bridge'||n.type==='OVSBridge'||n.source==='sdn'));}}catch(err){console.error('xrepl target resources:',err);}if(!cancelled)setXReplLoadingResources(false);};fetchTargetResources();return()=>{cancelled=true;};},[xReplForm.target_cluster]);// NS: Cross-cluster replication handlers const handleCreateXRepl=async()=>{try{const res=await authFetch(`${API_URL}/cross-cluster-replications`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({source_cluster:xReplForm.source_cluster,target_cluster:xReplForm.target_cluster,vmid:parseInt(xReplForm.vmid),vm_type:xReplForm.vm_type||'qemu',target_storage:xReplForm.target_storage,target_bridge:xReplForm.target_bridge,schedule:xReplForm.schedule,retention:parseInt(xReplForm.retention)||3})});if(res&&res.ok){if(addToast)addToast(t('xReplCreated')||'Replication job created','success');setShowCreateXRepl(false);setXReplForm({source_cluster:'',vmid:'',vm_type:'qemu',target_cluster:'',target_storage:'',target_bridge:'vmbr0',schedule:'0 */6 * * *',retention:3});await fetchXReplJobs();}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplCreateFailed')||'Failed to create replication job','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleDeleteXRepl=async jobId=>{if(!confirm(t('confirmDeleteXRepl')||'Delete this replication job?'))return;try{const res=await authFetch(`${API_URL}/cross-cluster-replications/${jobId}`,{method:'DELETE'});if(res&&res.ok){if(addToast)addToast(t('xReplDeleted')||'Replication job deleted','success');await fetchXReplJobs();}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplDeleteFailed')||'Failed to delete','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleRunXReplNow=async jobId=>{try{const res=await authFetch(`${API_URL}/cross-cluster-replications/${jobId}/run`,{method:'POST'});if(res&&res.ok){if(addToast)addToast(t('xReplStarted')||'Replication started','success');}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplStartFailed')||'Failed to start','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleSave=async()=>{if(!form.name.trim())return;setSaving(true);await onSave(form);setSaving(false);};// MK: tab config const tabs=[{id:'general',label:t('general')||'General',icon:Icons.Settings},{id:'lb',label:t('crossClusterLB')||'Cross-Cluster LB',icon:Icons.Scale},{id:'replication',label:t('crossClusterReplication')||'Replication',icon:Icons.Globe},{id:'info',label:t('info')||'Info',icon:Icons.Info}];return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4"},/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl w-full max-w-2xl max-h-[80vh] overflow-y-auto"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-5 border-b border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"w-10 h-10 rounded-lg flex items-center justify-center",style:{backgroundColor:(form.color||'#E86F2D')+'33'}},/*#__PURE__*/React.createElement(Icons.Folder,{className:"w-5 h-5",style:{color:form.color||'#E86F2D'}})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-semibold text-white"},t('groupSettings')||'Group Settings'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},group.name))),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-gray-400 hover:text-white transition-colors"},/*#__PURE__*/React.createElement(Icons.X,null))),/*#__PURE__*/React.createElement("div",{className:"flex border-b border-proxmox-border"},tabs.map(tab=>/*#__PURE__*/React.createElement("button",{key:tab.id,onClick:()=>setActiveTab(tab.id),className:`flex items-center gap-2 px-4 py-2.5 text-sm font-medium transition-colors whitespace-nowrap ${activeTab===tab.id?'text-proxmox-orange border-b-2 border-proxmox-orange bg-proxmox-dark/50':'text-gray-400 hover:text-white hover:bg-proxmox-dark/30'}`},/*#__PURE__*/React.createElement(tab.icon,{className:"w-4 h-4"}),tab.label))),/*#__PURE__*/React.createElement("div",{className:"p-5"},activeTab==='general'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('name')||'Name'," *"),/*#__PURE__*/React.createElement("input",{value:form.name,onChange:e=>setForm(p=>({...p,name:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange",placeholder:"Production Cluster Group"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('description')||'Description'),/*#__PURE__*/React.createElement("textarea",{value:form.description,onChange:e=>setForm(p=>({...p,description:e.target.value})),rows:3,className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange resize-none",placeholder:"Optional description for this group..."})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('color')||'Color'),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("input",{type:"color",value:form.color,onChange:e=>setForm(p=>({...p,color:e.target.value})),className:"w-10 h-10 rounded cursor-pointer border border-proxmox-border"}),/*#__PURE__*/React.createElement("input",{type:"text",value:form.color,onChange:e=>setForm(p=>({...p,color:e.target.value})),className:"w-28 px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm font-mono focus:outline-none focus:border-proxmox-orange",placeholder:"#E86F2D"}),/*#__PURE__*/React.createElement("div",{className:"flex gap-1.5"},['#E86F2D','#3B82F6','#22C55E','#EAB308','#8B5CF6','#EC4899'].map(c=>/*#__PURE__*/React.createElement("button",{key:c,onClick:()=>setForm(p=>({...p,color:c})),className:`w-6 h-6 rounded-full border-2 transition-all ${form.color===c?'border-white scale-110':'border-transparent hover:border-gray-500'}`,style:{backgroundColor:c}})))))),activeTab==='lb'&&/*#__PURE__*/React.createElement("div",{className:"space-y-5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white"},t('enableCrossClusterLB')||'Enable Cross-Cluster Load Balancing'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('lbDescription')||'Automatically migrate VMs between clusters when resource thresholds are exceeded')),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_lb_enabled,onChange:e=>setForm(p=>({...p,cross_cluster_lb_enabled:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-proxmox-orange rounded-full peer-focus:ring-2 peer-focus:ring-proxmox-orange/50 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),form.cross_cluster_lb_enabled&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex justify-end"},/*#__PURE__*/React.createElement("button",{onClick:handleXclbBalanceNow,disabled:xclbRunning,className:"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg bg-proxmox-orange/20 text-proxmox-orange hover:bg-proxmox-orange/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"},xclbRunning?React.createElement('span',{className:'w-3.5 h-3.5 border-2 border-proxmox-orange/40 border-t-proxmox-orange rounded-full animate-spin'}):React.createElement(Icons.RefreshCw,{className:'w-3.5 h-3.5'}),t('balanceNow')||'Balance Now')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-3 bg-yellow-500/5 border border-yellow-500/20 rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-4 h-4 text-yellow-400"}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"text-sm text-yellow-300"},t('dryRunMode')||'Dry Run / Simulation Mode'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},t('dryRunDesc')||'Log what would happen without actually migrating'))),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_dry_run,onChange:e=>setForm(p=>({...p,cross_cluster_dry_run:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-yellow-500 rounded-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),/*#__PURE__*/React.createElement(Slider,{label:t('cpuThreshold')||'CPU Threshold (%)',description:t('crossClusterThresholdDesc')||'CPU threshold for cluster imbalance (10-80%)',value:form.cross_cluster_threshold,onChange:v=>setForm(p=>({...p,cross_cluster_threshold:v})),min:10,max:80}),/*#__PURE__*/React.createElement(Slider,{label:t('checkInterval')||'Check Interval',description:t('crossClusterIntervalDesc')||'Time between check cycles',value:form.cross_cluster_interval,onChange:v=>setForm(p=>({...p,cross_cluster_interval:v})),min:300,max:3600,step:60,unit:"s"}),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetStorage')||'Target Storage'),loadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loadingCrossClusterResources')||'Loading...'):commonStorages.length>0?/*#__PURE__*/React.createElement("select",{value:form.cross_cluster_target_storage,onChange:e=>setForm(p=>({...p,cross_cluster_target_storage:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange"},/*#__PURE__*/React.createElement("option",{value:""},t('selectStorage')||'Select storage...'),commonStorages.map(s=>/*#__PURE__*/React.createElement("option",{key:s.storage,value:s.storage},s.storage," (",s.type,")"))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-yellow-400 py-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3 inline mr-1"}),t('noCommonStorages')||'No common storage found across all clusters'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-1"},t('commonStorageHint')||'Only storages available on all clusters')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetBridge')||'Target Bridge'),loadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loadingCrossClusterResources')||'Loading...'):commonBridges.length>0?/*#__PURE__*/React.createElement("select",{value:form.cross_cluster_target_bridge,onChange:e=>setForm(p=>({...p,cross_cluster_target_bridge:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange"},commonBridges.filter(b=>b.source!=='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"Local Bridges"},commonBridges.filter(b=>b.source!=='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface,b.comments?` - ${b.comments}`:''))),commonBridges.filter(b=>b.source==='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"SDN VNets"},commonBridges.filter(b=>b.source==='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface," - ",b.zone||'SDN',b.alias?` (${b.alias})`:'')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-yellow-400 py-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3 inline mr-1"}),t('noCommonBridges')||'No common bridge found across all clusters'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-1"},t('commonBridgeHint')||'Only bridges available on all clusters'))),/*#__PURE__*/React.createElement(Slider,{label:t('maxMigrations')||'Max Migrations per Cycle',description:t('crossClusterMaxMigrationsDesc')||'Max migrations per check cycle',value:form.cross_cluster_max_migrations,onChange:v=>setForm(p=>({...p,cross_cluster_max_migrations:v})),min:1,max:5,step:1,unit:""}),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-3 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"text-sm text-white"},t('includeContainers')||'Include Containers'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('includeContainersDesc')||'Include containers (LXC) in cross-cluster balancing'),form.cross_cluster_include_containers&&/*#__PURE__*/React.createElement("p",{className:"text-xs text-yellow-400 mt-1 flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3"}),t('containerMigrationWarning')||'Containers are restarted during migration (downtime)')),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer ml-4"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_include_containers,onChange:e=>setForm(p=>({...p,cross_cluster_include_containers:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-proxmox-orange rounded-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-1"},t('excludedVMsCrossCluster')||'Excluded VMs/Containers'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mb-3"},t('excludedVMsCrossClusterDesc')||'VMs and containers excluded from automatic cross-cluster balancing'),loadingExcludedVMs?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loading'),"..."):groupClusters.filter(c=>c.connected).length===0?/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-600 py-2"},t('noExcludedVMsInGroup')||'No VMs excluded'):/*#__PURE__*/React.createElement("div",{className:"space-y-2"},groupClusters.filter(c=>c.connected).map(cluster=>{const excluded=excludedVMsByCluster[cluster.id]||[];const allVMs=allVMsByCluster[cluster.id]||[];const excludedIds=excluded.map(v=>v.vmid);const available=allVMs.filter(vm=>!excludedIds.includes(vm.vmid));const isExpanded=expandedClusters[cluster.id];return/*#__PURE__*/React.createElement("div",{key:cluster.id,className:"border border-proxmox-border rounded-lg overflow-hidden"},/*#__PURE__*/React.createElement("button",{onClick:()=>setExpandedClusters(prev=>({...prev,[cluster.id]:!prev[cluster.id]})),className:"w-full flex items-center justify-between p-2.5 bg-proxmox-dark/50 hover:bg-proxmox-dark text-left"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Server,{className:"w-3.5 h-3.5 text-proxmox-orange"}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white"},cluster.name),excluded.length>0&&/*#__PURE__*/React.createElement("span",{className:"text-xs bg-red-500/20 text-red-400 px-1.5 py-0.5 rounded"},excluded.length)),/*#__PURE__*/React.createElement(Icons.ChevronDown,{className:`w-4 h-4 text-gray-500 transition-transform ${isExpanded?'rotate-180':''}`})),isExpanded&&/*#__PURE__*/React.createElement("div",{className:"p-2.5 space-y-2 border-t border-proxmox-border"},excluded.length>0?/*#__PURE__*/React.createElement("div",{className:"space-y-1"},excluded.map(vm=>/*#__PURE__*/React.createElement("div",{key:vm.vmid,className:"flex items-center justify-between bg-proxmox-dark rounded px-2.5 py-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Monitor,{className:"w-3.5 h-3.5 text-red-400"}),/*#__PURE__*/React.createElement("span",{className:"text-sm"},vm.name||`VM ${vm.vmid}`),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},"(",vm.vmid,")")),/*#__PURE__*/React.createElement("button",{onClick:()=>includeVM(cluster.id,vm.vmid),className:"text-xs text-green-400 hover:text-green-300"},t('include')||'Include')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-600 p-2"},t('noExcludedVMsInGroup')||'No VMs excluded'),available.length>0&&/*#__PURE__*/React.createElement("div",{className:"flex gap-2 mt-1"},/*#__PURE__*/React.createElement("select",{id:`xclb-exclude-${cluster.id}`,className:"flex-1 bg-proxmox-dark border border-proxmox-border rounded px-2 py-1.5 text-sm",defaultValue:""},/*#__PURE__*/React.createElement("option",{value:"",disabled:true},t('selectVMToExclude')||'Select VM to exclude...'),available.map(vm=>/*#__PURE__*/React.createElement("option",{key:vm.vmid,value:vm.vmid},vm.name||`VM ${vm.vmid}`," (",vm.vmid,") - ",vm.node))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const select=document.getElementById(`xclb-exclude-${cluster.id}`);const vmid=parseInt(select?.value);if(!vmid)return;const vm=available.find(v=>v.vmid===vmid);excludeVM(cluster.id,vmid,vm?.name);select.value='';},className:"px-2.5 py-1.5 bg-red-500/20 text-red-400 rounded hover:bg-red-500/30 text-xs flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.Ban,{className:"w-3.5 h-3.5"}),t('exclude')||'Exclude'))));}))))),activeTab==='replication'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Globe,{className:"w-4 h-4 text-proxmox-orange"}),t('crossClusterReplication')||'Cross-Cluster Replication'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('crossClusterReplicationDesc')||'Replicate VM snapshots to another cluster (DR)')),groupClusters.length>=2&&/*#__PURE__*/React.createElement("button",{onClick:()=>setShowCreateXRepl(true),className:"flex items-center gap-1.5 px-3 py-1.5 bg-proxmox-orange/10 text-proxmox-orange rounded-lg text-xs hover:bg-proxmox-orange/20 transition-colors"},/*#__PURE__*/React.createElement(Icons.Plus,{className:"w-3.5 h-3.5"}),t('addDrJob')||'Add DR Job')),groupClusters.length<2?/*#__PURE__*/React.createElement("div",{className:"text-center py-8 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-8 h-8 mx-auto mb-2 text-gray-600"}),t('needTwoClusters')||'At least 2 clusters needed for cross-cluster replication'):xReplLoading?/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 py-8 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loading'),"..."):xReplJobs.length===0&&!showCreateXRepl?/*#__PURE__*/React.createElement("div",{className:"text-center py-8 text-gray-500 text-sm"},t('noReplicationJobs')||'No replication jobs configured'):/*#__PURE__*/React.createElement("div",{className:"space-y-2"},xReplJobs.map(job=>{const srcCluster=groupClusters.find(c=>c.id===job.source_cluster);const tgtCluster=groupClusters.find(c=>c.id===job.target_cluster);return/*#__PURE__*/React.createElement("div",{key:job.id,className:"bg-proxmox-dark rounded-lg p-3 flex items-center justify-between border border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex-1 min-w-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-wrap"},/*#__PURE__*/React.createElement("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${job.enabled?'bg-green-500':'bg-gray-500'}`}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white truncate"},srcCluster?.name||job.source_cluster),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3.5 h-3.5 text-gray-500 flex-shrink-0"}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white truncate"},tgtCluster?.name||job.target_cluster),/*#__PURE__*/React.createElement("span",{className:"text-xs bg-proxmox-dark px-1.5 py-0.5 rounded text-gray-400 border border-proxmox-border"},job.vm_type==='lxc'?'CT':'VM'," ",job.vmid)),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 mt-1 flex items-center gap-2 flex-wrap"},/*#__PURE__*/React.createElement("span",null,job.schedule),/*#__PURE__*/React.createElement("span",null,"\xB7"),/*#__PURE__*/React.createElement("span",null,job.target_storage||'default'),job.last_run&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("span",null,"\xB7"),/*#__PURE__*/React.createElement("span",null,t('lastRunPrefix')||'Last',": ",new Date(job.last_run).toLocaleString())),job.last_status&&/*#__PURE__*/React.createElement("span",{className:job.last_status==='OK'?'text-green-400':'text-red-400'},job.last_status),job.last_error&&/*#__PURE__*/React.createElement("span",{className:"text-red-400"},job.last_error))),/*#__PURE__*/React.createElement("div",{className:"flex gap-1 flex-shrink-0 ml-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>handleRunXReplNow(job.id),className:"p-1.5 rounded hover:bg-green-500/10 text-gray-400 hover:text-green-400",title:t('runNow')||'Run now'},/*#__PURE__*/React.createElement(Icons.Play,{className:"w-3.5 h-3.5"})),/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteXRepl(job.id),className:"p-1.5 rounded hover:bg-red-500/10 text-gray-400 hover:text-red-400",title:t('delete')||'Delete'},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-3.5 h-3.5"}))));})),showCreateXRepl&&groupClusters.length>=2&&/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark border border-proxmox-border rounded-lg p-4"},/*#__PURE__*/React.createElement("h5",{className:"text-sm font-medium text-white mb-3"},t('newCrossClusterReplication')||'New Cross-Cluster Replication'),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('sourceCluster')||'Source Cluster'),/*#__PURE__*/React.createElement("select",{value:xReplForm.source_cluster,onChange:e=>setXReplForm(f=>({...f,source_cluster:e.target.value,vmid:'',vm_type:'qemu'})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectCluster')||'Select cluster...'),groupClusters.filter(c=>c.connected).map(c=>/*#__PURE__*/React.createElement("option",{key:c.id,value:c.id},c.name)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},"VM"),xReplLoadingVMs?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.source_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.vmid,onChange:e=>{const vmid=e.target.value;const vm=xReplSourceVMs.find(v=>String(v.vmid)===vmid);setXReplForm(f=>({...f,vmid,vm_type:vm?.type||'qemu'}));},className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectVM')||'Select VM...'),xReplSourceVMs.map(vm=>/*#__PURE__*/React.createElement("option",{key:vm.vmid,value:vm.vmid},vm.name||`VM ${vm.vmid}`," (",vm.vmid,") - ",vm.type==='lxc'?'CT':'VM'))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a source cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetCluster')||'Target Cluster'),/*#__PURE__*/React.createElement("select",{value:xReplForm.target_cluster,onChange:e=>setXReplForm(f=>({...f,target_cluster:e.target.value,target_storage:'',target_bridge:'vmbr0'})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectCluster')||'Select cluster...'),groupClusters.filter(c=>c.connected&&c.id!==xReplForm.source_cluster).map(c=>/*#__PURE__*/React.createElement("option",{key:c.id,value:c.id},c.name)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetStorage')||'Target Storage'),xReplLoadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.target_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.target_storage,onChange:e=>setXReplForm(f=>({...f,target_storage:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectStorage')||'Select storage...'),xReplTargetStorages.map(s=>/*#__PURE__*/React.createElement("option",{key:s.storage,value:s.storage},s.storage," (",s.type,")"))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a target cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetBridge')||'Target Bridge'),xReplLoadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.target_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.target_bridge,onChange:e=>setXReplForm(f=>({...f,target_bridge:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},xReplTargetBridges.filter(b=>b.source!=='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"Local Bridges"},xReplTargetBridges.filter(b=>b.source!=='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface,b.comments?` - ${b.comments}`:''))),xReplTargetBridges.filter(b=>b.source==='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"SDN VNets"},xReplTargetBridges.filter(b=>b.source==='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface," - ",b.zone||'SDN',b.alias?` (${b.alias})`:'')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a target cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('scheduleCron')||'Schedule (Cron)'),/*#__PURE__*/React.createElement("input",{type:"text",value:xReplForm.schedule,onChange:e=>setXReplForm(f=>({...f,schedule:e.target.value})),placeholder:"0 */6 * * *",className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('replicationRetention')||'Retention'),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",max:"30",value:xReplForm.retention,onChange:e=>setXReplForm(f=>({...f,retention:parseInt(e.target.value)||1})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"}))),/*#__PURE__*/React.createElement("div",{className:"flex gap-2 mt-3"},/*#__PURE__*/React.createElement("button",{onClick:handleCreateXRepl,disabled:!xReplForm.source_cluster||!xReplForm.vmid||!xReplForm.target_cluster,className:"px-3 py-1.5 bg-proxmox-orange text-white rounded-lg text-sm hover:bg-proxmox-orange/90 transition-colors disabled:opacity-50"},t('create')||'Create'),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowCreateXRepl(false),className:"px-3 py-1.5 bg-proxmox-dark border border-proxmox-border text-gray-300 rounded-lg text-sm hover:bg-proxmox-darker transition-colors"},t('cancel')||'Cancel'))),Object.keys(nativeReplByCluster).length>0&&/*#__PURE__*/React.createElement("div",{className:"mt-6"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mb-3"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 text-purple-400"}),/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white"},t('nativeProxmoxReplication')||'Native Proxmox Replication (ZFS)')),/*#__PURE__*/React.createElement("div",{className:"space-y-3"},Object.entries(nativeReplByCluster).map(([cid,jobs])=>{const cluster=groupClusters.find(c=>c.id===cid);return/*#__PURE__*/React.createElement("div",{key:cid,className:"bg-proxmox-dark rounded-lg border border-proxmox-border overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"px-3 py-2 border-b border-proxmox-border bg-purple-500/5"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-medium text-purple-300"},cluster?.name||cid)),jobs.map((job,idx)=>{const hasErr=job.fail_count>0||job.error;const lastSync=job.last_sync?new Date(job.last_sync*1000).toLocaleString():'-';return/*#__PURE__*/React.createElement("div",{key:job.id||idx,className:"px-3 py-2 flex items-center justify-between border-b border-proxmox-border/50 last:border-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-wrap min-w-0"},/*#__PURE__*/React.createElement("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${job.disable?'bg-gray-500':hasErr?'bg-red-500':'bg-green-500'}`}),/*#__PURE__*/React.createElement("span",{className:"text-xs bg-proxmox-dark px-1.5 py-0.5 rounded text-gray-400 border border-proxmox-border"},"VM ",job.guest),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},job.source||'?'),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3 h-3 text-gray-600 flex-shrink-0"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},job.target),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600 font-mono"},job.schedule)),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-shrink-0 ml-2"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600"},lastSync),job.duration!=null&&/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600 font-mono"},job.duration.toFixed(1),"s")));}));})))),activeTab==='info'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-3"},t('groupInfo')||'Group Information'),/*#__PURE__*/React.createElement("div",{className:"space-y-2 text-sm"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('groupId')||'Group ID'),/*#__PURE__*/React.createElement("span",{className:"text-white font-mono text-xs"},group.id)),/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('created')||'Created'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.created?new Date(group.created).toLocaleString():'-')),group.tenant_id&&/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('tenant')||'Tenant'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.tenant_id)))),group.cross_cluster_lb_enabled&&/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-3"},t('lbStatus')||'Load Balancing Status'),/*#__PURE__*/React.createElement("div",{className:"space-y-2 text-sm"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('lastRun')||'Last LB Run'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.last_lb_run?new Date(group.last_lb_run).toLocaleString():t('neverRun')||'Never run')),/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('mode')||'Mode'),/*#__PURE__*/React.createElement("span",{className:group.cross_cluster_dry_run?'text-yellow-400':'text-green-400'},group.cross_cluster_dry_run?t('simulation')||'Simulation':t('active')||'Active')))),/*#__PURE__*/React.createElement("div",{className:"p-4 bg-blue-500/5 border border-blue-500/20 rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-start gap-3"},/*#__PURE__*/React.createElement(Icons.Info,{className:"w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5"}),/*#__PURE__*/React.createElement("div",{className:"text-sm text-gray-400"},/*#__PURE__*/React.createElement("p",{className:"mb-2"},t('lbExplanation')||'Cross-Cluster Load Balancing monitors CPU and RAM usage across all clusters in this group. When a cluster exceeds the configured threshold, VMs are automatically migrated to a less loaded cluster.'),/*#__PURE__*/React.createElement("p",null,t('lbDryRunExplanation')||'Enable Dry Run mode first to review what actions would be taken before enabling live migrations.')))))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-end gap-3 p-5 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-sm text-gray-400 hover:text-white transition-colors"},t('cancel')||'Cancel'),/*#__PURE__*/React.createElement("button",{onClick:handleSave,disabled:saving||!form.name.trim(),className:"px-5 py-2 bg-proxmox-orange hover:bg-orange-600 disabled:opacity-50 rounded-lg text-sm font-medium text-white transition-colors flex items-center gap-2"},saving?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"})," ",t('saving')||'Saving...'):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement(Icons.Save,{className:"w-4 h-4"})," ",t('save')||'Save')))));}// Cluster Health Widget -function ClusterHealth({metrics,isCorporate}){const{t}=useTranslation();const nodes=Object.entries(metrics).filter(([k,m])=>m&&typeof m==='object'&&k!=='error'&&k!=='offline');if(nodes.length===0)return null;const avgCpu=nodes.reduce((acc,[,m])=>acc+(m.cpu_percent??0),0)/nodes.length;const avgMem=nodes.reduce((acc,[,m])=>acc+(m.mem_percent??0),0)/nodes.length;const diskNodes=nodes.filter(([,m])=>m.disk_percent!=null);const avgDisk=diskNodes.length>0?diskNodes.reduce((acc,[,m])=>acc+m.disk_percent,0)/diskNodes.length:null;const onlineNodes=nodes.filter(([,m])=>m.status==='online'&&!m.maintenance_mode).length;const maintenanceNodes=nodes.filter(([,m])=>m.maintenance_mode).length;const offlineNodes=nodes.length-onlineNodes-maintenanceNodes;const offlineRatio=nodes.length>0?offlineNodes/nodes.length*100:0;// When disk stats unavailable (e.g. XCP-ng), re-normalize: CPU 37.5%, RAM 37.5%, Offline 25% -const healthScore=avgDisk!=null?Math.max(0,100-(avgCpu*0.3+avgMem*0.3+avgDisk*0.2+offlineRatio*0.2)):Math.max(0,100-(avgCpu*0.375+avgMem*0.375+offlineRatio*0.25));const healthLabel=healthScore>=80?t('excellent'):healthScore>=60?t('good'):healthScore>=40?t('warning'):t('critical');const healthColor=healthScore>=80?'#22c55e':healthScore>=60?'#84cc16':healthScore>=40?'#eab308':'#ef4444';// LW: Feb 2026 - corporate compact variant (Clarity dark theme) -const corpHealthColor=healthScore>=80?'#60b515':healthScore>=60?'#60b515':healthScore>=40?'#efc006':'#f54f47';if(isCorporate){return/*#__PURE__*/React.createElement("div",{className:"p-3",style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)'},title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-[11px] font-semibold uppercase tracking-wider mb-2",style:{color:'var(--corp-text-muted)'}},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4 flex-wrap"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-20 h-2 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${healthScore}%`,backgroundColor:corpHealthColor,borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-medium",style:{color:'var(--color-text)'}},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-[11px]",style:{color:'var(--corp-text-muted)'}},healthLabel)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},t('nodesOnline'),": ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},onlineNodes,"/",nodes.length)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"CPU: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},avgCpu.toFixed(1),"%")),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"RAM: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},avgMem.toFixed(1),"%")),maintenanceNodes>0&&/*#__PURE__*/React.createElement("span",{className:"text-[12px] flex items-center gap-1",style:{color:'var(--color-warning)'}},/*#__PURE__*/React.createElement(Icons.Wrench,{className:"w-3 h-3"})," ",maintenanceNodes," ",t('maintenance'))));}return/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl p-5",title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4"},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center mb-6"},/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement("svg",{viewBox:"0 0 100 100",className:"w-32 h-32"},/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#30363D",strokeWidth:"8"}),/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:healthColor,strokeWidth:"8",strokeLinecap:"round",strokeDasharray:`${healthScore*2.83} 283`,transform:"rotate(-90 50 50)",className:"transition-all duration-1000"})),/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 flex flex-col items-center justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-2xl font-bold text-white"},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-400"},healthLabel)))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},onlineNodes,"/",nodes.length),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('nodesOnline'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgDisk!=null?`${avgDisk.toFixed(1)}%`:'N/A'),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgStorage'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgCpu.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgCpu'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},avgMem.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgRam')))),maintenanceNodes>0&&/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-4 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 text-yellow-400"},/*#__PURE__*/React.createElement(Icons.Wrench,null),/*#__PURE__*/React.createElement("span",{className:"text-sm font-medium"},maintenanceNodes," Node(s) ",t('maintenance')))));}// Create Snapshot Modal - NS: Feb 2026 enhanced with efficient mode +function ClusterHealth({metrics,clusterStatus,isCorporate}){const{t}=useTranslation();const nodes=Object.entries(metrics).filter(([k,m])=>m&&typeof m==='object'&&k!=='error'&&k!=='offline');if(nodes.length===0&&!clusterStatus)return null;// Node status from per-node metrics (has maintenance_mode info not in datacenter status) +const onlineNodes=nodes.filter(([,m])=>m.status==='online'&&!m.maintenance_mode).length;const maintenanceNodes=nodes.filter(([,m])=>m.maintenance_mode).length;// Real-time display values from per-node SSE metrics (~1s updates) +const displayCpu=nodes.length>0?nodes.reduce((acc,[,m])=>acc+(m.cpu_percent??0),0)/nodes.length:0;const displayMem=nodes.length>0?nodes.reduce((acc,[,m])=>acc+(m.mem_percent??0),0)/nodes.length:0;// Health score inputs from cluster-level datacenter/status (same source as badge/overview) +let healthCpu,healthMem,healthStorage,offlineRatio,totalNodeCount;if(clusterStatus&&clusterStatus.resources){const resources=clusterStatus.resources;healthCpu=resources.cpu?.percent||0;healthMem=resources.memory?.percent||0;healthStorage=resources.storage?.percent||0;totalNodeCount=clusterStatus.nodes?.total||nodes.length;const offlineNodes=clusterStatus.nodes?.offline||0;offlineRatio=totalNodeCount>0?offlineNodes/totalNodeCount*100:0;}else{// Fallback: per-node data (when datacenter status not yet loaded) +healthCpu=displayCpu;healthMem=displayMem;const diskNodes=nodes.filter(([,m])=>m.disk_percent!=null);healthStorage=diskNodes.length>0?diskNodes.reduce((acc,[,m])=>acc+m.disk_percent,0)/diskNodes.length:0;totalNodeCount=nodes.length;const offlineNodes=nodes.length-onlineNodes-maintenanceNodes;offlineRatio=totalNodeCount>0?offlineNodes/totalNodeCount*100:0;}// Storage display: use cluster-level storage pools (not rootfs) when available +const displayStorage=clusterStatus?.resources?.storage?.percent??(nodes.length>0?(()=>{const dn=nodes.filter(([,m])=>m.disk_percent!=null);return dn.length>0?dn.reduce((acc,[,m])=>acc+m.disk_percent,0)/dn.length:0;})():0);// Unified formula - same weights as badge/overview (CPU 30%, RAM 30%, Storage 20%, Offline 20%) +const healthScore=Math.max(0,100-(healthCpu*0.3+healthMem*0.3+healthStorage*0.2+offlineRatio*0.2));const healthLabel=healthScore>=80?t('excellent'):healthScore>=60?t('good'):healthScore>=40?t('warning'):t('critical');const healthColor=healthScore>=80?'#22c55e':healthScore>=60?'#eab308':healthScore>=40?'#f97316':'#ef4444';// LW: Feb 2026 - corporate compact variant (Clarity dark theme) +const corpHealthColor=healthScore>=80?'#60b515':healthScore>=60?'#60b515':healthScore>=40?'#efc006':'#f54f47';if(isCorporate){return/*#__PURE__*/React.createElement("div",{className:"p-3",style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)'},title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-[11px] font-semibold uppercase tracking-wider mb-2",style:{color:'var(--corp-text-muted)'}},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4 flex-wrap"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-20 h-2 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${healthScore}%`,backgroundColor:corpHealthColor,borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-medium",style:{color:'var(--color-text)'}},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-[11px]",style:{color:'var(--corp-text-muted)'}},healthLabel)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},t('nodesOnline'),": ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},onlineNodes,"/",totalNodeCount)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"CPU: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},displayCpu.toFixed(1),"%")),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"RAM: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},displayMem.toFixed(1),"%")),maintenanceNodes>0&&/*#__PURE__*/React.createElement("span",{className:"text-[12px] flex items-center gap-1",style:{color:'var(--color-warning)'}},/*#__PURE__*/React.createElement(Icons.Wrench,{className:"w-3 h-3"})," ",maintenanceNodes," ",t('maintenance'))));}return/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl p-5",title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4"},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center mb-6"},/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement("svg",{viewBox:"0 0 100 100",className:"w-32 h-32"},/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#30363D",strokeWidth:"8"}),/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:healthColor,strokeWidth:"8",strokeLinecap:"round",strokeDasharray:`${healthScore*2.83} 283`,transform:"rotate(-90 50 50)",className:"transition-all duration-1000"})),/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 flex flex-col items-center justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-2xl font-bold text-white"},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-400"},healthLabel)))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},onlineNodes,"/",totalNodeCount),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('nodesOnline'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},displayStorage.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgStorage'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},displayCpu.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgCpu'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},displayMem.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgRam')))),maintenanceNodes>0&&/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-4 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 text-yellow-400"},/*#__PURE__*/React.createElement(Icons.Wrench,null),/*#__PURE__*/React.createElement("span",{className:"text-sm font-medium"},maintenanceNodes," Node(s) ",t('maintenance')))));}// Create Snapshot Modal - NS: Feb 2026 enhanced with efficient mode function CreateSnapshotModal({isQemu,onSubmit,onClose,loading,efficientInfo}){const{t}=useTranslation();const[snapname,setSnapname]=useState(`snap_${Date.now()}`);const[description,setDescription]=useState('');const[vmstate,setVmstate]=useState(false);const[mode,setMode]=useState('standard');const[snapSizeGb,setSnapSizeGb]=useState(efficientInfo?.recommended_snap_size_gb||10);const isEfficient=mode==='efficient';const canEfficient=efficientInfo?.eligible;const handleSubmit=()=>{if(!snapname.trim())return;onSubmit(snapname.trim(),description,vmstate,isEfficient?{mode:'efficient',snap_size_gb:snapSizeGb}:{mode:'standard'});};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:`w-full ${canEfficient?'max-w-lg':'max-w-md'} bg-proxmox-card border border-proxmox-border rounded-xl p-6 animate-scale-in`},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold text-white mb-4"},t('createSnapshot')),/*#__PURE__*/React.createElement("div",{className:"space-y-4"},canEfficient&&/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-2"},t('snapshotMode')),/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setMode('standard'),className:`flex-1 px-3 py-2 rounded-lg text-sm border transition-colors ${!isEfficient?'bg-blue-600/20 border-blue-500 text-blue-400':'bg-proxmox-dark border-proxmox-border text-gray-400 hover:border-gray-500'}`},t('normalMode')),/*#__PURE__*/React.createElement("button",{onClick:()=>setMode('efficient'),className:`flex-1 px-3 py-2 rounded-lg text-sm border transition-colors ${isEfficient?'bg-green-600/20 border-green-500 text-green-400':'bg-proxmox-dark border-proxmox-border text-gray-400 hover:border-gray-500'}`},/*#__PURE__*/React.createElement("span",{className:"flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement(Icons.Zap,null),t('efficientMode'))))),isEfficient&&efficientInfo&&/*#__PURE__*/React.createElement("div",{className:"p-3 bg-proxmox-dark rounded-lg border border-green-500/30 space-y-3"},/*#__PURE__*/React.createElement("div",{className:"text-sm font-medium text-green-400 flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.Zap,null),t('spaceSavings'),": ",efficientInfo.savings_percent,"%"),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs text-gray-400 mb-1"},/*#__PURE__*/React.createElement("span",null,t('normalSnapshotSize')),/*#__PURE__*/React.createElement("span",null,efficientInfo.total_disk_size_gb?.toFixed(1)," GB")),/*#__PURE__*/React.createElement("div",{className:"w-full h-3 bg-gray-700 rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"h-full bg-red-500 rounded-full",style:{width:'100%'}}))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs text-gray-400 mb-1"},/*#__PURE__*/React.createElement("span",null,t('efficientSnapshotSize')),/*#__PURE__*/React.createElement("span",null,"~",snapSizeGb?.toFixed(1)," GB")),/*#__PURE__*/React.createElement("div",{className:"w-full h-3 bg-gray-700 rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"h-full bg-green-500 rounded-full",style:{width:`${Math.max(3,snapSizeGb/efficientInfo.total_disk_size_gb*100)}%`}}))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('snapshotSizeGb')," (",t('recommended'),": ",efficientInfo.recommended_snap_size_gb?.toFixed(1)," GB)"),/*#__PURE__*/React.createElement("input",{type:"number",value:snapSizeGb,onChange:e=>setSnapSizeGb(parseFloat(e.target.value)||1),min:"1",max:efficientInfo.vg_free_gb-2,step:"1",className:"w-full px-3 py-1.5 bg-proxmox-card border border-proxmox-border rounded-lg text-white text-sm"})),/*#__PURE__*/React.createElement("div",{className:`text-xs flex items-center gap-1 ${efficientInfo.has_guest_agent?'text-green-400':'text-yellow-400'}`},efficientInfo.has_guest_agent?/*#__PURE__*/React.createElement(Icons.CheckCircle,null):/*#__PURE__*/React.createElement(Icons.AlertTriangle,null),efficientInfo.has_guest_agent?t('guestAgentDetected'):t('noGuestAgent')),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 italic"},t('managedByPegaprox'))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('name')),/*#__PURE__*/React.createElement("input",{type:"text",value:snapname,onChange:e=>setSnapname(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:"snapshot-name"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('description')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"text",value:description,onChange:e=>setDescription(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('snapshotDescription')})),isQemu&&!isEfficient&&/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-2 text-sm text-gray-300"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:vmstate,onChange:e=>setVmstate(e.target.checked),className:"rounded"}),t('saveRamState'))),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-3 mt-6"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-gray-300 hover:text-white"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:handleSubmit,disabled:loading||!snapname.trim(),className:"flex items-center gap-2 px-4 py-2 rounded-lg text-white disabled:opacity-50 bg-green-600 hover:bg-green-700"},loading&&/*#__PURE__*/React.createElement(Icons.RotateCw,null),isEfficient&&/*#__PURE__*/React.createElement(Icons.Zap,null),t('create')))));}// Create Replication Modal function CreateReplicationModal({nodes,onSubmit,onClose,loading}){const{t}=useTranslation();const[target,setTarget]=useState(nodes[0]||'');const[schedule,setSchedule]=useState('*/15');const[rate,setRate]=useState('');const[comment,setComment]=useState('');const scheduleOptions=[{value:'*/5',label:t('every5min')},{value:'*/15',label:t('every15min')},{value:'*/30',label:t('every30min')},{value:'0 *',label:t('hourly')},{value:'0 */2',label:t('every2hours')},{value:'0 */4',label:t('every4hours')},{value:'0 */6',label:t('every6hours')},{value:'0 */12',label:t('every12hours')},{value:'0 0',label:t('daily')}];const handleSubmit=()=>{if(!target)return;onSubmit(target,schedule,rate?parseInt(rate):null,comment);};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-md bg-proxmox-card border border-proxmox-border rounded-xl p-6 animate-scale-in"},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold text-white mb-4"},t('createReplicationJob')),/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetNode')),/*#__PURE__*/React.createElement("select",{value:target,onChange:e=>setTarget(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white"},nodes.map(n=>/*#__PURE__*/React.createElement("option",{key:n,value:n},n)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},"Schedule"),/*#__PURE__*/React.createElement("select",{value:schedule,onChange:e=>setSchedule(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white"},scheduleOptions.map(opt=>/*#__PURE__*/React.createElement("option",{key:opt.value,value:opt.value},opt.label)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('rateLimit')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"number",value:rate,onChange:e=>setRate(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('unlimited'),min:"1"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('comment')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"text",value:comment,onChange:e=>setComment(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('commentPlaceholder')}))),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-3 mt-6"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-gray-300 hover:text-white"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:handleSubmit,disabled:loading||!target,className:"flex items-center gap-2 px-4 py-2 bg-green-600 rounded-lg text-white hover:bg-green-700 disabled:opacity-50"},loading&&/*#__PURE__*/React.createElement(Icons.RotateCw,null),t('create')))));}// Shared form components (defined outside ConfigModal to prevent re-creation) // ns: could use a form library but this is fine for now @@ -4200,7 +4205,7 @@ const allDs=[...(clusterDatastores&&clusterDatastores.shared||[])];Object.values(clusterDatastores&&clusterDatastores.local||{}).forEach(nodeDs=>{(nodeDs||[]).forEach(d=>allDs.push(d));});let totalStorage=allDs.reduce((s,d)=>s+(d.total||0),0);let usedStorage=allDs.reduce((s,d)=>s+(d.used||0),0);// fallback to node disk metrics if datastores haven't loaded if(totalStorage===0&&metrics.length>0){totalStorage=metrics.reduce((s,m)=>s+(m.disk_total||0),0);usedStorage=metrics.reduce((s,m)=>s+(m.disk_used||0),0);}const storagePct=totalStorage>0?usedStorage/totalStorage*100:0;const allVms=(clusterResources||[]).filter(r=>r.type==='qemu'||r.type==='lxc');const runningVms=allVms.filter(r=>r.status==='running').length;const fmtB=b=>{if(!b)return'0';const g=b/1073741824;return g>=1?`${g.toFixed(1)} GB`:`${(b/1048576).toFixed(0)} MB`;};const threshColor=pct=>pct>80?'#f54f47':pct>60?'#efc006':'#60b515';// inline donut const Donut=({value,color,sz})=>{const r=(sz-6)/2,c=r*2*Math.PI,off=c-value/100*c;return/*#__PURE__*/React.createElement("svg",{width:sz,height:sz,style:{transform:'rotate(-90deg)',flexShrink:0}},/*#__PURE__*/React.createElement("circle",{cx:sz/2,cy:sz/2,r:r,fill:"none",className:"corp-donut-track",strokeWidth:6}),/*#__PURE__*/React.createElement("circle",{cx:sz/2,cy:sz/2,r:r,fill:"none",stroke:color,strokeWidth:6,strokeLinecap:"round",strokeDasharray:c,strokeDashoffset:off,className:"corp-donut-fill"}));};return/*#__PURE__*/React.createElement("div",{className:"corp-overview-grid"},/*#__PURE__*/React.createElement("div",{className:"corp-overview-card"},/*#__PURE__*/React.createElement(Donut,{value:avgCpu,color:threshColor(avgCpu),sz:48}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"corp-overview-label"},t('clusterCpu')),/*#__PURE__*/React.createElement("div",{className:"corp-overview-value"},avgCpu.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"corp-overview-sub"},metrics.length," ",t('nodes').toLowerCase()))),/*#__PURE__*/React.createElement("div",{className:"corp-overview-card"},/*#__PURE__*/React.createElement(Donut,{value:memPct,color:threshColor(memPct),sz:48}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"corp-overview-label"},t('clusterMemory')),/*#__PURE__*/React.createElement("div",{className:"corp-overview-value"},memPct.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"corp-overview-sub"},fmtB(usedMem)," / ",fmtB(totalMem)))),/*#__PURE__*/React.createElement("div",{className:"corp-overview-card"},/*#__PURE__*/React.createElement(Donut,{value:storagePct,color:threshColor(storagePct),sz:48}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"corp-overview-label"},t('clusterStorage')),/*#__PURE__*/React.createElement("div",{className:"corp-overview-value"},storagePct.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"corp-overview-sub"},fmtB(usedStorage)," / ",fmtB(totalStorage)))),/*#__PURE__*/React.createElement("div",{className:"corp-overview-card"},/*#__PURE__*/React.createElement("div",{style:{width:48,height:48,display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}},/*#__PURE__*/React.createElement(Icons.Monitor,{className:"w-6 h-6",style:{color:'var(--corp-accent)'}})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"corp-overview-label"},"VMs / CTs"),/*#__PURE__*/React.createElement("div",{className:"corp-overview-value"},runningVms," ",/*#__PURE__*/React.createElement("span",{style:{fontSize:13,fontWeight:400,color:'var(--corp-text-muted)'}},"/ ",allVms.length)),/*#__PURE__*/React.createElement("div",{className:"corp-overview-sub"},runningVms," ",t('runningOf')," ",allVms.length))));})(),isCorporate&&selectedCluster&&(()=>{const onlineCount=Object.values(clusterMetrics).filter(m=>m&&m.status!=='offline').length;const totalNodes=Object.keys(clusterMetrics).length+Object.entries(knownNodes).filter(([n,d])=>d.status==='offline'&&!clusterMetrics[n]).length;const haNodes=Object.values(clusterMetrics).filter(m=>m&&m.ha_active).length;// #252: use quorate from PVE API (accounts for QDevice votes) -const clusterStatus=allClusterMetrics[selectedCluster.id]?.data?.cluster;const isQuorate=clusterStatus?.quorate!==undefined?clusterStatus.quorate:onlineCount>(totalNodes||onlineCount)/2;return/*#__PURE__*/React.createElement("div",{className:"corp-services-bar"},/*#__PURE__*/React.createElement("div",{className:"corp-svc-item"},/*#__PURE__*/React.createElement("span",{className:"corp-svc-label"},t('nodes')),/*#__PURE__*/React.createElement("span",{style:{color:onlineCount===totalNodes?'var(--color-success)':'var(--color-warning)'}},onlineCount,"/",totalNodes||onlineCount),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-muted)'}},t('online').toLowerCase())),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)'}},"|"),/*#__PURE__*/React.createElement("div",{className:"corp-svc-item"},/*#__PURE__*/React.createElement("span",{className:"corp-svc-label"},"Quorum"),/*#__PURE__*/React.createElement("span",{className:"corp-badge",style:isQuorate?{background:'rgba(96,181,21,0.12)',color:'#60b515',border:'1px solid rgba(96,181,21,0.25)',fontSize:11,padding:'0 5px'}:{background:'rgba(245,79,71,0.12)',color:'#f54f47',border:'1px solid rgba(245,79,71,0.25)',fontSize:11,padding:'0 5px'}},isQuorate?t('quorumOk'):t('quorumLost'))),haNodes>0&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)'}},"|"),/*#__PURE__*/React.createElement("div",{className:"corp-svc-item"},/*#__PURE__*/React.createElement("span",{className:"corp-svc-label"},"HA"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-success)'}},t('haEnabled')),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-muted)'}},"(",haNodes,")"))));})(),/*#__PURE__*/React.createElement("div",{className:isCorporate?'':'xl:col-span-2 space-y-6'},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:`font-semibold text-gray-400 uppercase tracking-wider ${isCorporate?'text-xs mb-2':'text-sm mb-4'}`},t('nodes')),isCorporate?/*#__PURE__*/React.createElement("div",{className:"border border-proxmox-border bg-proxmox-card"},Object.entries(clusterMetrics).sort(([a],[b])=>a.localeCompare(b)).map(([node,metrics])=>/*#__PURE__*/React.createElement(NodeCompactRow,{key:node,name:node,metrics:metrics,clusterId:selectedCluster.id,onOpenNodeConfig:nodeName=>setConfigNode(nodeName),onMaintenanceToggle:handleMaintenanceToggle,onStartUpdate:handleStartUpdate,onNodeAction:handleNodeAction,onRemoveNode:nodeName=>{setNodeToRemoveDash({name:nodeName});setShowRemoveNodeDash(true);},onMoveNode:nodeName=>{setNodeToMoveDash(nodeName);setShowMoveNodeDash(true);}})),Object.entries(knownNodes).filter(([nodeName,nodeData])=>nodeData.status==='offline'&&!clusterMetrics[nodeName]).map(([nodeName])=>/*#__PURE__*/React.createElement(NodeCompactRow,{key:`offline-${nodeName}`,name:nodeName,metrics:null,clusterId:selectedCluster.id})),Object.entries(nodeAlerts).filter(([nodeName,alert])=>alert.cluster_id===selectedCluster.id&&!clusterMetrics[nodeName]&&!knownNodes[nodeName]?.status).map(([nodeName])=>/*#__PURE__*/React.createElement(NodeCompactRow,{key:`alert-${nodeName}`,name:nodeName,metrics:null,clusterId:selectedCluster.id}))):/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4"},Object.entries(clusterMetrics).sort(([a],[b])=>a.localeCompare(b)).map(([node,metrics],idx)=>/*#__PURE__*/React.createElement(NodeCard,{key:node,name:node,metrics:metrics,index:idx,clusterId:selectedCluster.id,onMaintenanceToggle:handleMaintenanceToggle,onStartUpdate:handleStartUpdate,onOpenNodeConfig:nodeName=>setConfigNode(nodeName),onNodeAction:handleNodeAction,onRemoveNode:nodeName=>{setNodeToRemoveDash({name:nodeName});setShowRemoveNodeDash(true);},onMoveNode:nodeName=>{setNodeToMoveDash(nodeName);setShowMoveNodeDash(true);}})),Object.entries(knownNodes).filter(([nodeName,nodeData])=>nodeData.status==='offline'&&!clusterMetrics[nodeName]).map(([nodeName,nodeData],idx)=>/*#__PURE__*/React.createElement("div",{key:`offline-${nodeName}`,className:"relative bg-proxmox-card border-2 border-red-500/50 rounded-xl p-4"},/*#__PURE__*/React.createElement("div",{className:"absolute top-2 right-2"},/*#__PURE__*/React.createElement("span",{className:"px-2 py-1 bg-red-500 text-white text-xs font-bold rounded animate-pulse"},"OFFLINE")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 mb-4"},/*#__PURE__*/React.createElement("div",{className:"p-2 bg-red-500/20 rounded-lg"},/*#__PURE__*/React.createElement(Icons.Server,{className:"text-red-400"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white"},nodeName),/*#__PURE__*/React.createElement("p",{className:"text-xs text-red-400"},t('nodeUnreachable')||'Node unreachable'))),/*#__PURE__*/React.createElement("div",{className:"space-y-3 opacity-50"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"CPU"),/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"--")),/*#__PURE__*/React.createElement("div",{className:"h-2 bg-proxmox-dark rounded-full"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"RAM"),/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"--")),/*#__PURE__*/React.createElement("div",{className:"h-2 bg-proxmox-dark rounded-full"}))),/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-3 border-t border-red-500/30 text-xs text-red-400"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"inline w-3 h-3 mr-1"}),t('offlineSince')||'Offline since',": ",nodeData.offlineSince?new Date(nodeData.offlineSince).toLocaleTimeString():'Unknown'),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 mt-2"},t('lastSeen')||'Last seen',": ",nodeData.lastSeen?new Date(nodeData.lastSeen).toLocaleString():'Unknown'))),Object.entries(nodeAlerts).filter(([nodeName,alert])=>alert.cluster_id===selectedCluster.id&&!clusterMetrics[nodeName]&&!knownNodes[nodeName]?.status).map(([nodeName,alert],idx)=>/*#__PURE__*/React.createElement("div",{key:`alert-${nodeName}`,className:"relative bg-proxmox-card border-2 border-red-500/50 rounded-xl p-4 animate-pulse"},/*#__PURE__*/React.createElement("div",{className:"absolute top-2 right-2"},/*#__PURE__*/React.createElement("span",{className:"px-2 py-1 bg-red-500 text-white text-xs font-bold rounded animate-pulse"},"OFFLINE")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 mb-4"},/*#__PURE__*/React.createElement("div",{className:"p-2 bg-red-500/20 rounded-lg"},/*#__PURE__*/React.createElement(Icons.Server,{className:"text-red-400"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white"},nodeName),/*#__PURE__*/React.createElement("p",{className:"text-xs text-red-400"},t('nodeUnreachable')||'Node unreachable'))),/*#__PURE__*/React.createElement("div",{className:"space-y-3 opacity-50"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"CPU"),/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"--")),/*#__PURE__*/React.createElement("div",{className:"h-2 bg-proxmox-dark rounded-full"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"RAM"),/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"--")),/*#__PURE__*/React.createElement("div",{className:"h-2 bg-proxmox-dark rounded-full"}))),/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-3 border-t border-red-500/30 text-xs text-red-400"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"inline w-3 h-3 mr-1"}),t('offlineSince')||'Offline since',": ",new Date(alert.timestamp).toLocaleTimeString())))),Object.keys(clusterMetrics).length===0&&Object.keys(nodeAlerts).length===0&&/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl p-8 text-center"},connectionError?/*#__PURE__*/React.createElement("div",{className:"text-red-400"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"mx-auto mb-2"}),/*#__PURE__*/React.createElement("p",{className:"font-medium"},t('connectionError')||'Connection Error'),/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-500 mt-1"},connectionError),/*#__PURE__*/React.createElement("button",{onClick:()=>{setConnectionError(null);fetchClusterMetrics(selectedCluster.id);},className:"mt-3 px-4 py-2 bg-proxmox-orange hover:bg-orange-600 rounded-lg text-sm text-white"},t('retry')||'Retry')):/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement(Icons.RotateCw,{className:"mx-auto mb-2 animate-spin text-gray-500"}),/*#__PURE__*/React.createElement("p",{className:"text-gray-500"},t('loadingMetrics')||'Loading metrics...'))))),isCorporate&&selectedCluster&&/*#__PURE__*/React.createElement("div",{className:"corp-overview-lower"},/*#__PURE__*/React.createElement("div",{style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)',padding:'10px 12px'}},/*#__PURE__*/React.createElement("h3",{className:"text-[11px] font-semibold uppercase tracking-wider mb-2",style:{color:'var(--corp-text-muted)'}},t('topConsumers')),(()=>{const running=(clusterResources||[]).filter(r=>(r.type==='qemu'||r.type==='lxc')&&r.status==='running');const topCpu=[...running].sort((a,b)=>(b.cpu_percent||0)-(a.cpu_percent||0)).slice(0,5);const topMem=[...running].sort((a,b)=>(b.mem||0)-(a.mem||0)).slice(0,5);const fmtMem=b=>{if(!b)return'0';const g=b/1073741824;return g>=1?`${g.toFixed(1)}G`:`${(b/1048576).toFixed(0)}M`;};const cpuColor=v=>v>80?'#f54f47':v>60?'#efc006':'#49afd9';if(running.length===0)return/*#__PURE__*/React.createElement("div",{className:"text-[12px]",style:{color:'var(--corp-text-muted)'}},"-");return/*#__PURE__*/React.createElement("div",{style:{display:'grid',gridTemplateColumns:'1fr 1fr',gap:'0 16px'}},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[10px] uppercase mb-1",style:{color:'var(--corp-text-muted)',letterSpacing:'0.05em'}},t('topCpuConsumers')),/*#__PURE__*/React.createElement("div",{className:"corp-consumers-list"},topCpu.map((vm,i)=>/*#__PURE__*/React.createElement("div",{key:vm.vmid,className:"corp-consumer-row"},/*#__PURE__*/React.createElement("span",{className:"corp-consumer-rank"},i+1,"."),/*#__PURE__*/React.createElement("span",{className:"corp-consumer-name",onClick:()=>{setActiveTab('resources');setResourcesSubTab('management');setTimeout(()=>setHighlightedVm(vm),100);},title:`${vm.name||vm.vmid} (${vm.node})`},vm.name||`VM ${vm.vmid}`),/*#__PURE__*/React.createElement("div",{className:"corp-consumer-bar"},/*#__PURE__*/React.createElement("div",{className:"corp-consumer-bar-fill",style:{width:`${Math.min(vm.cpu_percent||0,100)}%`,background:cpuColor(vm.cpu_percent||0)}})),/*#__PURE__*/React.createElement("span",{className:"corp-consumer-val"},(vm.cpu_percent||0).toFixed(1),"%"))))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[10px] uppercase mb-1",style:{color:'var(--corp-text-muted)',letterSpacing:'0.05em'}},t('topMemConsumers')),/*#__PURE__*/React.createElement("div",{className:"corp-consumers-list"},topMem.map((vm,i)=>/*#__PURE__*/React.createElement("div",{key:vm.vmid,className:"corp-consumer-row"},/*#__PURE__*/React.createElement("span",{className:"corp-consumer-rank"},i+1,"."),/*#__PURE__*/React.createElement("span",{className:"corp-consumer-name",onClick:()=>{setActiveTab('resources');setResourcesSubTab('management');setTimeout(()=>setHighlightedVm(vm),100);},title:`${vm.name||vm.vmid} (${vm.node})`},vm.name||`VM ${vm.vmid}`),/*#__PURE__*/React.createElement("div",{className:"corp-consumer-bar"},/*#__PURE__*/React.createElement("div",{className:"corp-consumer-bar-fill",style:{width:`${Math.min(vm.mem/(vm.maxmem||1)*100,100)}%`,background:'#9b59b6'}})),/*#__PURE__*/React.createElement("span",{className:"corp-consumer-val"},fmtMem(vm.mem)))))));})()),/*#__PURE__*/React.createElement("div",{style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)',padding:'10px 12px'}},/*#__PURE__*/React.createElement("h3",{className:"text-[11px] font-semibold uppercase tracking-wider mb-2",style:{color:'var(--corp-text-muted)'}},t('recentTasks')),(()=>{const recent=(tasks||[]).slice(0,8);if(recent.length===0)return/*#__PURE__*/React.createElement("div",{className:"text-[12px]",style:{color:'var(--corp-text-muted)'}},t('noRecentTasks'));const relTime=ts=>{if(!ts)return'';const d=Date.now()/1000-ts;if(d<60)return`${Math.floor(d)}s ${t('timeAgo')}`;if(d<3600)return`${Math.floor(d/60)}m ${t('timeAgo')}`;if(d<86400)return`${Math.floor(d/3600)}h ${t('timeAgo')}`;return`${Math.floor(d/86400)}d ${t('timeAgo')}`;};const statusColor=s=>s==='running'?'#49afd9':s==='OK'||s==='ok'?'#60b515':s==='error'||s==='failed'?'#f54f47':'#728b9a';return/*#__PURE__*/React.createElement("div",null,recent.map((task,i)=>/*#__PURE__*/React.createElement("div",{key:task.upid||i,className:"corp-tasks-item"},/*#__PURE__*/React.createElement("span",{className:"corp-tasks-status",style:{background:statusColor(task.status||task.exitstatus)}}),/*#__PURE__*/React.createElement("span",{className:"corp-tasks-desc"},task.type||task.worker_type||'?',task.id?` ${task.id}`:''),/*#__PURE__*/React.createElement("span",{className:"corp-tasks-node"},task.node||''),/*#__PURE__*/React.createElement("span",{className:"corp-tasks-time"},relTime(task.starttime||task.pstart)))));})())),/*#__PURE__*/React.createElement("div",{className:isCorporate?'space-y-3':'space-y-6'},/*#__PURE__*/React.createElement(ClusterHealth,{metrics:clusterMetrics,isCorporate:isCorporate}),/*#__PURE__*/React.createElement("div",{className:isCorporate?'bg-proxmox-card border border-proxmox-border p-3':'bg-proxmox-card border border-proxmox-border rounded-xl p-5'},/*#__PURE__*/React.createElement("h3",{className:`font-semibold text-gray-400 uppercase tracking-wider ${isCorporate?'text-xs mb-2':'text-sm mb-4'}`},t('lastMigrations')),/*#__PURE__*/React.createElement(MigrationHistory,{logs:migrationLogs}))))),activeTab==='resources'&&/*#__PURE__*/React.createElement("div",{className:isCorporate?'space-y-2':'space-y-4'},isCorporate&&selectedSidebarVm?/*#__PURE__*/React.createElement(CorporateVmDetailView,{vm:selectedSidebarVm,clusterId:selectedSidebarVm._clusterId||selectedCluster.id,onAction:handleVmAction,onOpenConsole:handleOpenConsole,onOpenConfig:handleOpenConfig,onBack:()=>setSelectedSidebarVm(null),onMigrate:vm=>setDashMigrateVm(vm),onClone:vm=>setDashCloneVm(vm),onForceStop:handleForceStop,onDelete:vm=>setDashDeleteVm(vm),onCrossClusterMigrate:vm=>setDashCrossClusterVm(vm),showCrossCluster:clusters.length>1,actionLoading:actionLoading,onShowMetrics:vm=>setCorpMetricsVm(vm),addToast:addToast,authFetch:authFetch}):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:isCorporate?'corp-tab-strip':'flex items-center gap-1 border-b border-proxmox-border pb-2'},/*#__PURE__*/React.createElement("button",{onClick:()=>setResourcesSubTab('management'),className:isCorporate?`flex items-center gap-1 ${resourcesSubTab==='management'?'active':''}`:`flex items-center gap-2 px-4 py-2 rounded-lg text-sm transition-colors ${resourcesSubTab==='management'?'bg-proxmox-orange text-white':'text-gray-400 hover:text-white hover:bg-proxmox-hover'}`},/*#__PURE__*/React.createElement(Icons.Server,{className:isCorporate?'w-3 h-3':'w-4 h-4'}),t('resourcesLabel')||'Resources'),/*#__PURE__*/React.createElement("button",{onClick:()=>{setResourcesSubTab('snapshots');fetchGlobalSnapshots(selectedCluster.id);},className:isCorporate?`flex items-center gap-1 ${resourcesSubTab==='snapshots'?'active':''}`:`flex items-center gap-2 px-4 py-2 rounded-lg text-sm transition-colors ${resourcesSubTab==='snapshots'?'bg-proxmox-orange text-white':'text-gray-400 hover:text-white hover:bg-proxmox-hover'}`},/*#__PURE__*/React.createElement(Icons.Camera,{className:isCorporate?'w-3 h-3':'w-4 h-4'}),t('snapshotsOverview')||'Snapshot Overview'),/*#__PURE__*/React.createElement("button",{onClick:()=>setResourcesSubTab('logs'),className:isCorporate?`flex items-center gap-1 ${resourcesSubTab==='logs'?'active':''}`:`flex items-center gap-2 px-4 py-2 rounded-lg text-sm transition-colors ${resourcesSubTab==='logs'?'bg-proxmox-orange text-white':'text-gray-400 hover:text-white hover:bg-proxmox-hover'}`},/*#__PURE__*/React.createElement(Icons.Terminal,{className:isCorporate?'w-3 h-3':'w-4 h-4'}),t('syslog')||'Log Events'),/*#__PURE__*/React.createElement("button",{onClick:()=>setResourcesSubTab('topology'),className:isCorporate?`flex items-center gap-1 ${resourcesSubTab==='topology'?'active':''}`:`flex items-center gap-2 px-4 py-2 rounded-lg text-sm transition-colors ${resourcesSubTab==='topology'?'bg-proxmox-orange text-white':'text-gray-400 hover:text-white hover:bg-proxmox-hover'}`},/*#__PURE__*/React.createElement(Icons.Network,{className:isCorporate?'w-3 h-3':'w-4 h-4'}),t('topologyView')||'Topology')),resourcesSubTab==='management'&&/*#__PURE__*/React.createElement("div",{className:isCorporate?'p-0':'bg-proxmox-card border border-proxmox-border rounded-xl p-6'},/*#__PURE__*/React.createElement(ResourceTable,{resources:clusterResources,clusterId:selectedCluster.id,clusters:clusters,sourceCluster:selectedCluster,onVmAction:handleVmAction,onOpenConsole:handleOpenConsole,onOpenConfig:handleOpenConfig,onMigrate:handleMigrate,onBulkMigrate:handleBulkMigrate,onDelete:handleDeleteVm,onClone:handleCloneVm,onForceStop:handleForceStop,onCrossClusterMigrate:handleCrossClusterMigrate,nodes:Object.keys(clusterMetrics),onOpenTags:resource=>{loadVmTags(selectedCluster.id,resource.vmid);loadClusterTags(selectedCluster.id);setShowTagEditor({clusterId:selectedCluster.id,vmid:resource.vmid,vmName:resource.name||`VM ${resource.vmid}`});},highlightedVm:highlightedVm,addToast:addToast,pendingVmAction:pendingVmAction,onPendingActionConsumed:()=>setPendingVmAction(null),onVmNavigate:isCorporate?vm=>{setSelectedSidebarVm({...vm,_clusterId:selectedCluster.id});setSelectedSidebarNode(null);setSelectedSidebarDatastore(null);// expand cluster tree so user sees where the VM is +const clusterStatus=allClusterMetrics[selectedCluster.id]?.data?.cluster;const isQuorate=clusterStatus?.quorate!==undefined?clusterStatus.quorate:onlineCount>(totalNodes||onlineCount)/2;return/*#__PURE__*/React.createElement("div",{className:"corp-services-bar"},/*#__PURE__*/React.createElement("div",{className:"corp-svc-item"},/*#__PURE__*/React.createElement("span",{className:"corp-svc-label"},t('nodes')),/*#__PURE__*/React.createElement("span",{style:{color:onlineCount===totalNodes?'var(--color-success)':'var(--color-warning)'}},onlineCount,"/",totalNodes||onlineCount),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-muted)'}},t('online').toLowerCase())),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)'}},"|"),/*#__PURE__*/React.createElement("div",{className:"corp-svc-item"},/*#__PURE__*/React.createElement("span",{className:"corp-svc-label"},"Quorum"),/*#__PURE__*/React.createElement("span",{className:"corp-badge",style:isQuorate?{background:'rgba(96,181,21,0.12)',color:'#60b515',border:'1px solid rgba(96,181,21,0.25)',fontSize:11,padding:'0 5px'}:{background:'rgba(245,79,71,0.12)',color:'#f54f47',border:'1px solid rgba(245,79,71,0.25)',fontSize:11,padding:'0 5px'}},isQuorate?t('quorumOk'):t('quorumLost'))),haNodes>0&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-divider)'}},"|"),/*#__PURE__*/React.createElement("div",{className:"corp-svc-item"},/*#__PURE__*/React.createElement("span",{className:"corp-svc-label"},"HA"),/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-success)'}},t('haEnabled')),/*#__PURE__*/React.createElement("span",{style:{color:'var(--corp-text-muted)'}},"(",haNodes,")"))));})(),/*#__PURE__*/React.createElement("div",{className:isCorporate?'':'xl:col-span-2 space-y-6'},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:`font-semibold text-gray-400 uppercase tracking-wider ${isCorporate?'text-xs mb-2':'text-sm mb-4'}`},t('nodes')),isCorporate?/*#__PURE__*/React.createElement("div",{className:"border border-proxmox-border bg-proxmox-card"},Object.entries(clusterMetrics).sort(([a],[b])=>a.localeCompare(b)).map(([node,metrics])=>/*#__PURE__*/React.createElement(NodeCompactRow,{key:node,name:node,metrics:metrics,clusterId:selectedCluster.id,onOpenNodeConfig:nodeName=>setConfigNode(nodeName),onMaintenanceToggle:handleMaintenanceToggle,onStartUpdate:handleStartUpdate,onNodeAction:handleNodeAction,onRemoveNode:nodeName=>{setNodeToRemoveDash({name:nodeName});setShowRemoveNodeDash(true);},onMoveNode:nodeName=>{setNodeToMoveDash(nodeName);setShowMoveNodeDash(true);}})),Object.entries(knownNodes).filter(([nodeName,nodeData])=>nodeData.status==='offline'&&!clusterMetrics[nodeName]).map(([nodeName])=>/*#__PURE__*/React.createElement(NodeCompactRow,{key:`offline-${nodeName}`,name:nodeName,metrics:null,clusterId:selectedCluster.id})),Object.entries(nodeAlerts).filter(([nodeName,alert])=>alert.cluster_id===selectedCluster.id&&!clusterMetrics[nodeName]&&!knownNodes[nodeName]?.status).map(([nodeName])=>/*#__PURE__*/React.createElement(NodeCompactRow,{key:`alert-${nodeName}`,name:nodeName,metrics:null,clusterId:selectedCluster.id}))):/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4"},Object.entries(clusterMetrics).sort(([a],[b])=>a.localeCompare(b)).map(([node,metrics],idx)=>/*#__PURE__*/React.createElement(NodeCard,{key:node,name:node,metrics:metrics,index:idx,clusterId:selectedCluster.id,onMaintenanceToggle:handleMaintenanceToggle,onStartUpdate:handleStartUpdate,onOpenNodeConfig:nodeName=>setConfigNode(nodeName),onNodeAction:handleNodeAction,onRemoveNode:nodeName=>{setNodeToRemoveDash({name:nodeName});setShowRemoveNodeDash(true);},onMoveNode:nodeName=>{setNodeToMoveDash(nodeName);setShowMoveNodeDash(true);}})),Object.entries(knownNodes).filter(([nodeName,nodeData])=>nodeData.status==='offline'&&!clusterMetrics[nodeName]).map(([nodeName,nodeData],idx)=>/*#__PURE__*/React.createElement("div",{key:`offline-${nodeName}`,className:"relative bg-proxmox-card border-2 border-red-500/50 rounded-xl p-4"},/*#__PURE__*/React.createElement("div",{className:"absolute top-2 right-2"},/*#__PURE__*/React.createElement("span",{className:"px-2 py-1 bg-red-500 text-white text-xs font-bold rounded animate-pulse"},"OFFLINE")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 mb-4"},/*#__PURE__*/React.createElement("div",{className:"p-2 bg-red-500/20 rounded-lg"},/*#__PURE__*/React.createElement(Icons.Server,{className:"text-red-400"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white"},nodeName),/*#__PURE__*/React.createElement("p",{className:"text-xs text-red-400"},t('nodeUnreachable')||'Node unreachable'))),/*#__PURE__*/React.createElement("div",{className:"space-y-3 opacity-50"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"CPU"),/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"--")),/*#__PURE__*/React.createElement("div",{className:"h-2 bg-proxmox-dark rounded-full"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"RAM"),/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"--")),/*#__PURE__*/React.createElement("div",{className:"h-2 bg-proxmox-dark rounded-full"}))),/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-3 border-t border-red-500/30 text-xs text-red-400"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"inline w-3 h-3 mr-1"}),t('offlineSince')||'Offline since',": ",nodeData.offlineSince?new Date(nodeData.offlineSince).toLocaleTimeString():'Unknown'),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 mt-2"},t('lastSeen')||'Last seen',": ",nodeData.lastSeen?new Date(nodeData.lastSeen).toLocaleString():'Unknown'))),Object.entries(nodeAlerts).filter(([nodeName,alert])=>alert.cluster_id===selectedCluster.id&&!clusterMetrics[nodeName]&&!knownNodes[nodeName]?.status).map(([nodeName,alert],idx)=>/*#__PURE__*/React.createElement("div",{key:`alert-${nodeName}`,className:"relative bg-proxmox-card border-2 border-red-500/50 rounded-xl p-4 animate-pulse"},/*#__PURE__*/React.createElement("div",{className:"absolute top-2 right-2"},/*#__PURE__*/React.createElement("span",{className:"px-2 py-1 bg-red-500 text-white text-xs font-bold rounded animate-pulse"},"OFFLINE")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 mb-4"},/*#__PURE__*/React.createElement("div",{className:"p-2 bg-red-500/20 rounded-lg"},/*#__PURE__*/React.createElement(Icons.Server,{className:"text-red-400"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"font-semibold text-white"},nodeName),/*#__PURE__*/React.createElement("p",{className:"text-xs text-red-400"},t('nodeUnreachable')||'Node unreachable'))),/*#__PURE__*/React.createElement("div",{className:"space-y-3 opacity-50"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"CPU"),/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"--")),/*#__PURE__*/React.createElement("div",{className:"h-2 bg-proxmox-dark rounded-full"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"RAM"),/*#__PURE__*/React.createElement("span",{className:"text-gray-500"},"--")),/*#__PURE__*/React.createElement("div",{className:"h-2 bg-proxmox-dark rounded-full"}))),/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-3 border-t border-red-500/30 text-xs text-red-400"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"inline w-3 h-3 mr-1"}),t('offlineSince')||'Offline since',": ",new Date(alert.timestamp).toLocaleTimeString())))),Object.keys(clusterMetrics).length===0&&Object.keys(nodeAlerts).length===0&&/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl p-8 text-center"},connectionError?/*#__PURE__*/React.createElement("div",{className:"text-red-400"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"mx-auto mb-2"}),/*#__PURE__*/React.createElement("p",{className:"font-medium"},t('connectionError')||'Connection Error'),/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-500 mt-1"},connectionError),/*#__PURE__*/React.createElement("button",{onClick:()=>{setConnectionError(null);fetchClusterMetrics(selectedCluster.id);},className:"mt-3 px-4 py-2 bg-proxmox-orange hover:bg-orange-600 rounded-lg text-sm text-white"},t('retry')||'Retry')):/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement(Icons.RotateCw,{className:"mx-auto mb-2 animate-spin text-gray-500"}),/*#__PURE__*/React.createElement("p",{className:"text-gray-500"},t('loadingMetrics')||'Loading metrics...'))))),isCorporate&&selectedCluster&&/*#__PURE__*/React.createElement("div",{className:"corp-overview-lower"},/*#__PURE__*/React.createElement("div",{style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)',padding:'10px 12px'}},/*#__PURE__*/React.createElement("h3",{className:"text-[11px] font-semibold uppercase tracking-wider mb-2",style:{color:'var(--corp-text-muted)'}},t('topConsumers')),(()=>{const running=(clusterResources||[]).filter(r=>(r.type==='qemu'||r.type==='lxc')&&r.status==='running');const topCpu=[...running].sort((a,b)=>(b.cpu_percent||0)-(a.cpu_percent||0)).slice(0,5);const topMem=[...running].sort((a,b)=>(b.mem||0)-(a.mem||0)).slice(0,5);const fmtMem=b=>{if(!b)return'0';const g=b/1073741824;return g>=1?`${g.toFixed(1)}G`:`${(b/1048576).toFixed(0)}M`;};const cpuColor=v=>v>80?'#f54f47':v>60?'#efc006':'#49afd9';if(running.length===0)return/*#__PURE__*/React.createElement("div",{className:"text-[12px]",style:{color:'var(--corp-text-muted)'}},"-");return/*#__PURE__*/React.createElement("div",{style:{display:'grid',gridTemplateColumns:'1fr 1fr',gap:'0 16px'}},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[10px] uppercase mb-1",style:{color:'var(--corp-text-muted)',letterSpacing:'0.05em'}},t('topCpuConsumers')),/*#__PURE__*/React.createElement("div",{className:"corp-consumers-list"},topCpu.map((vm,i)=>/*#__PURE__*/React.createElement("div",{key:vm.vmid,className:"corp-consumer-row"},/*#__PURE__*/React.createElement("span",{className:"corp-consumer-rank"},i+1,"."),/*#__PURE__*/React.createElement("span",{className:"corp-consumer-name",onClick:()=>{setActiveTab('resources');setResourcesSubTab('management');setTimeout(()=>setHighlightedVm(vm),100);},title:`${vm.name||vm.vmid} (${vm.node})`},vm.name||`VM ${vm.vmid}`),/*#__PURE__*/React.createElement("div",{className:"corp-consumer-bar"},/*#__PURE__*/React.createElement("div",{className:"corp-consumer-bar-fill",style:{width:`${Math.min(vm.cpu_percent||0,100)}%`,background:cpuColor(vm.cpu_percent||0)}})),/*#__PURE__*/React.createElement("span",{className:"corp-consumer-val"},(vm.cpu_percent||0).toFixed(1),"%"))))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[10px] uppercase mb-1",style:{color:'var(--corp-text-muted)',letterSpacing:'0.05em'}},t('topMemConsumers')),/*#__PURE__*/React.createElement("div",{className:"corp-consumers-list"},topMem.map((vm,i)=>/*#__PURE__*/React.createElement("div",{key:vm.vmid,className:"corp-consumer-row"},/*#__PURE__*/React.createElement("span",{className:"corp-consumer-rank"},i+1,"."),/*#__PURE__*/React.createElement("span",{className:"corp-consumer-name",onClick:()=>{setActiveTab('resources');setResourcesSubTab('management');setTimeout(()=>setHighlightedVm(vm),100);},title:`${vm.name||vm.vmid} (${vm.node})`},vm.name||`VM ${vm.vmid}`),/*#__PURE__*/React.createElement("div",{className:"corp-consumer-bar"},/*#__PURE__*/React.createElement("div",{className:"corp-consumer-bar-fill",style:{width:`${Math.min(vm.mem/(vm.maxmem||1)*100,100)}%`,background:'#9b59b6'}})),/*#__PURE__*/React.createElement("span",{className:"corp-consumer-val"},fmtMem(vm.mem)))))));})()),/*#__PURE__*/React.createElement("div",{style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)',padding:'10px 12px'}},/*#__PURE__*/React.createElement("h3",{className:"text-[11px] font-semibold uppercase tracking-wider mb-2",style:{color:'var(--corp-text-muted)'}},t('recentTasks')),(()=>{const recent=(tasks||[]).slice(0,8);if(recent.length===0)return/*#__PURE__*/React.createElement("div",{className:"text-[12px]",style:{color:'var(--corp-text-muted)'}},t('noRecentTasks'));const relTime=ts=>{if(!ts)return'';const d=Date.now()/1000-ts;if(d<60)return`${Math.floor(d)}s ${t('timeAgo')}`;if(d<3600)return`${Math.floor(d/60)}m ${t('timeAgo')}`;if(d<86400)return`${Math.floor(d/3600)}h ${t('timeAgo')}`;return`${Math.floor(d/86400)}d ${t('timeAgo')}`;};const statusColor=s=>s==='running'?'#49afd9':s==='OK'||s==='ok'?'#60b515':s==='error'||s==='failed'?'#f54f47':'#728b9a';return/*#__PURE__*/React.createElement("div",null,recent.map((task,i)=>/*#__PURE__*/React.createElement("div",{key:task.upid||i,className:"corp-tasks-item"},/*#__PURE__*/React.createElement("span",{className:"corp-tasks-status",style:{background:statusColor(task.status||task.exitstatus)}}),/*#__PURE__*/React.createElement("span",{className:"corp-tasks-desc"},task.type||task.worker_type||'?',task.id?` ${task.id}`:''),/*#__PURE__*/React.createElement("span",{className:"corp-tasks-node"},task.node||''),/*#__PURE__*/React.createElement("span",{className:"corp-tasks-time"},relTime(task.starttime||task.pstart)))));})())),/*#__PURE__*/React.createElement("div",{className:isCorporate?'space-y-3':'space-y-6'},/*#__PURE__*/React.createElement(ClusterHealth,{metrics:clusterMetrics,clusterStatus:allClusterMetrics[selectedCluster?.id]?.data,isCorporate:isCorporate}),/*#__PURE__*/React.createElement("div",{className:isCorporate?'bg-proxmox-card border border-proxmox-border p-3':'bg-proxmox-card border border-proxmox-border rounded-xl p-5'},/*#__PURE__*/React.createElement("h3",{className:`font-semibold text-gray-400 uppercase tracking-wider ${isCorporate?'text-xs mb-2':'text-sm mb-4'}`},t('lastMigrations')),/*#__PURE__*/React.createElement(MigrationHistory,{logs:migrationLogs}))))),activeTab==='resources'&&/*#__PURE__*/React.createElement("div",{className:isCorporate?'space-y-2':'space-y-4'},isCorporate&&selectedSidebarVm?/*#__PURE__*/React.createElement(CorporateVmDetailView,{vm:selectedSidebarVm,clusterId:selectedSidebarVm._clusterId||selectedCluster.id,onAction:handleVmAction,onOpenConsole:handleOpenConsole,onOpenConfig:handleOpenConfig,onBack:()=>setSelectedSidebarVm(null),onMigrate:vm=>setDashMigrateVm(vm),onClone:vm=>setDashCloneVm(vm),onForceStop:handleForceStop,onDelete:vm=>setDashDeleteVm(vm),onCrossClusterMigrate:vm=>setDashCrossClusterVm(vm),showCrossCluster:clusters.length>1,actionLoading:actionLoading,onShowMetrics:vm=>setCorpMetricsVm(vm),addToast:addToast,authFetch:authFetch}):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:isCorporate?'corp-tab-strip':'flex items-center gap-1 border-b border-proxmox-border pb-2'},/*#__PURE__*/React.createElement("button",{onClick:()=>setResourcesSubTab('management'),className:isCorporate?`flex items-center gap-1 ${resourcesSubTab==='management'?'active':''}`:`flex items-center gap-2 px-4 py-2 rounded-lg text-sm transition-colors ${resourcesSubTab==='management'?'bg-proxmox-orange text-white':'text-gray-400 hover:text-white hover:bg-proxmox-hover'}`},/*#__PURE__*/React.createElement(Icons.Server,{className:isCorporate?'w-3 h-3':'w-4 h-4'}),t('resourcesLabel')||'Resources'),/*#__PURE__*/React.createElement("button",{onClick:()=>{setResourcesSubTab('snapshots');fetchGlobalSnapshots(selectedCluster.id);},className:isCorporate?`flex items-center gap-1 ${resourcesSubTab==='snapshots'?'active':''}`:`flex items-center gap-2 px-4 py-2 rounded-lg text-sm transition-colors ${resourcesSubTab==='snapshots'?'bg-proxmox-orange text-white':'text-gray-400 hover:text-white hover:bg-proxmox-hover'}`},/*#__PURE__*/React.createElement(Icons.Camera,{className:isCorporate?'w-3 h-3':'w-4 h-4'}),t('snapshotsOverview')||'Snapshot Overview'),/*#__PURE__*/React.createElement("button",{onClick:()=>setResourcesSubTab('logs'),className:isCorporate?`flex items-center gap-1 ${resourcesSubTab==='logs'?'active':''}`:`flex items-center gap-2 px-4 py-2 rounded-lg text-sm transition-colors ${resourcesSubTab==='logs'?'bg-proxmox-orange text-white':'text-gray-400 hover:text-white hover:bg-proxmox-hover'}`},/*#__PURE__*/React.createElement(Icons.Terminal,{className:isCorporate?'w-3 h-3':'w-4 h-4'}),t('syslog')||'Log Events'),/*#__PURE__*/React.createElement("button",{onClick:()=>setResourcesSubTab('topology'),className:isCorporate?`flex items-center gap-1 ${resourcesSubTab==='topology'?'active':''}`:`flex items-center gap-2 px-4 py-2 rounded-lg text-sm transition-colors ${resourcesSubTab==='topology'?'bg-proxmox-orange text-white':'text-gray-400 hover:text-white hover:bg-proxmox-hover'}`},/*#__PURE__*/React.createElement(Icons.Network,{className:isCorporate?'w-3 h-3':'w-4 h-4'}),t('topologyView')||'Topology')),resourcesSubTab==='management'&&/*#__PURE__*/React.createElement("div",{className:isCorporate?'p-0':'bg-proxmox-card border border-proxmox-border rounded-xl p-6'},/*#__PURE__*/React.createElement(ResourceTable,{resources:clusterResources,clusterId:selectedCluster.id,clusters:clusters,sourceCluster:selectedCluster,onVmAction:handleVmAction,onOpenConsole:handleOpenConsole,onOpenConfig:handleOpenConfig,onMigrate:handleMigrate,onBulkMigrate:handleBulkMigrate,onDelete:handleDeleteVm,onClone:handleCloneVm,onForceStop:handleForceStop,onCrossClusterMigrate:handleCrossClusterMigrate,nodes:Object.keys(clusterMetrics),onOpenTags:resource=>{loadVmTags(selectedCluster.id,resource.vmid);loadClusterTags(selectedCluster.id);setShowTagEditor({clusterId:selectedCluster.id,vmid:resource.vmid,vmName:resource.name||`VM ${resource.vmid}`});},highlightedVm:highlightedVm,addToast:addToast,pendingVmAction:pendingVmAction,onPendingActionConsumed:()=>setPendingVmAction(null),onVmNavigate:isCorporate?vm=>{setSelectedSidebarVm({...vm,_clusterId:selectedCluster.id});setSelectedSidebarNode(null);setSelectedSidebarDatastore(null);// expand cluster tree so user sees where the VM is setExpandedSidebarClusters(prev=>({...prev,[selectedCluster.id]:true}));}:undefined}),/*#__PURE__*/React.createElement("div",{className:`flex gap-3 ${isCorporate?'mt-2':'mt-4'}`},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowCreateVm('qemu'),className:isCorporate?'flex items-center gap-1.5 px-3 py-1.5 bg-blue-600 text-[13px] text-white hover:bg-blue-700 border border-blue-700':'flex items-center gap-2 px-4 py-2 bg-blue-600 rounded-lg text-white hover:bg-blue-700 transition-colors'},/*#__PURE__*/React.createElement(Icons.Plus,{className:isCorporate?'w-3 h-3':''}),t('createVm')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowCreateVm('lxc'),className:isCorporate?'flex items-center gap-1.5 px-3 py-1.5 bg-purple-600 text-[13px] text-white hover:bg-purple-700 border border-purple-700':'flex items-center gap-2 px-4 py-2 bg-purple-600 rounded-lg text-white hover:bg-purple-700 transition-colors'},/*#__PURE__*/React.createElement(Icons.Plus,{className:isCorporate?'w-3 h-3':''}),t('createContainer')))),resourcesSubTab==='snapshots'&&/*#__PURE__*/React.createElement("div",{className:isCorporate?'bg-proxmox-card border border-proxmox-border p-4 space-y-3':'bg-proxmox-card border border-proxmox-border rounded-xl p-6 space-y-4'},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold text-white"},t('snapshotsOverview')||'Snapshot Overview'),/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400 mt-1"},t('snapshotsDesc')||'Overview of the oldest snapshots in this cluster')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("input",{type:"date",value:snapshotFilterDate,onChange:e=>setSnapshotFilterDate(e.target.value),className:"rounded-lg bg-proxmox-dark border border-proxmox-border px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-proxmox-orange"}),/*#__PURE__*/React.createElement("button",{onClick:()=>applySnapshotFilter(selectedCluster.id),disabled:!snapshotFilterDate,className:"rounded-lg bg-proxmox-orange px-4 py-2 text-sm font-medium text-white hover:bg-proxmox-orange/90 disabled:opacity-50 disabled:cursor-not-allowed"},t('filter')||'Filter'),/*#__PURE__*/React.createElement("button",{onClick:()=>fetchGlobalSnapshots(selectedCluster.id),className:"p-2 rounded-lg bg-proxmox-dark border border-proxmox-border text-gray-400 hover:text-white",title:"Refresh"},/*#__PURE__*/React.createElement(Icons.RotateCw,{className:"w-4 h-4"})))),!Array.isArray(sortedSnapshots)||sortedSnapshots.length===0?/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark rounded-xl p-8 text-center"},/*#__PURE__*/React.createElement(Icons.Camera,{className:"mx-auto mb-3 w-10 h-10 text-gray-600"}),/*#__PURE__*/React.createElement("p",{className:"text-gray-500"},t('noSnapshots')||'No snapshots found'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-2"},t('snapshotsHint')||'Create snapshots on VMs to see them listed here')):/*#__PURE__*/React.createElement("div",{className:"overflow-x-auto bg-proxmox-dark rounded-xl border border-gray-800"},/*#__PURE__*/React.createElement("table",{className:"min-w-full text-sm"},/*#__PURE__*/React.createElement("thead",{className:"bg-black/40 text-gray-400"},/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleSnapshotSort('vmid')},"VM ID ",snapshotSortBy==='vmid'&&(snapshotSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleSnapshotSort('vm_name')},"VM Name ",snapshotSortBy==='vm_name'&&(snapshotSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleSnapshotSort('vm_type')},t('snapshotsType')||'Type'," ",snapshotSortBy==='vm_type'&&(snapshotSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleSnapshotSort('node')},"Node ",snapshotSortBy==='node'&&(snapshotSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleSnapshotSort('snapshot_name')},"Snapshot ",snapshotSortBy==='snapshot_name'&&(snapshotSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleSnapshotSort('snapshot_date')},t('snapshotsDate')||'Created'," ",snapshotSortBy==='snapshot_date'&&(snapshotSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleSnapshotSort('age')},t('snapshotsAge')||'Age'," ",snapshotSortBy==='age'&&(snapshotSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-right w-12"},t('snapshotsAction')||'Action'))),/*#__PURE__*/React.createElement("tbody",{className:"divide-y divide-gray-800"},sortedSnapshots.map((snap,idx)=>/*#__PURE__*/React.createElement("tr",{key:`${snap.vmid}-${snap.snapshot_name}-${idx}`,className:"group hover:bg-white/5 transition-colors"},/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-gray-300"},snap.vmid??'-'),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-gray-200"},snap.vm_name??'-'),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3"},/*#__PURE__*/React.createElement("span",{className:`px-2 py-1 rounded text-xs ${snap.vm_type==='qemu'?'bg-blue-500/20 text-blue-400':'bg-purple-500/20 text-purple-400'}`},snap.vm_type==='qemu'?'VM':'CT')),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-gray-300"},snap.node??'-'),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 font-mono text-gray-200"},snap.snapshot_name??'-'),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-gray-300"},snap.snapshot_date??'-'),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-yellow-400"},snap.age??'-'),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-right"},/*#__PURE__*/React.createElement("button",{onClick:()=>deleteGlobalSnapshot(snap,selectedCluster.id),className:"opacity-0 group-hover:opacity-100 transition text-red-500 hover:text-red-400",title:"Delete snapshot"},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-4 h-4"}))))))))),resourcesSubTab==='logs'&&/*#__PURE__*/React.createElement("div",{className:isCorporate?'bg-proxmox-card border border-proxmox-border p-4 space-y-4':'bg-proxmox-card border border-proxmox-border rounded-xl p-6 space-y-4'},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold text-white"},t('syslog')||'Log Events'),/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400 mt-1"},"Integrated syslog events with filtering, search, sorting and 50-row pages")),/*#__PURE__*/React.createElement("button",{onClick:()=>fetchLogEvents({page:logEventsPage}),className:"self-start p-2 rounded-lg bg-proxmox-dark border border-proxmox-border text-gray-400 hover:text-white",title:"Refresh"},/*#__PURE__*/React.createElement(Icons.RotateCw,{className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-6"},/*#__PURE__*/React.createElement("div",{className:"xl:col-span-2"},/*#__PURE__*/React.createElement("label",{className:"block text-xs font-medium text-gray-500 mb-1"},"Search"),/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement(Icons.Search,{className:"absolute left-3 top-1/2 -translate-y-1/2 text-gray-500 w-4 h-4"}),/*#__PURE__*/React.createElement("input",{type:"text",value:logEventFilters.search,onChange:e=>setLogEventFilters(prev=>({...prev,search:e.target.value})),onKeyDown:e=>{if(e.key==='Enter')applyLogEventFilters();},placeholder:"Message, host, IP, protocol",className:"w-full rounded-lg bg-proxmox-dark border border-proxmox-border pl-9 pr-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-proxmox-orange"}))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs font-medium text-gray-500 mb-1"},"Severity"),/*#__PURE__*/React.createElement("select",{value:logEventFilters.severity,onChange:e=>setLogEventFilters(prev=>({...prev,severity:e.target.value})),className:"w-full rounded-lg bg-proxmox-dark border border-proxmox-border px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-proxmox-orange"},/*#__PURE__*/React.createElement("option",{value:""},"All severities"),logEventsSeverities.map(sev=>/*#__PURE__*/React.createElement("option",{key:sev.value,value:sev.value},sev.label)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs font-medium text-gray-500 mb-1"},"Protocol"),/*#__PURE__*/React.createElement("select",{value:logEventFilters.protocol,onChange:e=>setLogEventFilters(prev=>({...prev,protocol:e.target.value})),className:"w-full rounded-lg bg-proxmox-dark border border-proxmox-border px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-proxmox-orange"},/*#__PURE__*/React.createElement("option",{value:""},"All protocols"),logEventsProtocols.map(protocol=>/*#__PURE__*/React.createElement("option",{key:protocol,value:protocol},protocol)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs font-medium text-gray-500 mb-1"},"Hostname"),/*#__PURE__*/React.createElement("input",{type:"text",value:logEventFilters.hostname,onChange:e=>setLogEventFilters(prev=>({...prev,hostname:e.target.value})),onKeyDown:e=>{if(e.key==='Enter')applyLogEventFilters();},placeholder:"Filter host",className:"w-full rounded-lg bg-proxmox-dark border border-proxmox-border px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-proxmox-orange"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs font-medium text-gray-500 mb-1"},"Source IP"),/*#__PURE__*/React.createElement("input",{type:"text",value:logEventFilters.source_ip,onChange:e=>setLogEventFilters(prev=>({...prev,source_ip:e.target.value})),onKeyDown:e=>{if(e.key==='Enter')applyLogEventFilters();},placeholder:"Filter IP",className:"w-full rounded-lg bg-proxmox-dark border border-proxmox-border px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-proxmox-orange"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-wrap items-center gap-2"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs font-medium text-gray-500 mb-1"},"Facility"),/*#__PURE__*/React.createElement("input",{type:"number",min:"0",value:logEventFilters.facility,onChange:e=>setLogEventFilters(prev=>({...prev,facility:e.target.value})),onKeyDown:e=>{if(e.key==='Enter')applyLogEventFilters();},placeholder:"e.g. 1",className:"w-28 rounded-lg bg-proxmox-dark border border-proxmox-border px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-proxmox-orange"})),/*#__PURE__*/React.createElement("button",{onClick:applyLogEventFilters,className:"mt-5 rounded-lg bg-proxmox-orange px-4 py-2 text-sm font-medium text-white hover:bg-proxmox-orange/90"},t('filter')||'Filter'),/*#__PURE__*/React.createElement("button",{onClick:resetLogEventFilters,className:"mt-5 rounded-lg bg-proxmox-dark border border-proxmox-border px-4 py-2 text-sm text-gray-300 hover:text-white"},"Reset"),/*#__PURE__*/React.createElement("div",{className:"mt-5 text-xs text-gray-500"},logEventsTotal>0?`${logEventsTotal} events found`:'No events matched')),logEventsLoading?/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark rounded-xl p-8 text-center text-gray-400"},"Loading log events..."):logEventsError?/*#__PURE__*/React.createElement("div",{className:"bg-red-500/10 border border-red-500/30 rounded-xl p-4 text-sm text-red-300"},logEventsError):!Array.isArray(logEvents)||logEvents.length===0?/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark rounded-xl p-8 text-center"},/*#__PURE__*/React.createElement(Icons.Terminal,{className:"mx-auto mb-3 w-10 h-10 text-gray-600"}),/*#__PURE__*/React.createElement("p",{className:"text-gray-500"},"No log events found"),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-2"},"Adjust the filters or wait for new syslog messages to arrive")):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"overflow-x-auto bg-proxmox-dark rounded-xl border border-gray-800"},/*#__PURE__*/React.createElement("table",{className:"min-w-full text-sm"},/*#__PURE__*/React.createElement("thead",{className:"bg-black/40 text-gray-400"},/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleLogEventSort('timestamp')},"Timestamp ",logEventsSortBy==='timestamp'&&(logEventsSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleLogEventSort('severity')},"Severity ",logEventsSortBy==='severity'&&(logEventsSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleLogEventSort('protocol')},"Protocol ",logEventsSortBy==='protocol'&&(logEventsSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleLogEventSort('hostname')},"Hostname ",logEventsSortBy==='hostname'&&(logEventsSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleLogEventSort('source_ip')},"Source IP ",logEventsSortBy==='source_ip'&&(logEventsSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleLogEventSort('facility')},"Facility ",logEventsSortBy==='facility'&&(logEventsSortDir==='asc'?'↑':'↓')),/*#__PURE__*/React.createElement("th",{className:"px-4 py-3 text-left cursor-pointer hover:text-white",onClick:()=>toggleLogEventSort('message')},"Message ",logEventsSortBy==='message'&&(logEventsSortDir==='asc'?'↑':'↓')))),/*#__PURE__*/React.createElement("tbody",{className:"divide-y divide-gray-800"},logEvents.map(event=>{const sev=(event.severity_text||'').toLowerCase();const severityClass=sev==='emergency'||sev==='alert'||sev==='critical'||sev==='error'?'bg-red-500/20 text-red-300':sev==='warning'?'bg-yellow-500/20 text-yellow-300':sev==='notice'?'bg-blue-500/20 text-blue-300':'bg-gray-500/20 text-gray-300';return/*#__PURE__*/React.createElement("tr",{key:event.id,className:"hover:bg-white/5 transition-colors align-top"},/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-gray-300 whitespace-nowrap"},event.timestamp?new Date(event.timestamp).toLocaleString():'-'),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3"},/*#__PURE__*/React.createElement("span",{className:`inline-flex items-center rounded px-2 py-1 text-xs font-medium ${severityClass}`},event.severity_text||event.severity||'-')),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-gray-300 whitespace-nowrap"},event.protocol||'-'),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-gray-200 whitespace-nowrap"},event.hostname||'-'),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-gray-300 whitespace-nowrap"},event.source_ip||'-'),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-gray-400 whitespace-nowrap"},event.facility??'-'),/*#__PURE__*/React.createElement("td",{className:"px-4 py-3 text-gray-200 min-w-[24rem] whitespace-pre-wrap break-words"},event.message||'-'));})))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"},/*#__PURE__*/React.createElement("div",{className:"text-sm text-gray-400"},"Page ",logEventsPage," of ",Math.max(logEventsTotalPages,1)," with up to 50 events per page"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setLogEventsPage(prev=>Math.max(prev-1,1)),disabled:logEventsPage<=1,className:"rounded-lg bg-proxmox-dark border border-proxmox-border px-3 py-2 text-sm text-gray-300 hover:text-white disabled:opacity-50 disabled:cursor-not-allowed"},"Previous"),/*#__PURE__*/React.createElement("button",{onClick:()=>setLogEventsPage(prev=>Math.min(prev+1,Math.max(logEventsTotalPages,1))),disabled:logEventsPage>=logEventsTotalPages,className:"rounded-lg bg-proxmox-dark border border-proxmox-border px-3 py-2 text-sm text-gray-300 hover:text-white disabled:opacity-50 disabled:cursor-not-allowed"},"Next"))))),resourcesSubTab==='topology'&&/*#__PURE__*/React.createElement(TopologyView,{clusterName:selectedCluster?.name||selectedCluster?.id,clusterId:selectedCluster?.id,nodes:Object.entries(clusterMetrics).filter(([k])=>k!=='error'&&k!=='offline').map(([name,m])=>({name,...m})),resources:clusterResources,pbsServers:pbsServers,isCorporate:isCorporate,onVmClick:isCorporate?vm=>{setSelectedSidebarVm({...vm,_clusterId:selectedCluster.id});setSelectedSidebarNode(null);setSelectedSidebarDatastore(null);setExpandedSidebarClusters(prev=>({...prev,[selectedCluster.id]:true}));}:undefined}))),activeTab==='datacenter'&&/*#__PURE__*/React.createElement(DatacenterTab,{clusterId:selectedCluster.id,addToast:addToast}),activeTab==='datastore'&&/*#__PURE__*/React.createElement(DatastoreTab,{clusterId:selectedCluster.id,addToast:addToast,initialStorage:selectedSidebarDatastore?.name||null,initialNode:selectedSidebarDatastore?.node||null,sharedDatastoreData:clusterDatastores}),activeTab==='automation'&&/*#__PURE__*/React.createElement("div",{className:"space-y-6"},/*#__PURE__*/React.createElement("div",{className:isCorporate?'corp-tab-strip':'flex items-center gap-1 border-b border-proxmox-border pb-2'},[{id:'schedules',label:t('scheduledActions')||'Schedules',icon:Icons.Clock},{id:'tags',label:t('tagsLabels')||'Tags',icon:Icons.Tag},{id:'alerts',label:t('alerts')||'Alerts',icon:Icons.Bell},{id:'affinity',label:t('affinityRules')||'Affinity',icon:Icons.Link},{id:'scripts',label:t('customScripts')||'Scripts',icon:Icons.Terminal},{id:'hardening',label:t('hardenNode')||'Harden PVE Node',icon:Icons.Shield}].map(sub=>/*#__PURE__*/React.createElement("button",{key:sub.id,onClick:()=>setAutomationSubTab(sub.id),className:isCorporate?`flex items-center gap-1 ${automationSubTab===sub.id?'active':''}`:`flex items-center gap-2 px-4 py-2 rounded-lg text-sm transition-colors ${automationSubTab===sub.id?'bg-proxmox-orange text-white':'text-gray-400 hover:text-white hover:bg-proxmox-hover'}`},/*#__PURE__*/React.createElement(sub.icon,{className:isCorporate?'w-3 h-3':'w-4 h-4'}),sub.label))),automationSubTab==='schedules'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400"},t('schedulesDesc')||'Automatically start, stop, reboot or snapshot VMs on a schedule'),/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingSchedule(null);setShowScheduleModal(true);},className:"flex items-center gap-2 px-4 py-2 bg-proxmox-orange hover:bg-orange-600 rounded-lg text-sm"},/*#__PURE__*/React.createElement(Icons.Plus,null)," ",t('newSchedule')||'New Schedule')),schedules.filter(s=>s.cluster_id===selectedCluster?.id).length===0?/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark rounded-xl p-8 text-center"},/*#__PURE__*/React.createElement(Icons.Clock,{className:"mx-auto mb-3 w-10 h-10 text-gray-600"}),/*#__PURE__*/React.createElement("p",{className:"text-gray-500"},t('noSchedules')||'No scheduled actions for this cluster')):/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl overflow-hidden"},/*#__PURE__*/React.createElement("table",{className:"w-full"},/*#__PURE__*/React.createElement("thead",{className:"bg-proxmox-dark"},/*#__PURE__*/React.createElement("tr",{className:"text-left text-xs text-gray-500 uppercase"},/*#__PURE__*/React.createElement("th",{className:"p-3"},t('status')||'Status'),/*#__PURE__*/React.createElement("th",{className:"p-3"},t('name')||'Name'),/*#__PURE__*/React.createElement("th",{className:"p-3"},"VM"),/*#__PURE__*/React.createElement("th",{className:"p-3"},t('action')||'Action'),/*#__PURE__*/React.createElement("th",{className:"p-3"},t('schedule')||'Schedule'),/*#__PURE__*/React.createElement("th",{className:"p-3"},t('lastRun')||'Last Run'),/*#__PURE__*/React.createElement("th",{className:"p-3 w-16"}))),/*#__PURE__*/React.createElement("tbody",{className:"divide-y divide-proxmox-border"},schedules.filter(s=>s.cluster_id===selectedCluster?.id).map(schedule=>/*#__PURE__*/React.createElement("tr",{key:schedule.id,className:"hover:bg-proxmox-hover"},/*#__PURE__*/React.createElement("td",{className:"p-3"},/*#__PURE__*/React.createElement("button",{onClick:()=>toggleScheduleEnabled(schedule.id,!schedule.enabled),className:`w-9 h-5 rounded-full relative transition-colors ${schedule.enabled?'bg-green-500':'bg-gray-600'}`},/*#__PURE__*/React.createElement("span",{className:`absolute top-0.5 w-4 h-4 bg-white rounded-full transition-transform ${schedule.enabled?'left-4':'left-0.5'}`}))),/*#__PURE__*/React.createElement("td",{className:"p-3 font-medium"},schedule.name),/*#__PURE__*/React.createElement("td",{className:"p-3 text-sm"},schedule.vm_type==='lxc'?'📦':'🖥️'," ",schedule.vmid),/*#__PURE__*/React.createElement("td",{className:"p-3"},/*#__PURE__*/React.createElement("span",{className:`px-2 py-1 rounded text-xs ${schedule.action==='start'?'bg-green-500/20 text-green-400':schedule.action==='stop'?'bg-red-500/20 text-red-400':schedule.action==='snapshot'?'bg-blue-500/20 text-blue-400':'bg-yellow-500/20 text-yellow-400'}`},schedule.action)),/*#__PURE__*/React.createElement("td",{className:"p-3 text-sm text-gray-400"},schedule.time," \u2022 ",schedule.schedule_type==='daily'?t('daily')||'Daily':schedule.schedule_type==='weekdays'?t('weekdays')||'Weekdays':schedule.schedule_type==='weekends'?t('weekends')||'Weekends':schedule.schedule_type==='weekly'?(schedule.days||[]).join(', '):schedule.date),/*#__PURE__*/React.createElement("td",{className:"p-3 text-xs text-gray-500"},schedule.last_run||'-',schedule.run_count>0&&` (${schedule.run_count}x)`),/*#__PURE__*/React.createElement("td",{className:"p-3 flex gap-1"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingSchedule(schedule);setShowScheduleModal(true);},className:"p-1 hover:bg-blue-500/20 rounded text-gray-500 hover:text-blue-400",title:"Edit"},/*#__PURE__*/React.createElement(Icons.Edit,{className:"w-4 h-4"})),/*#__PURE__*/React.createElement("button",{onClick:()=>deleteSchedule(schedule.id),className:"p-1 hover:bg-red-500/20 rounded text-gray-500 hover:text-red-400"},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-4 h-4"}))))))))),automationSubTab==='tags'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400"},t('tagsDesc')||'Organize VMs with tags. Click the tag icon on any VM to add tags.'),clusterTags.length===0?/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark rounded-xl p-8 text-center"},/*#__PURE__*/React.createElement(Icons.Tag,{className:"mx-auto mb-3 w-10 h-10 text-gray-600"}),/*#__PURE__*/React.createElement("p",{className:"text-gray-500"},t('noTagsInCluster')||'No tags in this cluster yet'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-2"},t('addTagHint')||'Use the tag button on VMs in the Resources tab')):/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex flex-wrap gap-2"},clusterTags.map((tag,idx)=>/*#__PURE__*/React.createElement("span",{key:idx,className:"flex items-center gap-2 px-3 py-1.5 rounded-full border",style:{backgroundColor:(tag.color||'#6b7280')+'20',borderColor:tag.color||'#6b7280',color:tag.color||'#9ca3af'}},tag.name,/*#__PURE__*/React.createElement("span",{className:"text-xs opacity-60"},"(",tag.count||0,")")))),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},clusterTags.length," ",t('tagsTotal')||'tags'," \u2022 ",t('clickVmTag')||'Click tag icon on VMs to manage'))),automationSubTab==='alerts'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400"},t('alertsDesc')||'Get notified when resources exceed thresholds'),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowAlertModal(true),className:"flex items-center gap-2 px-4 py-2 bg-proxmox-orange hover:bg-orange-600 rounded-lg text-sm"},/*#__PURE__*/React.createElement(Icons.Plus,null)," ",t('newAlert')||'New Alert')),clusterAlerts.length===0?/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark rounded-xl p-8 text-center"},/*#__PURE__*/React.createElement(Icons.Bell,{className:"mx-auto mb-3 w-10 h-10 text-gray-600"}),/*#__PURE__*/React.createElement("p",{className:"text-gray-500"},t('noAlertsCluster')||'No alerts for this cluster')):/*#__PURE__*/React.createElement("div",{className:"space-y-2"},clusterAlerts.map(alert=>/*#__PURE__*/React.createElement("div",{key:alert.id,className:`flex items-center justify-between p-3 rounded-lg border ${alert.enabled?'bg-proxmox-dark border-proxmox-border':'bg-proxmox-darker border-proxmox-darker opacity-60'}`},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("button",{onClick:()=>toggleAlertEnabled(alert.id,!alert.enabled),className:`w-9 h-5 rounded-full relative transition-colors ${alert.enabled?'bg-green-500':'bg-gray-600'}`},/*#__PURE__*/React.createElement("span",{className:`absolute top-0.5 w-4 h-4 bg-white rounded-full transition-transform ${alert.enabled?'left-4':'left-0.5'}`})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"font-medium flex items-center gap-2"},alert.name,/*#__PURE__*/React.createElement("span",{className:`px-1.5 py-0.5 text-xs rounded ${alert.target_type==='vm'?'bg-blue-500/20 text-blue-400':alert.target_type==='node'?'bg-purple-500/20 text-purple-400':'bg-gray-500/20 text-gray-400'}`},alert.target_type==='vm'?`VM ${alert.target_id||''}`:alert.target_type==='node'?`Node ${alert.target_id||''}`:t('cluster')||'Cluster')),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},alert.metric?.toUpperCase()," ",alert.operator," ",alert.threshold,"%"))),/*#__PURE__*/React.createElement("button",{onClick:()=>deleteClusterAlert(alert.id),className:"p-1.5 hover:bg-red-500/20 rounded text-gray-500 hover:text-red-400"},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-4 h-4"})))))),automationSubTab==='affinity'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400"},t('affinityDesc')||'Keep VMs together or separate across nodes'),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowAffinityModal(true),className:"flex items-center gap-2 px-4 py-2 bg-proxmox-orange hover:bg-orange-600 rounded-lg text-sm"},/*#__PURE__*/React.createElement(Icons.Plus,null)," ",t('newRule')||'New Rule')),clusterAffinityRules.length===0?/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark rounded-xl p-8 text-center"},/*#__PURE__*/React.createElement(Icons.Link,{className:"mx-auto mb-3 w-10 h-10 text-gray-600"}),/*#__PURE__*/React.createElement("p",{className:"text-gray-500"},t('noAffinityCluster')||'No affinity rules for this cluster')):/*#__PURE__*/React.createElement("div",{className:"space-y-2"},clusterAffinityRules.map(rule=>/*#__PURE__*/React.createElement("div",{key:rule.id,className:"flex items-center justify-between p-3 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("span",{className:`px-2 py-1 rounded text-xs ${rule.type==='together'?'bg-green-500/20 text-green-400':'bg-red-500/20 text-red-400'}`},rule.type==='together'?t('together')||'Together':t('separate')||'Separate'),rule.enforce&&/*#__PURE__*/React.createElement("span",{className:"px-1.5 py-0.5 text-xs bg-yellow-500/20 text-yellow-400 rounded"},t('enforced')||'Enforced'),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white font-medium"},rule.name||rule.id),/*#__PURE__*/React.createElement("span",{className:"text-sm text-gray-400"},"VMs: ",(rule.vm_ids||rule.vms||[]).join(', '))),/*#__PURE__*/React.createElement("button",{onClick:()=>deleteClusterAffinityRule(rule.id),className:"p-1.5 hover:bg-red-500/20 rounded text-gray-500 hover:text-red-400"},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-4 h-4"})))))),automationSubTab==='scripts'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center"},/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400"},t('scriptsDesc')||'Run custom .sh or .py scripts on cluster nodes with permission control'),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>loadCustomScripts(selectedCluster?.id),className:"flex items-center gap-1 px-3 py-2 bg-proxmox-dark hover:bg-proxmox-hover rounded-lg text-sm text-gray-400",title:t('refresh')||'Refresh'},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4"})),/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingScript(null);setShowScriptModal(true);},className:"flex items-center gap-2 px-4 py-2 bg-proxmox-orange hover:bg-orange-600 rounded-lg text-sm"},/*#__PURE__*/React.createElement(Icons.Plus,null)," ",t('newScript')||'New Script'))),!customScripts||customScripts.length===0?/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark rounded-xl p-8 text-center"},/*#__PURE__*/React.createElement(Icons.Terminal,{className:"mx-auto mb-3 w-10 h-10 text-gray-600"}),/*#__PURE__*/React.createElement("p",{className:"text-gray-500"},t('noScripts')||'No custom scripts configured'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-2"},t('scriptsInfo')||'Create scripts to automate tasks across your cluster nodes')):/*#__PURE__*/React.createElement("div",{className:"space-y-3"},customScripts.map(script=>/*#__PURE__*/React.createElement("div",{key:script.id,className:"bg-proxmox-card border border-proxmox-border rounded-xl p-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:`w-10 h-10 rounded-lg flex items-center justify-center ${script.type==='python'?'bg-blue-500/20 text-blue-400':'bg-green-500/20 text-green-400'}`},script.type==='python'?'🐍':'📜'),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h4",{className:"font-medium text-white"},script.name),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},script.type==='python'?'Python':'Bash'," \u2022 ",script.target_nodes==='all'?'All Nodes':script.target_nodes,script.created_by&&/*#__PURE__*/React.createElement("span",{className:"ml-2"},"\u2022 Created by ",script.created_by)))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowScriptRunModal(script),className:"p-2 rounded-lg bg-green-500/20 text-green-400 hover:bg-green-500/30",title:t('runScript')||'Run Script',disabled:!script.enabled},/*#__PURE__*/React.createElement(Icons.Play,{className:"w-4 h-4"})),script.last_run&&/*#__PURE__*/React.createElement("button",{onClick:async()=>{try{const res=await authFetch(`${API_URL}/clusters/${selectedCluster.id}/scripts/${script.id}/output`);if(res.ok){const data=await res.json();setScriptOutput({name:data.name,output:data.output,last_run:data.last_run,last_status:data.last_status});}}catch(e){addToast('Error loading output','error');}},className:"p-2 rounded-lg bg-blue-500/20 text-blue-400 hover:bg-blue-500/30",title:t('viewOutput')||'View Last Output'},/*#__PURE__*/React.createElement(Icons.FileText,{className:"w-4 h-4"})),/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingScript(script);setShowScriptModal(true);},className:"p-2 rounded-lg bg-proxmox-dark text-gray-400 hover:bg-proxmox-hover hover:text-white"},/*#__PURE__*/React.createElement(Icons.Edit,{className:"w-4 h-4"})),/*#__PURE__*/React.createElement("button",{onClick:async()=>{if(!confirm(t('confirmDeleteScript')||'Delete this script? It will be permanently removed after 20 days.'))return;try{const res=await authFetch(`${API_URL}/clusters/${selectedCluster.id}/scripts/${script.id}`,{method:'DELETE'});if(res.ok){const result=await res.json();addToast(result.message||t('scriptMarkedForDeletion')||'Script marked for deletion (20 days)','success');setCustomScripts(prev=>prev.filter(s=>s.id!==script.id));}}catch(e){addToast('Error deleting script','error');}},className:"p-2 rounded-lg bg-red-500/20 text-red-400 hover:bg-red-500/30"},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-4 h-4"})))),script.description&&/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400 mt-2"},script.description),script.last_run&&/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-2"},"Last run: ",new Date(script.last_run).toLocaleString()," -",/*#__PURE__*/React.createElement("span",{className:script.last_status==='success'?'text-green-400 ml-1':script.last_status==='partial'?'text-yellow-400 ml-1':'text-red-400 ml-1'},script.last_status))))),/*#__PURE__*/React.createElement("div",{className:"bg-blue-500/10 border border-blue-500/30 rounded-xl p-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-start gap-3"},/*#__PURE__*/React.createElement(Icons.Shield,{className:"text-blue-400 w-5 h-5 mt-0.5"}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h4",{className:"font-medium text-blue-300"},t('scriptPermissions')||'Script Permissions'),/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400 mt-1"},t('scriptPermissionsDesc')||'Scripts require SSH access to nodes. Configure SSH key in cluster settings. Scripts run with the SSH user\'s permissions.'))))),automationSubTab==='hardening'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-start"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("p",{className:"text-sm text-gray-400"},t('hardenDesc')||'Apply CIS Benchmark hardening controls to individual PVE nodes via SSH.'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-1"},t('hardenCisRef')||'Based on CIS Benchmark, Lynis Security Auditing & DoD STIG Guidelines'))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("select",{value:hardenNode,onChange:e=>{setHardenNode(e.target.value);setHardenStatus(null);setHardenResults(null);setHardenSelected({});},className:"bg-proxmox-dark border border-proxmox-border rounded-lg px-3 py-2 text-sm flex-1 max-w-xs"},/*#__PURE__*/React.createElement("option",{value:""},t('selectNode')||'Select a node...'),Object.keys(clusterMetrics).sort().map(n=>/*#__PURE__*/React.createElement("option",{key:n,value:n},n))),/*#__PURE__*/React.createElement("button",{onClick:()=>checkHardening(hardenNode),disabled:!hardenNode||hardenLoading,className:`px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 ${!hardenNode||hardenLoading?'bg-gray-700 text-gray-500 cursor-not-allowed':'bg-proxmox-orange hover:bg-orange-600 text-white'}`},hardenLoading?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement(Icons.RotateCw,{className:"w-4 h-4 animate-spin"})," ",t('checking')||'Checking...'):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement(Icons.Shield,{className:"w-4 h-4"})," ",t('checkStatus')||'Check Status'))),hardenLoading&&/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center py-12"},/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement(Icons.RotateCw,{className:"animate-spin w-8 h-8 text-gray-500 mx-auto mb-2"}),/*#__PURE__*/React.createElement("p",{className:"text-gray-400 text-sm"},t('checkingHardening')||'Checking CIS controls via SSH...'))),hardenStatus&&!hardenLoading&&(()=>{// CIS Benchmark controls const cisControls={fs_modules:{ref:'1.1.1.1-1.1.1.5',title:t('cisFs')||'Disable Unused Filesystem Modules',desc:t('cisFsDesc')||'Prevents loading of rarely-used filesystem kernel modules (cramfs, freevxfs, hfs, hfsplus, jffs2)',impact:t('cisFsImpact')||'None - VMs and containers unaffected'},core_dumps:{ref:'1.5.11-1.5.13',title:t('cisCoreDumps')||'Disable Core Dumps',desc:t('cisCoreDumpsDesc')||'Prevents the system from creating memory dumps when programs crash that can contain passwords and keys',impact:t('cisCoreDumpsImpact')||'None - VM crashes still logged normally'},mount_options:{ref:'1.6.1',title:t('cisMounts')||'Mount Options Hardening',desc:t('cisMountsDesc')||'Applies nodev, nosuid, noexec to /tmp, /var/tmp, /dev/shm to prevent privilege escalation',impact:t('cisMountsImpact')||'No impact on cluster operations or VMs'},cron_hardening:{ref:'2.4.1.2-2.4.2.1',title:t('cisCron')||'Cron/At Access Hardening',desc:t('cisCronDesc')||'Restricts job scheduler access to root only, preventing persistence attacks',impact:t('cisCronImpact')||'None - PVE scheduled tasks run as root'},net_protocols:{ref:'3.2.1-3.2.2',title:t('cisNet')||'Disable Unused Network Protocols',desc:t('cisNetDesc')||'Prevents loading of rarely-used network protocols (dccp, sctp, rds, tipc)',impact:t('cisNetImpact')||'None - not used by Proxmox'},journald:{ref:'4.2.1.1-4.2.1.4',title:t('cisJournald')||'Journald Hardening',desc:t('cisJournaldDesc')||'Configures persistent, compressed logging that survives reboots for forensic analysis',impact:t('cisJournaldImpact')||'Better forensic capabilities'},ssh_perms:{ref:'5.1.1-5.1.3',title:t('cisSshPerms')||'SSH File Permissions',desc:t('cisSshPermsDesc')||'Sets secure permissions (600) on SSH config and host keys',impact:t('cisSshPermsImpact')||'None - SSH works normally'},ssh_crypto:{ref:'5.1.4-5.1.22',title:t('cisSshCrypto')||'SSH Cryptographic Hardening',desc:t('cisSshCryptoDesc')||'Configures SSH to use only strong ciphers (AES-GCM/CTR), secure key exchange (Curve25519), and strong MACs (SHA2-ETM)',impact:t('cisSshCryptoImpact')||'Very old SSH clients may not connect'},pam_faillock:{ref:'5.3.3.1',title:t('cisPam')||'Account Lockout (pam_faillock)',desc:t('cisPamDesc')||'Locks accounts after 5 failed login attempts, auto-unlocks after 10 minutes. Root excluded.',impact:t('cisPamImpact')||'Works alongside Fail2Ban for defense in depth'},shell_timeout:{ref:'5.4.3.2',title:t('cisTimeout')||'Shell Timeout',desc:t('cisTimeoutDesc')||'Automatically logs out inactive shell sessions after 15 minutes',impact:t('cisTimeoutImpact')||'Only interactive SSH sessions affected'},file_perms:{ref:'6.1',title:t('cisFilePerms')||'System File Permissions',desc:t('cisFilePermsDesc')||'Sets correct permissions on /etc/passwd, /etc/shadow, /etc/group, /etc/gshadow',impact:t('cisFilePermsImpact')||'No impact - standard Linux hardening'}};// Lynis recommendations const lynisControls={backup_dns:{ref:'NETW-2705',title:t('lynDns')||'Backup Nameserver',desc:t('lynDnsDesc')||'Ensures at least 2 DNS nameservers are configured for redundancy (configurable)',impact:t('lynDnsImpact')||'Prevents DNS outages'},postfix_banner:{ref:'MAIL-8818',title:t('lynPostfix')||'Postfix Banner Hardening',desc:t('lynPostfixDesc')||'Removes software version information from mail server banner to prevent version disclosure',impact:t('lynPostfixImpact')||'None - only hides version string'},pw_hash_rounds:{ref:'AUTH-9230',title:t('lynPwHash')||'Password Hashing Rounds',desc:t('lynPwHashDesc')||'Increases SHA password hashing iterations (5000-500000 rounds), making brute-force attacks significantly slower',impact:t('lynPwHashImpact')||'Only affects new/changed passwords'},pw_quality:{ref:'AUTH-9262',title:t('lynPwQuality')||'PAM Password Quality',desc:t('lynPwQualityDesc')||'Enforces strong password policies: min 12 chars, uppercase, lowercase, digit, special char. Installs libpam-pwquality.',impact:t('lynPwQualityImpact')||'Users must set stronger passwords'},pw_aging:{ref:'AUTH-9286',title:t('lynPwAging')||'Password Aging',desc:t('lynPwAgingDesc')||'Forces password changes every 365 days with 30-day warning period. Root excluded.',impact:t('lynPwAgingImpact')||'Users must change passwords annually'},pw_history:{ref:'AUTH-9290',title:t('lynPwHistory')||'Password History',desc:t('lynPwHistoryDesc')||'Prevents reuse of the last 24 passwords via pam_pwhistory.',impact:t('lynPwHistoryImpact')||'Users cannot reuse recent passwords'},default_umask:{ref:'AUTH-9328',title:t('lynUmask')||'Default Umask (027)',desc:t('lynUmaskDesc')||'Tightens default file permissions so new files are only readable by owner and group, not everyone',impact:t('lynUmaskImpact')||'New files more restrictive by default'},pkg_cleanup:{ref:'PKGS-7346',title:t('lynPkgCleanup')||'Old Package Cleanup',desc:t('lynPkgCleanupDesc')||'Removes leftover configuration files from previously uninstalled packages',impact:t('lynPkgCleanupImpact')||'None - removes unused config remnants'},debsums:{ref:'PKGS-7370',title:t('lynDebsums')||'Package Verification (debsums)',desc:t('lynDebsumsDesc')||'Installs debsums to verify package file integrity and detect corrupted or tampered system files',impact:t('lynDebsumsImpact')||'None - read-only verification tool'},login_banners:{ref:'BANN-7126',title:t('lynBanners')||'Login Banners',desc:t('lynBannersDesc')||'Displays legal warning banner before login, required by compliance frameworks (PCI-DSS, HIPAA)',impact:t('lynBannersImpact')||'None - cosmetic only'},file_integrity:{ref:'FINT-4350',title:t('lynAide')||'File Integrity Monitoring (AIDE)',desc:t('lynAideDesc')||'Creates database of file checksums to detect unauthorized changes to system binaries and configs',impact:t('lynAideImpact')||'Initial DB build runs in background'},process_acct:{ref:'ACCT-9622',title:t('lynAcct')||'Process Accounting',desc:t('lynAcctDesc')||'Logs information about every process execution for forensic analysis (use lastcomm to view)',impact:t('lynAcctImpact')||'Minimal overhead'},sysstat:{ref:'ACCT-9626',title:t('lynSysstat')||'System Statistics (sysstat)',desc:t('lynSysstatDesc')||'Collects CPU, memory, disk I/O and network statistics over time (use sar for historical data)',impact:t('lynSysstatImpact')||'Minimal overhead - helps diagnose issues'},usb_storage:{ref:'USB-1000',title:t('lynUsb')||'Disable USB/Firewire Storage',desc:t('lynUsbDesc')||'Prevents loading of USB and Firewire storage drivers to block data theft. Keyboards/mice still work.',impact:t('lynUsbImpact')||'USB drives will not be recognized'},restrict_compilers:{ref:'HRDN-7222',title:t('lynCompilers')||'Restrict Compiler Access',desc:t('lynCompilersDesc')||'Limits compiler (gcc, g++, make) usage to root only, preventing attackers from compiling exploit code',impact:t('lynCompilersImpact')||'Non-root users cannot compile'},apt_show_versions:{ref:'PKGS-7394',title:t('lynAptShow')||'Patch Management (apt-show-versions)',desc:t('lynAptShowDesc')||'Installs apt-show-versions for quick overview of installed packages and their update status',impact:t('lynAptShowImpact')||'None - read-only query tool'},pam_tmpdir:{ref:'DEB-0280',title:t('lynTmpdir')||'Isolate /tmp per Session',desc:t('lynTmpdirDesc')||'Creates isolated temporary directories for each user session, preventing /tmp race condition attacks',impact:t('lynTmpdirImpact')||'None - only affects interactive sessions'}};// STIG (DoD) controls diff --git a/web/src/dashboard.js b/web/src/dashboard.js index 742ba6b..b9c5639 100644 --- a/web/src/dashboard.js +++ b/web/src/dashboard.js @@ -8576,7 +8576,7 @@
{/* Cluster Health */} - + {/* Migration History */}
150: Kritisch (rot)', excellent: 'Ausgezeichnet', good: 'Gut', @@ -3716,7 +3716,7 @@ clusterReconfigured: 'Cluster re-configured successfully', reconfigure: 'Re-configure', clusterHealth: 'Cluster Health', - clusterHealthTooltip: 'Health = 100 − (CPU×30% + RAM×30% + Storage×20% + Offline Nodes×20%)\nWithout storage data: CPU×37.5% + RAM×37.5% + Offline×25%\n\n80+: Excellent\n60–79: Good\n40–59: Warning\n< 40: Critical', + clusterHealthTooltip: 'Health = 100 − (CPU×30% + RAM×30% + Storage×20% + Offline Nodes×20%)\n\n80+: Excellent\n60–79: Good\n40–59: Warning\n< 40: Critical', nodeScoreTooltip: 'Node score = CPU% + RAM% (lower is better)\n\n< 100: Good (green)\n100–150: Elevated (yellow)\n> 150: Critical (red)', excellent: 'Excellent', good: 'Good', @@ -6737,7 +6737,7 @@ clusterReconfigured: 'Cluster reconfiguré avec succès', reconfigure: 'Reconfigurer', clusterHealth: 'Santé du Cluster', - clusterHealthTooltip: 'Santé = 100 − (CPU×30% + RAM×30% + Stockage×20% + Nœuds hors ligne×20%)\nSans données stockage : CPU×37,5% + RAM×37,5% + Hors ligne×25%\n\n80+ : Excellente\n60–79 : Bien\n40–59 : Avertissement\n< 40 : Critique', + clusterHealthTooltip: 'Santé = 100 − (CPU×30% + RAM×30% + Stockage×20% + Nœuds hors ligne×20%)\n\n80+ : Excellente\n60–79 : Bien\n40–59 : Avertissement\n< 40 : Critique', nodeScoreTooltip: 'Score nœud = CPU% + RAM% (plus bas est mieux)\n\n< 100 : Bon (vert)\n100–150 : Élevé (jaune)\n> 150 : Critique (rouge)', excellent: 'Excellente', good: 'Bien', @@ -9613,7 +9613,7 @@ clusterReconfigured: 'Cluster reconfigurado exitosamente', reconfigure: 'Reconfigurar', clusterHealth: 'Salud del cluster', - clusterHealthTooltip: 'Salud = 100 − (CPU×30% + RAM×30% + Almacenamiento×20% + Nodos fuera de línea×20%)\nSin datos de almacenamiento: CPU×37,5% + RAM×37,5% + Fuera de línea×25%\n\n80+: Excelente\n60–79: Buena\n40–59: Advertencia\n< 40: Crítica', + clusterHealthTooltip: 'Salud = 100 − (CPU×30% + RAM×30% + Almacenamiento×20% + Nodos fuera de línea×20%)\n\n80+: Excelente\n60–79: Buena\n40–59: Advertencia\n< 40: Crítica', nodeScoreTooltip: 'Puntuación nodo = CPU% + RAM% (menor es mejor)\n\n< 100: Bueno (verde)\n100–150: Elevado (amarillo)\n> 150: Crítico (rojo)', excellent: 'Excelente', good: 'Buena', @@ -12646,7 +12646,7 @@ clusterReconfigured: 'Cluster reconfigurado com sucesso', reconfigure: 'Reconfigurar', clusterHealth: 'Saúde do Cluster', - clusterHealthTooltip: 'Saúde = 100 − (CPU×30% + RAM×30% + Armazenamento×20% + Nós offline×20%)\nSem dados de armazenamento: CPU×37,5% + RAM×37,5% + Offline×25%\n\n80+: Excelente\n60–79: Bom\n40–59: Aviso\n< 40: Crítico', + clusterHealthTooltip: 'Saúde = 100 − (CPU×30% + RAM×30% + Armazenamento×20% + Nós offline×20%)\n\n80+: Excelente\n60–79: Bom\n40–59: Aviso\n< 40: Crítico', nodeScoreTooltip: 'Pontuação nó = CPU% + RAM% (menor é melhor)\n\n< 100: Bom (verde)\n100–150: Elevado (amarelo)\n> 150: Crítico (vermelho)', excellent: 'Excelente', good: 'Bom', @@ -15478,7 +15478,7 @@ clusterReconfigured: '클러스터가 성공적으로 재구성되었습니다', reconfigure: '재구성', clusterHealth: '클러스터 상태', - clusterHealthTooltip: '상태 = 100 − (CPU×30% + RAM×30% + 스토리지×20% + 오프라인 노드×20%)\n스토리지 데이터 없음: CPU×37.5% + RAM×37.5% + 오프라인×25%\n\n80+: 우수\n60–79: 양호\n40–59: 경고\n< 40: 심각', + clusterHealthTooltip: '상태 = 100 − (CPU×30% + RAM×30% + 스토리지×20% + 오프라인 노드×20%)\n\n80+: 우수\n60–79: 양호\n40–59: 경고\n< 40: 심각', nodeScoreTooltip: '노드 점수 = CPU% + RAM% (낮을수록 좋음)\n\n< 100: 양호 (녹색)\n100–150: 주의 (노란색)\n> 150: 심각 (빨간색)', excellent: '우수', good: '양호', diff --git a/web/src/vm_modals.js b/web/src/vm_modals.js index 1a6daa7..93a0539 100644 --- a/web/src/vm_modals.js +++ b/web/src/vm_modals.js @@ -6146,26 +6146,50 @@ } // Cluster Health Widget - function ClusterHealth({ metrics, isCorporate }) { + function ClusterHealth({ metrics, clusterStatus, isCorporate }) { const { t } = useTranslation(); const nodes = Object.entries(metrics).filter(([k, m]) => m && typeof m === 'object' && k !== 'error' && k !== 'offline'); - if (nodes.length === 0) return null; + if (nodes.length === 0 && !clusterStatus) return null; - const avgCpu = nodes.reduce((acc, [, m]) => acc + (m.cpu_percent ?? 0), 0) / nodes.length; - const avgMem = nodes.reduce((acc, [, m]) => acc + (m.mem_percent ?? 0), 0) / nodes.length; - const diskNodes = nodes.filter(([, m]) => m.disk_percent != null); - const avgDisk = diskNodes.length > 0 ? diskNodes.reduce((acc, [, m]) => acc + m.disk_percent, 0) / diskNodes.length : null; + // Node status from per-node metrics (has maintenance_mode info not in datacenter status) const onlineNodes = nodes.filter(([, m]) => m.status === 'online' && !m.maintenance_mode).length; const maintenanceNodes = nodes.filter(([, m]) => m.maintenance_mode).length; - const offlineNodes = nodes.length - onlineNodes - maintenanceNodes; - const offlineRatio = nodes.length > 0 ? (offlineNodes / nodes.length) * 100 : 0; - // When disk stats unavailable (e.g. XCP-ng), re-normalize: CPU 37.5%, RAM 37.5%, Offline 25% - const healthScore = avgDisk != null - ? Math.max(0, 100 - (avgCpu * 0.3 + avgMem * 0.3 + avgDisk * 0.2 + offlineRatio * 0.2)) - : Math.max(0, 100 - (avgCpu * 0.375 + avgMem * 0.375 + offlineRatio * 0.25)); + // Real-time display values from per-node SSE metrics (~1s updates) + const displayCpu = nodes.length > 0 ? nodes.reduce((acc, [, m]) => acc + (m.cpu_percent ?? 0), 0) / nodes.length : 0; + const displayMem = nodes.length > 0 ? nodes.reduce((acc, [, m]) => acc + (m.mem_percent ?? 0), 0) / nodes.length : 0; + + // Health score inputs from cluster-level datacenter/status (same source as badge/overview) + let healthCpu, healthMem, healthStorage, offlineRatio, totalNodeCount; + + if (clusterStatus && clusterStatus.resources) { + const resources = clusterStatus.resources; + healthCpu = resources.cpu?.percent || 0; + healthMem = resources.memory?.percent || 0; + healthStorage = resources.storage?.percent || 0; + totalNodeCount = clusterStatus.nodes?.total || nodes.length; + const offlineNodes = clusterStatus.nodes?.offline || 0; + offlineRatio = totalNodeCount > 0 ? (offlineNodes / totalNodeCount) * 100 : 0; + } else { + // Fallback: per-node data (when datacenter status not yet loaded) + healthCpu = displayCpu; + healthMem = displayMem; + const diskNodes = nodes.filter(([, m]) => m.disk_percent != null); + healthStorage = diskNodes.length > 0 ? diskNodes.reduce((acc, [, m]) => acc + m.disk_percent, 0) / diskNodes.length : 0; + totalNodeCount = nodes.length; + const offlineNodes = nodes.length - onlineNodes - maintenanceNodes; + offlineRatio = totalNodeCount > 0 ? (offlineNodes / totalNodeCount) * 100 : 0; + } + + // Storage display: use cluster-level storage pools (not rootfs) when available + const displayStorage = clusterStatus?.resources?.storage?.percent ?? (nodes.length > 0 + ? (() => { const dn = nodes.filter(([, m]) => m.disk_percent != null); return dn.length > 0 ? dn.reduce((acc, [, m]) => acc + m.disk_percent, 0) / dn.length : 0; })() + : 0); + + // Unified formula - same weights as badge/overview (CPU 30%, RAM 30%, Storage 20%, Offline 20%) + const healthScore = Math.max(0, 100 - (healthCpu * 0.3 + healthMem * 0.3 + healthStorage * 0.2 + offlineRatio * 0.2)); const healthLabel = healthScore >= 80 ? t('excellent') : healthScore >= 60 ? t('good') : healthScore >= 40 ? t('warning') : t('critical'); - const healthColor = healthScore >= 80 ? '#22c55e' : healthScore >= 60 ? '#84cc16' : healthScore >= 40 ? '#eab308' : '#ef4444'; + const healthColor = healthScore >= 80 ? '#22c55e' : healthScore >= 60 ? '#eab308' : healthScore >= 40 ? '#f97316' : '#ef4444'; // LW: Feb 2026 - corporate compact variant (Clarity dark theme) const corpHealthColor = healthScore >= 80 ? '#60b515' : healthScore >= 60 ? '#60b515' : healthScore >= 40 ? '#efc006' : '#f54f47'; @@ -6181,9 +6205,9 @@ {healthScore.toFixed(0)} {healthLabel}
- {t('nodesOnline')}: {onlineNodes}/{nodes.length} - CPU: {avgCpu.toFixed(1)}% - RAM: {avgMem.toFixed(1)}% + {t('nodesOnline')}: {onlineNodes}/{totalNodeCount} + CPU: {displayCpu.toFixed(1)}% + RAM: {displayMem.toFixed(1)}% {maintenanceNodes > 0 && ( {maintenanceNodes} {t('maintenance')} @@ -6224,19 +6248,19 @@
-
{onlineNodes}/{nodes.length}
+
{onlineNodes}/{totalNodeCount}
{t('nodesOnline')}
-
{avgDisk != null ? `${avgDisk.toFixed(1)}%` : 'N/A'}
+
{displayStorage.toFixed(1)}%
{t('avgStorage')}
-
{avgCpu.toFixed(1)}%
+
{displayCpu.toFixed(1)}%
{t('avgCpu')}
-
{avgMem.toFixed(1)}%
+
{displayMem.toFixed(1)}%
{t('avgRam')}
From dcf4b833a6e5115b2532123c29e64b0bad57772a Mon Sep 17 00:00:00 2001 From: Lukas Alstrup Date: Tue, 7 Apr 2026 12:24:40 +0200 Subject: [PATCH 6/6] fix: harden ClusterHealth against edge cases --- web/index.html | 13 ++++++++----- web/src/vm_modals.js | 33 +++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/web/index.html b/web/index.html index 030cd1c..ffa7c4a 100644 --- a/web/index.html +++ b/web/index.html @@ -3277,12 +3277,15 @@ useEffect(()=>{if(!xReplForm.target_cluster||!authFetch)return;let cancelled=false;const fetchTargetResources=async()=>{setXReplLoadingResources(true);setXReplTargetStorages([]);setXReplTargetBridges([]);try{const nodesRes=await authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes`);if(!nodesRes||!nodesRes.ok||cancelled)return;const nodesData=await nodesRes.json();const onlineNode=(Array.isArray(nodesData)?nodesData:nodesData.nodes||[]).find(n=>n.status==='online');if(!onlineNode||cancelled)return;const nodeName=onlineNode.node||onlineNode.name;const[storRes,netRes]=await Promise.all([authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes/${nodeName}/storage`),authFetch(`${API_URL}/clusters/${xReplForm.target_cluster}/nodes/${nodeName}/networks`)]);if(cancelled)return;if(storRes&&storRes.ok){const storData=await storRes.json();setXReplTargetStorages((Array.isArray(storData)?storData:storData.storages||[]).filter(s=>s.content&&(s.content.includes('images')||s.content.includes('rootdir'))));}if(netRes&&netRes.ok){const netData=await netRes.json();setXReplTargetBridges((Array.isArray(netData)?netData:netData.networks||[]).filter(n=>n.type==='bridge'||n.type==='OVSBridge'||n.source==='sdn'));}}catch(err){console.error('xrepl target resources:',err);}if(!cancelled)setXReplLoadingResources(false);};fetchTargetResources();return()=>{cancelled=true;};},[xReplForm.target_cluster]);// NS: Cross-cluster replication handlers const handleCreateXRepl=async()=>{try{const res=await authFetch(`${API_URL}/cross-cluster-replications`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({source_cluster:xReplForm.source_cluster,target_cluster:xReplForm.target_cluster,vmid:parseInt(xReplForm.vmid),vm_type:xReplForm.vm_type||'qemu',target_storage:xReplForm.target_storage,target_bridge:xReplForm.target_bridge,schedule:xReplForm.schedule,retention:parseInt(xReplForm.retention)||3})});if(res&&res.ok){if(addToast)addToast(t('xReplCreated')||'Replication job created','success');setShowCreateXRepl(false);setXReplForm({source_cluster:'',vmid:'',vm_type:'qemu',target_cluster:'',target_storage:'',target_bridge:'vmbr0',schedule:'0 */6 * * *',retention:3});await fetchXReplJobs();}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplCreateFailed')||'Failed to create replication job','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleDeleteXRepl=async jobId=>{if(!confirm(t('confirmDeleteXRepl')||'Delete this replication job?'))return;try{const res=await authFetch(`${API_URL}/cross-cluster-replications/${jobId}`,{method:'DELETE'});if(res&&res.ok){if(addToast)addToast(t('xReplDeleted')||'Replication job deleted','success');await fetchXReplJobs();}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplDeleteFailed')||'Failed to delete','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleRunXReplNow=async jobId=>{try{const res=await authFetch(`${API_URL}/cross-cluster-replications/${jobId}/run`,{method:'POST'});if(res&&res.ok){if(addToast)addToast(t('xReplStarted')||'Replication started','success');}else if(res){const err=await res.json();if(addToast)addToast(err.error||t('xReplStartFailed')||'Failed to start','error');}}catch(e){if(addToast)addToast(t('connectionError')||'Connection error','error');}};const handleSave=async()=>{if(!form.name.trim())return;setSaving(true);await onSave(form);setSaving(false);};// MK: tab config const tabs=[{id:'general',label:t('general')||'General',icon:Icons.Settings},{id:'lb',label:t('crossClusterLB')||'Cross-Cluster LB',icon:Icons.Scale},{id:'replication',label:t('crossClusterReplication')||'Replication',icon:Icons.Globe},{id:'info',label:t('info')||'Info',icon:Icons.Info}];return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4"},/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl w-full max-w-2xl max-h-[80vh] overflow-y-auto"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-5 border-b border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"w-10 h-10 rounded-lg flex items-center justify-center",style:{backgroundColor:(form.color||'#E86F2D')+'33'}},/*#__PURE__*/React.createElement(Icons.Folder,{className:"w-5 h-5",style:{color:form.color||'#E86F2D'}})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-semibold text-white"},t('groupSettings')||'Group Settings'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},group.name))),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-gray-400 hover:text-white transition-colors"},/*#__PURE__*/React.createElement(Icons.X,null))),/*#__PURE__*/React.createElement("div",{className:"flex border-b border-proxmox-border"},tabs.map(tab=>/*#__PURE__*/React.createElement("button",{key:tab.id,onClick:()=>setActiveTab(tab.id),className:`flex items-center gap-2 px-4 py-2.5 text-sm font-medium transition-colors whitespace-nowrap ${activeTab===tab.id?'text-proxmox-orange border-b-2 border-proxmox-orange bg-proxmox-dark/50':'text-gray-400 hover:text-white hover:bg-proxmox-dark/30'}`},/*#__PURE__*/React.createElement(tab.icon,{className:"w-4 h-4"}),tab.label))),/*#__PURE__*/React.createElement("div",{className:"p-5"},activeTab==='general'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('name')||'Name'," *"),/*#__PURE__*/React.createElement("input",{value:form.name,onChange:e=>setForm(p=>({...p,name:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange",placeholder:"Production Cluster Group"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('description')||'Description'),/*#__PURE__*/React.createElement("textarea",{value:form.description,onChange:e=>setForm(p=>({...p,description:e.target.value})),rows:3,className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange resize-none",placeholder:"Optional description for this group..."})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('color')||'Color'),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("input",{type:"color",value:form.color,onChange:e=>setForm(p=>({...p,color:e.target.value})),className:"w-10 h-10 rounded cursor-pointer border border-proxmox-border"}),/*#__PURE__*/React.createElement("input",{type:"text",value:form.color,onChange:e=>setForm(p=>({...p,color:e.target.value})),className:"w-28 px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm font-mono focus:outline-none focus:border-proxmox-orange",placeholder:"#E86F2D"}),/*#__PURE__*/React.createElement("div",{className:"flex gap-1.5"},['#E86F2D','#3B82F6','#22C55E','#EAB308','#8B5CF6','#EC4899'].map(c=>/*#__PURE__*/React.createElement("button",{key:c,onClick:()=>setForm(p=>({...p,color:c})),className:`w-6 h-6 rounded-full border-2 transition-all ${form.color===c?'border-white scale-110':'border-transparent hover:border-gray-500'}`,style:{backgroundColor:c}})))))),activeTab==='lb'&&/*#__PURE__*/React.createElement("div",{className:"space-y-5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white"},t('enableCrossClusterLB')||'Enable Cross-Cluster Load Balancing'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('lbDescription')||'Automatically migrate VMs between clusters when resource thresholds are exceeded')),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_lb_enabled,onChange:e=>setForm(p=>({...p,cross_cluster_lb_enabled:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-proxmox-orange rounded-full peer-focus:ring-2 peer-focus:ring-proxmox-orange/50 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),form.cross_cluster_lb_enabled&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex justify-end"},/*#__PURE__*/React.createElement("button",{onClick:handleXclbBalanceNow,disabled:xclbRunning,className:"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg bg-proxmox-orange/20 text-proxmox-orange hover:bg-proxmox-orange/30 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"},xclbRunning?React.createElement('span',{className:'w-3.5 h-3.5 border-2 border-proxmox-orange/40 border-t-proxmox-orange rounded-full animate-spin'}):React.createElement(Icons.RefreshCw,{className:'w-3.5 h-3.5'}),t('balanceNow')||'Balance Now')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-3 bg-yellow-500/5 border border-yellow-500/20 rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-4 h-4 text-yellow-400"}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"text-sm text-yellow-300"},t('dryRunMode')||'Dry Run / Simulation Mode'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500"},t('dryRunDesc')||'Log what would happen without actually migrating'))),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_dry_run,onChange:e=>setForm(p=>({...p,cross_cluster_dry_run:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-yellow-500 rounded-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),/*#__PURE__*/React.createElement(Slider,{label:t('cpuThreshold')||'CPU Threshold (%)',description:t('crossClusterThresholdDesc')||'CPU threshold for cluster imbalance (10-80%)',value:form.cross_cluster_threshold,onChange:v=>setForm(p=>({...p,cross_cluster_threshold:v})),min:10,max:80}),/*#__PURE__*/React.createElement(Slider,{label:t('checkInterval')||'Check Interval',description:t('crossClusterIntervalDesc')||'Time between check cycles',value:form.cross_cluster_interval,onChange:v=>setForm(p=>({...p,cross_cluster_interval:v})),min:300,max:3600,step:60,unit:"s"}),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetStorage')||'Target Storage'),loadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loadingCrossClusterResources')||'Loading...'):commonStorages.length>0?/*#__PURE__*/React.createElement("select",{value:form.cross_cluster_target_storage,onChange:e=>setForm(p=>({...p,cross_cluster_target_storage:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange"},/*#__PURE__*/React.createElement("option",{value:""},t('selectStorage')||'Select storage...'),commonStorages.map(s=>/*#__PURE__*/React.createElement("option",{key:s.storage,value:s.storage},s.storage," (",s.type,")"))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-yellow-400 py-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3 inline mr-1"}),t('noCommonStorages')||'No common storage found across all clusters'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-1"},t('commonStorageHint')||'Only storages available on all clusters')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('targetBridge')||'Target Bridge'),loadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loadingCrossClusterResources')||'Loading...'):commonBridges.length>0?/*#__PURE__*/React.createElement("select",{value:form.cross_cluster_target_bridge,onChange:e=>setForm(p=>({...p,cross_cluster_target_bridge:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white text-sm focus:outline-none focus:border-proxmox-orange"},commonBridges.filter(b=>b.source!=='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"Local Bridges"},commonBridges.filter(b=>b.source!=='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface,b.comments?` - ${b.comments}`:''))),commonBridges.filter(b=>b.source==='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"SDN VNets"},commonBridges.filter(b=>b.source==='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface," - ",b.zone||'SDN',b.alias?` (${b.alias})`:'')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-yellow-400 py-2"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3 inline mr-1"}),t('noCommonBridges')||'No common bridge found across all clusters'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-600 mt-1"},t('commonBridgeHint')||'Only bridges available on all clusters'))),/*#__PURE__*/React.createElement(Slider,{label:t('maxMigrations')||'Max Migrations per Cycle',description:t('crossClusterMaxMigrationsDesc')||'Max migrations per check cycle',value:form.cross_cluster_max_migrations,onChange:v=>setForm(p=>({...p,cross_cluster_max_migrations:v})),min:1,max:5,step:1,unit:""}),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-3 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"text-sm text-white"},t('includeContainers')||'Include Containers'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('includeContainersDesc')||'Include containers (LXC) in cross-cluster balancing'),form.cross_cluster_include_containers&&/*#__PURE__*/React.createElement("p",{className:"text-xs text-yellow-400 mt-1 flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-3 h-3"}),t('containerMigrationWarning')||'Containers are restarted during migration (downtime)')),/*#__PURE__*/React.createElement("label",{className:"relative inline-flex items-center cursor-pointer ml-4"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:form.cross_cluster_include_containers,onChange:e=>setForm(p=>({...p,cross_cluster_include_containers:e.target.checked})),className:"sr-only peer"}),/*#__PURE__*/React.createElement("div",{className:"w-11 h-6 bg-gray-600 peer-checked:bg-proxmox-orange rounded-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-full"}))),/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-1"},t('excludedVMsCrossCluster')||'Excluded VMs/Containers'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mb-3"},t('excludedVMsCrossClusterDesc')||'VMs and containers excluded from automatic cross-cluster balancing'),loadingExcludedVMs?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loading'),"..."):groupClusters.filter(c=>c.connected).length===0?/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-600 py-2"},t('noExcludedVMsInGroup')||'No VMs excluded'):/*#__PURE__*/React.createElement("div",{className:"space-y-2"},groupClusters.filter(c=>c.connected).map(cluster=>{const excluded=excludedVMsByCluster[cluster.id]||[];const allVMs=allVMsByCluster[cluster.id]||[];const excludedIds=excluded.map(v=>v.vmid);const available=allVMs.filter(vm=>!excludedIds.includes(vm.vmid));const isExpanded=expandedClusters[cluster.id];return/*#__PURE__*/React.createElement("div",{key:cluster.id,className:"border border-proxmox-border rounded-lg overflow-hidden"},/*#__PURE__*/React.createElement("button",{onClick:()=>setExpandedClusters(prev=>({...prev,[cluster.id]:!prev[cluster.id]})),className:"w-full flex items-center justify-between p-2.5 bg-proxmox-dark/50 hover:bg-proxmox-dark text-left"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Server,{className:"w-3.5 h-3.5 text-proxmox-orange"}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white"},cluster.name),excluded.length>0&&/*#__PURE__*/React.createElement("span",{className:"text-xs bg-red-500/20 text-red-400 px-1.5 py-0.5 rounded"},excluded.length)),/*#__PURE__*/React.createElement(Icons.ChevronDown,{className:`w-4 h-4 text-gray-500 transition-transform ${isExpanded?'rotate-180':''}`})),isExpanded&&/*#__PURE__*/React.createElement("div",{className:"p-2.5 space-y-2 border-t border-proxmox-border"},excluded.length>0?/*#__PURE__*/React.createElement("div",{className:"space-y-1"},excluded.map(vm=>/*#__PURE__*/React.createElement("div",{key:vm.vmid,className:"flex items-center justify-between bg-proxmox-dark rounded px-2.5 py-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Monitor,{className:"w-3.5 h-3.5 text-red-400"}),/*#__PURE__*/React.createElement("span",{className:"text-sm"},vm.name||`VM ${vm.vmid}`),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},"(",vm.vmid,")")),/*#__PURE__*/React.createElement("button",{onClick:()=>includeVM(cluster.id,vm.vmid),className:"text-xs text-green-400 hover:text-green-300"},t('include')||'Include')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-600 p-2"},t('noExcludedVMsInGroup')||'No VMs excluded'),available.length>0&&/*#__PURE__*/React.createElement("div",{className:"flex gap-2 mt-1"},/*#__PURE__*/React.createElement("select",{id:`xclb-exclude-${cluster.id}`,className:"flex-1 bg-proxmox-dark border border-proxmox-border rounded px-2 py-1.5 text-sm",defaultValue:""},/*#__PURE__*/React.createElement("option",{value:"",disabled:true},t('selectVMToExclude')||'Select VM to exclude...'),available.map(vm=>/*#__PURE__*/React.createElement("option",{key:vm.vmid,value:vm.vmid},vm.name||`VM ${vm.vmid}`," (",vm.vmid,") - ",vm.node))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const select=document.getElementById(`xclb-exclude-${cluster.id}`);const vmid=parseInt(select?.value);if(!vmid)return;const vm=available.find(v=>v.vmid===vmid);excludeVM(cluster.id,vmid,vm?.name);select.value='';},className:"px-2.5 py-1.5 bg-red-500/20 text-red-400 rounded hover:bg-red-500/30 text-xs flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.Ban,{className:"w-3.5 h-3.5"}),t('exclude')||'Exclude'))));}))))),activeTab==='replication'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white flex items-center gap-2"},/*#__PURE__*/React.createElement(Icons.Globe,{className:"w-4 h-4 text-proxmox-orange"}),t('crossClusterReplication')||'Cross-Cluster Replication'),/*#__PURE__*/React.createElement("p",{className:"text-xs text-gray-500 mt-0.5"},t('crossClusterReplicationDesc')||'Replicate VM snapshots to another cluster (DR)')),groupClusters.length>=2&&/*#__PURE__*/React.createElement("button",{onClick:()=>setShowCreateXRepl(true),className:"flex items-center gap-1.5 px-3 py-1.5 bg-proxmox-orange/10 text-proxmox-orange rounded-lg text-xs hover:bg-proxmox-orange/20 transition-colors"},/*#__PURE__*/React.createElement(Icons.Plus,{className:"w-3.5 h-3.5"}),t('addDrJob')||'Add DR Job')),groupClusters.length<2?/*#__PURE__*/React.createElement("div",{className:"text-center py-8 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.AlertTriangle,{className:"w-8 h-8 mx-auto mb-2 text-gray-600"}),t('needTwoClusters')||'At least 2 clusters needed for cross-cluster replication'):xReplLoading?/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 py-8 text-gray-500 text-sm"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"}),t('loading'),"..."):xReplJobs.length===0&&!showCreateXRepl?/*#__PURE__*/React.createElement("div",{className:"text-center py-8 text-gray-500 text-sm"},t('noReplicationJobs')||'No replication jobs configured'):/*#__PURE__*/React.createElement("div",{className:"space-y-2"},xReplJobs.map(job=>{const srcCluster=groupClusters.find(c=>c.id===job.source_cluster);const tgtCluster=groupClusters.find(c=>c.id===job.target_cluster);return/*#__PURE__*/React.createElement("div",{key:job.id,className:"bg-proxmox-dark rounded-lg p-3 flex items-center justify-between border border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex-1 min-w-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-wrap"},/*#__PURE__*/React.createElement("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${job.enabled?'bg-green-500':'bg-gray-500'}`}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white truncate"},srcCluster?.name||job.source_cluster),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3.5 h-3.5 text-gray-500 flex-shrink-0"}),/*#__PURE__*/React.createElement("span",{className:"text-sm text-white truncate"},tgtCluster?.name||job.target_cluster),/*#__PURE__*/React.createElement("span",{className:"text-xs bg-proxmox-dark px-1.5 py-0.5 rounded text-gray-400 border border-proxmox-border"},job.vm_type==='lxc'?'CT':'VM'," ",job.vmid)),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 mt-1 flex items-center gap-2 flex-wrap"},/*#__PURE__*/React.createElement("span",null,job.schedule),/*#__PURE__*/React.createElement("span",null,"\xB7"),/*#__PURE__*/React.createElement("span",null,job.target_storage||'default'),job.last_run&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("span",null,"\xB7"),/*#__PURE__*/React.createElement("span",null,t('lastRunPrefix')||'Last',": ",new Date(job.last_run).toLocaleString())),job.last_status&&/*#__PURE__*/React.createElement("span",{className:job.last_status==='OK'?'text-green-400':'text-red-400'},job.last_status),job.last_error&&/*#__PURE__*/React.createElement("span",{className:"text-red-400"},job.last_error))),/*#__PURE__*/React.createElement("div",{className:"flex gap-1 flex-shrink-0 ml-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>handleRunXReplNow(job.id),className:"p-1.5 rounded hover:bg-green-500/10 text-gray-400 hover:text-green-400",title:t('runNow')||'Run now'},/*#__PURE__*/React.createElement(Icons.Play,{className:"w-3.5 h-3.5"})),/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteXRepl(job.id),className:"p-1.5 rounded hover:bg-red-500/10 text-gray-400 hover:text-red-400",title:t('delete')||'Delete'},/*#__PURE__*/React.createElement(Icons.Trash,{className:"w-3.5 h-3.5"}))));})),showCreateXRepl&&groupClusters.length>=2&&/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-dark border border-proxmox-border rounded-lg p-4"},/*#__PURE__*/React.createElement("h5",{className:"text-sm font-medium text-white mb-3"},t('newCrossClusterReplication')||'New Cross-Cluster Replication'),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('sourceCluster')||'Source Cluster'),/*#__PURE__*/React.createElement("select",{value:xReplForm.source_cluster,onChange:e=>setXReplForm(f=>({...f,source_cluster:e.target.value,vmid:'',vm_type:'qemu'})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectCluster')||'Select cluster...'),groupClusters.filter(c=>c.connected).map(c=>/*#__PURE__*/React.createElement("option",{key:c.id,value:c.id},c.name)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},"VM"),xReplLoadingVMs?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.source_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.vmid,onChange:e=>{const vmid=e.target.value;const vm=xReplSourceVMs.find(v=>String(v.vmid)===vmid);setXReplForm(f=>({...f,vmid,vm_type:vm?.type||'qemu'}));},className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectVM')||'Select VM...'),xReplSourceVMs.map(vm=>/*#__PURE__*/React.createElement("option",{key:vm.vmid,value:vm.vmid},vm.name||`VM ${vm.vmid}`," (",vm.vmid,") - ",vm.type==='lxc'?'CT':'VM'))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a source cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetCluster')||'Target Cluster'),/*#__PURE__*/React.createElement("select",{value:xReplForm.target_cluster,onChange:e=>setXReplForm(f=>({...f,target_cluster:e.target.value,target_storage:'',target_bridge:'vmbr0'})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectCluster')||'Select cluster...'),groupClusters.filter(c=>c.connected&&c.id!==xReplForm.source_cluster).map(c=>/*#__PURE__*/React.createElement("option",{key:c.id,value:c.id},c.name)))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetStorage')||'Target Storage'),xReplLoadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.target_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.target_storage,onChange:e=>setXReplForm(f=>({...f,target_storage:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},/*#__PURE__*/React.createElement("option",{value:""},t('selectStorage')||'Select storage...'),xReplTargetStorages.map(s=>/*#__PURE__*/React.createElement("option",{key:s.storage,value:s.storage},s.storage," (",s.type,")"))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a target cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('targetBridge')||'Target Bridge'),xReplLoadingResources?/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-gray-500 text-sm py-2"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-3.5 h-3.5 animate-spin"}),t('loading'),"..."):xReplForm.target_cluster?/*#__PURE__*/React.createElement("select",{value:xReplForm.target_bridge,onChange:e=>setXReplForm(f=>({...f,target_bridge:e.target.value})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"},xReplTargetBridges.filter(b=>b.source!=='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"Local Bridges"},xReplTargetBridges.filter(b=>b.source!=='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface,b.comments?` - ${b.comments}`:''))),xReplTargetBridges.filter(b=>b.source==='sdn').length>0&&/*#__PURE__*/React.createElement("optgroup",{label:"SDN VNets"},xReplTargetBridges.filter(b=>b.source==='sdn').map(b=>/*#__PURE__*/React.createElement("option",{key:b.iface,value:b.iface},b.iface," - ",b.zone||'SDN',b.alias?` (${b.alias})`:'')))):/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 py-2"},t('selectClusterFirst')||'Select a target cluster first')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('scheduleCron')||'Schedule (Cron)'),/*#__PURE__*/React.createElement("input",{type:"text",value:xReplForm.schedule,onChange:e=>setXReplForm(f=>({...f,schedule:e.target.value})),placeholder:"0 */6 * * *",className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('replicationRetention')||'Retention'),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",max:"30",value:xReplForm.retention,onChange:e=>setXReplForm(f=>({...f,retention:parseInt(e.target.value)||1})),className:"w-full px-3 py-2 bg-proxmox-darker border border-proxmox-border rounded-lg text-white text-sm"}))),/*#__PURE__*/React.createElement("div",{className:"flex gap-2 mt-3"},/*#__PURE__*/React.createElement("button",{onClick:handleCreateXRepl,disabled:!xReplForm.source_cluster||!xReplForm.vmid||!xReplForm.target_cluster,className:"px-3 py-1.5 bg-proxmox-orange text-white rounded-lg text-sm hover:bg-proxmox-orange/90 transition-colors disabled:opacity-50"},t('create')||'Create'),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowCreateXRepl(false),className:"px-3 py-1.5 bg-proxmox-dark border border-proxmox-border text-gray-300 rounded-lg text-sm hover:bg-proxmox-darker transition-colors"},t('cancel')||'Cancel'))),Object.keys(nativeReplByCluster).length>0&&/*#__PURE__*/React.createElement("div",{className:"mt-6"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mb-3"},/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 text-purple-400"}),/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white"},t('nativeProxmoxReplication')||'Native Proxmox Replication (ZFS)')),/*#__PURE__*/React.createElement("div",{className:"space-y-3"},Object.entries(nativeReplByCluster).map(([cid,jobs])=>{const cluster=groupClusters.find(c=>c.id===cid);return/*#__PURE__*/React.createElement("div",{key:cid,className:"bg-proxmox-dark rounded-lg border border-proxmox-border overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"px-3 py-2 border-b border-proxmox-border bg-purple-500/5"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-medium text-purple-300"},cluster?.name||cid)),jobs.map((job,idx)=>{const hasErr=job.fail_count>0||job.error;const lastSync=job.last_sync?new Date(job.last_sync*1000).toLocaleString():'-';return/*#__PURE__*/React.createElement("div",{key:job.id||idx,className:"px-3 py-2 flex items-center justify-between border-b border-proxmox-border/50 last:border-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-wrap min-w-0"},/*#__PURE__*/React.createElement("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${job.disable?'bg-gray-500':hasErr?'bg-red-500':'bg-green-500'}`}),/*#__PURE__*/React.createElement("span",{className:"text-xs bg-proxmox-dark px-1.5 py-0.5 rounded text-gray-400 border border-proxmox-border"},"VM ",job.guest),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},job.source||'?'),/*#__PURE__*/React.createElement(Icons.ArrowRight,{className:"w-3 h-3 text-gray-600 flex-shrink-0"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-500"},job.target),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600 font-mono"},job.schedule)),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 flex-shrink-0 ml-2"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600"},lastSync),job.duration!=null&&/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-600 font-mono"},job.duration.toFixed(1),"s")));}));})))),activeTab==='info'&&/*#__PURE__*/React.createElement("div",{className:"space-y-4"},/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-3"},t('groupInfo')||'Group Information'),/*#__PURE__*/React.createElement("div",{className:"space-y-2 text-sm"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('groupId')||'Group ID'),/*#__PURE__*/React.createElement("span",{className:"text-white font-mono text-xs"},group.id)),/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('created')||'Created'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.created?new Date(group.created).toLocaleString():'-')),group.tenant_id&&/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('tenant')||'Tenant'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.tenant_id)))),group.cross_cluster_lb_enabled&&/*#__PURE__*/React.createElement("div",{className:"p-4 bg-proxmox-dark rounded-lg border border-proxmox-border"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-medium text-white mb-3"},t('lbStatus')||'Load Balancing Status'),/*#__PURE__*/React.createElement("div",{className:"space-y-2 text-sm"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('lastRun')||'Last LB Run'),/*#__PURE__*/React.createElement("span",{className:"text-white"},group.last_lb_run?new Date(group.last_lb_run).toLocaleString():t('neverRun')||'Never run')),/*#__PURE__*/React.createElement("div",{className:"flex justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-gray-400"},t('mode')||'Mode'),/*#__PURE__*/React.createElement("span",{className:group.cross_cluster_dry_run?'text-yellow-400':'text-green-400'},group.cross_cluster_dry_run?t('simulation')||'Simulation':t('active')||'Active')))),/*#__PURE__*/React.createElement("div",{className:"p-4 bg-blue-500/5 border border-blue-500/20 rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-start gap-3"},/*#__PURE__*/React.createElement(Icons.Info,{className:"w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5"}),/*#__PURE__*/React.createElement("div",{className:"text-sm text-gray-400"},/*#__PURE__*/React.createElement("p",{className:"mb-2"},t('lbExplanation')||'Cross-Cluster Load Balancing monitors CPU and RAM usage across all clusters in this group. When a cluster exceeds the configured threshold, VMs are automatically migrated to a less loaded cluster.'),/*#__PURE__*/React.createElement("p",null,t('lbDryRunExplanation')||'Enable Dry Run mode first to review what actions would be taken before enabling live migrations.')))))),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-end gap-3 p-5 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-sm text-gray-400 hover:text-white transition-colors"},t('cancel')||'Cancel'),/*#__PURE__*/React.createElement("button",{onClick:handleSave,disabled:saving||!form.name.trim(),className:"px-5 py-2 bg-proxmox-orange hover:bg-orange-600 disabled:opacity-50 rounded-lg text-sm font-medium text-white transition-colors flex items-center gap-2"},saving?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement(Icons.RefreshCw,{className:"w-4 h-4 animate-spin"})," ",t('saving')||'Saving...'):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement(Icons.Save,{className:"w-4 h-4"})," ",t('save')||'Save')))));}// Cluster Health Widget +// Note: ClusterHealth only renders for the connected selected cluster; +// disconnected clusters switch away from this view, so no connected guard is needed. function ClusterHealth({metrics,clusterStatus,isCorporate}){const{t}=useTranslation();const nodes=Object.entries(metrics).filter(([k,m])=>m&&typeof m==='object'&&k!=='error'&&k!=='offline');if(nodes.length===0&&!clusterStatus)return null;// Node status from per-node metrics (has maintenance_mode info not in datacenter status) -const onlineNodes=nodes.filter(([,m])=>m.status==='online'&&!m.maintenance_mode).length;const maintenanceNodes=nodes.filter(([,m])=>m.maintenance_mode).length;// Real-time display values from per-node SSE metrics (~1s updates) -const displayCpu=nodes.length>0?nodes.reduce((acc,[,m])=>acc+(m.cpu_percent??0),0)/nodes.length:0;const displayMem=nodes.length>0?nodes.reduce((acc,[,m])=>acc+(m.mem_percent??0),0)/nodes.length:0;// Health score inputs from cluster-level datacenter/status (same source as badge/overview) -let healthCpu,healthMem,healthStorage,offlineRatio,totalNodeCount;if(clusterStatus&&clusterStatus.resources){const resources=clusterStatus.resources;healthCpu=resources.cpu?.percent||0;healthMem=resources.memory?.percent||0;healthStorage=resources.storage?.percent||0;totalNodeCount=clusterStatus.nodes?.total||nodes.length;const offlineNodes=clusterStatus.nodes?.offline||0;offlineRatio=totalNodeCount>0?offlineNodes/totalNodeCount*100:0;}else{// Fallback: per-node data (when datacenter status not yet loaded) -healthCpu=displayCpu;healthMem=displayMem;const diskNodes=nodes.filter(([,m])=>m.disk_percent!=null);healthStorage=diskNodes.length>0?diskNodes.reduce((acc,[,m])=>acc+m.disk_percent,0)/diskNodes.length:0;totalNodeCount=nodes.length;const offlineNodes=nodes.length-onlineNodes-maintenanceNodes;offlineRatio=totalNodeCount>0?offlineNodes/totalNodeCount*100:0;}// Storage display: use cluster-level storage pools (not rootfs) when available -const displayStorage=clusterStatus?.resources?.storage?.percent??(nodes.length>0?(()=>{const dn=nodes.filter(([,m])=>m.disk_percent!=null);return dn.length>0?dn.reduce((acc,[,m])=>acc+m.disk_percent,0)/dn.length:0;})():0);// Unified formula - same weights as badge/overview (CPU 30%, RAM 30%, Storage 20%, Offline 20%) +const onlineNodes=nodes.length>0?nodes.filter(([,m])=>m.status==='online'&&!m.maintenance_mode).length:clusterStatus?.nodes?.online??0;const maintenanceNodes=nodes.filter(([,m])=>m.maintenance_mode).length;// Pre-compute disk nodes for fallback paths (reused below) +const diskNodes=nodes.filter(([,m])=>m.disk_percent!=null);const avgDiskPercent=diskNodes.length>0?diskNodes.reduce((acc,[,m])=>acc+(Number(m.disk_percent)||0),0)/diskNodes.length:0;// Real-time display values from per-node SSE metrics (~1s updates), fall back to clusterStatus +const displayCpu=nodes.length>0?nodes.reduce((acc,[,m])=>acc+(Number(m.cpu_percent)||0),0)/nodes.length:clusterStatus?.resources?.cpu?.percent??0;const displayMem=nodes.length>0?nodes.reduce((acc,[,m])=>acc+(Number(m.mem_percent)||0),0)/nodes.length:clusterStatus?.resources?.memory?.percent??0;// Health score inputs from cluster-level datacenter/status (same source as badge/overview) +let healthCpu,healthMem,healthStorage,offlineRatio,totalNodeCount;if(clusterStatus&&clusterStatus.resources){const resources=clusterStatus.resources;healthCpu=resources.cpu?.percent??0;healthMem=resources.memory?.percent??0;healthStorage=resources.storage?.percent??0;totalNodeCount=clusterStatus.nodes?.total??nodes.length;const offlineNodes=clusterStatus.nodes?.offline??0;offlineRatio=totalNodeCount>0?offlineNodes/totalNodeCount*100:0;}else{// Fallback: per-node data (when datacenter status not yet loaded) +healthCpu=displayCpu;healthMem=displayMem;healthStorage=avgDiskPercent;totalNodeCount=nodes.length;const offlineNodes=nodes.length-onlineNodes-maintenanceNodes;offlineRatio=totalNodeCount>0?offlineNodes/totalNodeCount*100:0;}// Storage display: use cluster-level storage pools (not rootfs) when available +const displayStorage=clusterStatus?.resources?.storage?.percent??avgDiskPercent;// Unified formula - same weights as badge/overview (CPU 30%, RAM 30%, Storage 20%, Offline 20%) const healthScore=Math.max(0,100-(healthCpu*0.3+healthMem*0.3+healthStorage*0.2+offlineRatio*0.2));const healthLabel=healthScore>=80?t('excellent'):healthScore>=60?t('good'):healthScore>=40?t('warning'):t('critical');const healthColor=healthScore>=80?'#22c55e':healthScore>=60?'#eab308':healthScore>=40?'#f97316':'#ef4444';// LW: Feb 2026 - corporate compact variant (Clarity dark theme) const corpHealthColor=healthScore>=80?'#60b515':healthScore>=60?'#60b515':healthScore>=40?'#efc006':'#f54f47';if(isCorporate){return/*#__PURE__*/React.createElement("div",{className:"p-3",style:{background:'var(--corp-header-bg)',border:'1px solid var(--corp-border-medium)'},title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-[11px] font-semibold uppercase tracking-wider mb-2",style:{color:'var(--corp-text-muted)'}},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4 flex-wrap"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-20 h-2 overflow-hidden",style:{background:'var(--corp-bar-track)',borderRadius:'1px'}},/*#__PURE__*/React.createElement("div",{className:"h-full",style:{width:`${healthScore}%`,backgroundColor:corpHealthColor,borderRadius:'1px'}})),/*#__PURE__*/React.createElement("span",{className:"text-[13px] font-medium",style:{color:'var(--color-text)'}},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-[11px]",style:{color:'var(--corp-text-muted)'}},healthLabel)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},t('nodesOnline'),": ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},onlineNodes,"/",totalNodeCount)),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"CPU: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},displayCpu.toFixed(1),"%")),/*#__PURE__*/React.createElement("span",{className:"text-[12px]",style:{color:'var(--corp-text-secondary)'}},"RAM: ",/*#__PURE__*/React.createElement("span",{style:{color:'var(--color-text)'}},displayMem.toFixed(1),"%")),maintenanceNodes>0&&/*#__PURE__*/React.createElement("span",{className:"text-[12px] flex items-center gap-1",style:{color:'var(--color-warning)'}},/*#__PURE__*/React.createElement(Icons.Wrench,{className:"w-3 h-3"})," ",maintenanceNodes," ",t('maintenance'))));}return/*#__PURE__*/React.createElement("div",{className:"bg-proxmox-card border border-proxmox-border rounded-xl p-5",title:t('clusterHealthTooltip')},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4"},t('clusterHealth')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center mb-6"},/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement("svg",{viewBox:"0 0 100 100",className:"w-32 h-32"},/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#30363D",strokeWidth:"8"}),/*#__PURE__*/React.createElement("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:healthColor,strokeWidth:"8",strokeLinecap:"round",strokeDasharray:`${healthScore*2.83} 283`,transform:"rotate(-90 50 50)",className:"transition-all duration-1000"})),/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 flex flex-col items-center justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-2xl font-bold text-white"},healthScore.toFixed(0)),/*#__PURE__*/React.createElement("span",{className:"text-xs text-gray-400"},healthLabel)))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},onlineNodes,"/",totalNodeCount),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('nodesOnline'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},displayStorage.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgStorage'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},displayCpu.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgCpu'))),/*#__PURE__*/React.createElement("div",{className:"text-center"},/*#__PURE__*/React.createElement("div",{className:"text-2xl font-bold text-white"},displayMem.toFixed(1),"%"),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500"},t('avgRam')))),maintenanceNodes>0&&/*#__PURE__*/React.createElement("div",{className:"mt-4 pt-4 border-t border-proxmox-border"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-center gap-2 text-yellow-400"},/*#__PURE__*/React.createElement(Icons.Wrench,null),/*#__PURE__*/React.createElement("span",{className:"text-sm font-medium"},maintenanceNodes," Node(s) ",t('maintenance')))));}// Create Snapshot Modal - NS: Feb 2026 enhanced with efficient mode function CreateSnapshotModal({isQemu,onSubmit,onClose,loading,efficientInfo}){const{t}=useTranslation();const[snapname,setSnapname]=useState(`snap_${Date.now()}`);const[description,setDescription]=useState('');const[vmstate,setVmstate]=useState(false);const[mode,setMode]=useState('standard');const[snapSizeGb,setSnapSizeGb]=useState(efficientInfo?.recommended_snap_size_gb||10);const isEfficient=mode==='efficient';const canEfficient=efficientInfo?.eligible;const handleSubmit=()=>{if(!snapname.trim())return;onSubmit(snapname.trim(),description,vmstate,isEfficient?{mode:'efficient',snap_size_gb:snapSizeGb}:{mode:'standard'});};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/60"},/*#__PURE__*/React.createElement("div",{className:`w-full ${canEfficient?'max-w-lg':'max-w-md'} bg-proxmox-card border border-proxmox-border rounded-xl p-6 animate-scale-in`},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-semibold text-white mb-4"},t('createSnapshot')),/*#__PURE__*/React.createElement("div",{className:"space-y-4"},canEfficient&&/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-2"},t('snapshotMode')),/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setMode('standard'),className:`flex-1 px-3 py-2 rounded-lg text-sm border transition-colors ${!isEfficient?'bg-blue-600/20 border-blue-500 text-blue-400':'bg-proxmox-dark border-proxmox-border text-gray-400 hover:border-gray-500'}`},t('normalMode')),/*#__PURE__*/React.createElement("button",{onClick:()=>setMode('efficient'),className:`flex-1 px-3 py-2 rounded-lg text-sm border transition-colors ${isEfficient?'bg-green-600/20 border-green-500 text-green-400':'bg-proxmox-dark border-proxmox-border text-gray-400 hover:border-gray-500'}`},/*#__PURE__*/React.createElement("span",{className:"flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement(Icons.Zap,null),t('efficientMode'))))),isEfficient&&efficientInfo&&/*#__PURE__*/React.createElement("div",{className:"p-3 bg-proxmox-dark rounded-lg border border-green-500/30 space-y-3"},/*#__PURE__*/React.createElement("div",{className:"text-sm font-medium text-green-400 flex items-center gap-1"},/*#__PURE__*/React.createElement(Icons.Zap,null),t('spaceSavings'),": ",efficientInfo.savings_percent,"%"),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs text-gray-400 mb-1"},/*#__PURE__*/React.createElement("span",null,t('normalSnapshotSize')),/*#__PURE__*/React.createElement("span",null,efficientInfo.total_disk_size_gb?.toFixed(1)," GB")),/*#__PURE__*/React.createElement("div",{className:"w-full h-3 bg-gray-700 rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"h-full bg-red-500 rounded-full",style:{width:'100%'}}))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs text-gray-400 mb-1"},/*#__PURE__*/React.createElement("span",null,t('efficientSnapshotSize')),/*#__PURE__*/React.createElement("span",null,"~",snapSizeGb?.toFixed(1)," GB")),/*#__PURE__*/React.createElement("div",{className:"w-full h-3 bg-gray-700 rounded-full overflow-hidden"},/*#__PURE__*/React.createElement("div",{className:"h-full bg-green-500 rounded-full",style:{width:`${Math.max(3,snapSizeGb/efficientInfo.total_disk_size_gb*100)}%`}}))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-xs text-gray-400 mb-1"},t('snapshotSizeGb')," (",t('recommended'),": ",efficientInfo.recommended_snap_size_gb?.toFixed(1)," GB)"),/*#__PURE__*/React.createElement("input",{type:"number",value:snapSizeGb,onChange:e=>setSnapSizeGb(parseFloat(e.target.value)||1),min:"1",max:efficientInfo.vg_free_gb-2,step:"1",className:"w-full px-3 py-1.5 bg-proxmox-card border border-proxmox-border rounded-lg text-white text-sm"})),/*#__PURE__*/React.createElement("div",{className:`text-xs flex items-center gap-1 ${efficientInfo.has_guest_agent?'text-green-400':'text-yellow-400'}`},efficientInfo.has_guest_agent?/*#__PURE__*/React.createElement(Icons.CheckCircle,null):/*#__PURE__*/React.createElement(Icons.AlertTriangle,null),efficientInfo.has_guest_agent?t('guestAgentDetected'):t('noGuestAgent')),/*#__PURE__*/React.createElement("div",{className:"text-xs text-gray-500 italic"},t('managedByPegaprox'))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('name')),/*#__PURE__*/React.createElement("input",{type:"text",value:snapname,onChange:e=>setSnapname(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:"snapshot-name"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-sm text-gray-400 mb-1"},t('description')," (",t('optional'),")"),/*#__PURE__*/React.createElement("input",{type:"text",value:description,onChange:e=>setDescription(e.target.value),className:"w-full px-3 py-2 bg-proxmox-dark border border-proxmox-border rounded-lg text-white",placeholder:t('snapshotDescription')})),isQemu&&!isEfficient&&/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-2 text-sm text-gray-300"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:vmstate,onChange:e=>setVmstate(e.target.checked),className:"rounded"}),t('saveRamState'))),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-3 mt-6"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-2 text-gray-300 hover:text-white"},t('cancel')),/*#__PURE__*/React.createElement("button",{onClick:handleSubmit,disabled:loading||!snapname.trim(),className:"flex items-center gap-2 px-4 py-2 rounded-lg text-white disabled:opacity-50 bg-green-600 hover:bg-green-700"},loading&&/*#__PURE__*/React.createElement(Icons.RotateCw,null),isEfficient&&/*#__PURE__*/React.createElement(Icons.Zap,null),t('create')))));}// Create Replication Modal diff --git a/web/src/vm_modals.js b/web/src/vm_modals.js index 93a0539..3e14bea 100644 --- a/web/src/vm_modals.js +++ b/web/src/vm_modals.js @@ -6146,45 +6146,50 @@ } // Cluster Health Widget + // Note: ClusterHealth only renders for the connected selected cluster; + // disconnected clusters switch away from this view, so no connected guard is needed. function ClusterHealth({ metrics, clusterStatus, isCorporate }) { const { t } = useTranslation(); const nodes = Object.entries(metrics).filter(([k, m]) => m && typeof m === 'object' && k !== 'error' && k !== 'offline'); if (nodes.length === 0 && !clusterStatus) return null; // Node status from per-node metrics (has maintenance_mode info not in datacenter status) - const onlineNodes = nodes.filter(([, m]) => m.status === 'online' && !m.maintenance_mode).length; + const onlineNodes = nodes.length > 0 + ? nodes.filter(([, m]) => m.status === 'online' && !m.maintenance_mode).length + : (clusterStatus?.nodes?.online ?? 0); const maintenanceNodes = nodes.filter(([, m]) => m.maintenance_mode).length; - // Real-time display values from per-node SSE metrics (~1s updates) - const displayCpu = nodes.length > 0 ? nodes.reduce((acc, [, m]) => acc + (m.cpu_percent ?? 0), 0) / nodes.length : 0; - const displayMem = nodes.length > 0 ? nodes.reduce((acc, [, m]) => acc + (m.mem_percent ?? 0), 0) / nodes.length : 0; + // Pre-compute disk nodes for fallback paths (reused below) + const diskNodes = nodes.filter(([, m]) => m.disk_percent != null); + const avgDiskPercent = diskNodes.length > 0 ? diskNodes.reduce((acc, [, m]) => acc + (Number(m.disk_percent) || 0), 0) / diskNodes.length : 0; + + // Real-time display values from per-node SSE metrics (~1s updates), fall back to clusterStatus + const displayCpu = nodes.length > 0 ? nodes.reduce((acc, [, m]) => acc + (Number(m.cpu_percent) || 0), 0) / nodes.length : (clusterStatus?.resources?.cpu?.percent ?? 0); + const displayMem = nodes.length > 0 ? nodes.reduce((acc, [, m]) => acc + (Number(m.mem_percent) || 0), 0) / nodes.length : (clusterStatus?.resources?.memory?.percent ?? 0); // Health score inputs from cluster-level datacenter/status (same source as badge/overview) let healthCpu, healthMem, healthStorage, offlineRatio, totalNodeCount; if (clusterStatus && clusterStatus.resources) { const resources = clusterStatus.resources; - healthCpu = resources.cpu?.percent || 0; - healthMem = resources.memory?.percent || 0; - healthStorage = resources.storage?.percent || 0; - totalNodeCount = clusterStatus.nodes?.total || nodes.length; - const offlineNodes = clusterStatus.nodes?.offline || 0; + healthCpu = resources.cpu?.percent ?? 0; + healthMem = resources.memory?.percent ?? 0; + healthStorage = resources.storage?.percent ?? 0; + totalNodeCount = clusterStatus.nodes?.total ?? nodes.length; + const offlineNodes = clusterStatus.nodes?.offline ?? 0; offlineRatio = totalNodeCount > 0 ? (offlineNodes / totalNodeCount) * 100 : 0; } else { // Fallback: per-node data (when datacenter status not yet loaded) healthCpu = displayCpu; healthMem = displayMem; - const diskNodes = nodes.filter(([, m]) => m.disk_percent != null); - healthStorage = diskNodes.length > 0 ? diskNodes.reduce((acc, [, m]) => acc + m.disk_percent, 0) / diskNodes.length : 0; + healthStorage = avgDiskPercent; totalNodeCount = nodes.length; const offlineNodes = nodes.length - onlineNodes - maintenanceNodes; offlineRatio = totalNodeCount > 0 ? (offlineNodes / totalNodeCount) * 100 : 0; } // Storage display: use cluster-level storage pools (not rootfs) when available - const displayStorage = clusterStatus?.resources?.storage?.percent ?? (nodes.length > 0 - ? (() => { const dn = nodes.filter(([, m]) => m.disk_percent != null); return dn.length > 0 ? dn.reduce((acc, [, m]) => acc + m.disk_percent, 0) / dn.length : 0; })() - : 0); + const displayStorage = clusterStatus?.resources?.storage?.percent ?? avgDiskPercent; // Unified formula - same weights as badge/overview (CPU 30%, RAM 30%, Storage 20%, Offline 20%) const healthScore = Math.max(0, 100 - (healthCpu * 0.3 + healthMem * 0.3 + healthStorage * 0.2 + offlineRatio * 0.2));