diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css index 87ee533..23a405a 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.css @@ -175,6 +175,22 @@ margin-bottom: 20px; } +.selected-disk-hint { + display: flex; + align-items: center; + gap: 8px; + color: #2e7d32; + font-weight: 500; + font-size: 14px; +} + +.hint-icon { + font-size: 16px; + width: 16px; + height: 16px; + color: #4caf50; +} + /* Opciones del select */ ::ng-deep .disk-option { display: flex; @@ -232,7 +248,9 @@ background: #e8f5e8; border: 1px solid #c8e6c9; border-radius: 12px; - margin-top: 16px; + margin-top: 0; + flex-shrink: 0; + min-width: 250px; } .info-icon { @@ -1126,7 +1144,6 @@ } } -/* ===== ANIMACIONES ===== */ .progress-segment { animation: slideIn 0.3s ease-out; } @@ -1157,8 +1174,7 @@ } } -/* ===== ESTADOS DE ADVERTENCIA ===== */ -/* Advertencia (90% a 99% usado) */ + .warning { color: #ff9800 !important; } @@ -1171,7 +1187,6 @@ color: #ff9800 !important; } -/* Peligro (100% o más usado) */ .danger { color: #f44336 !important; font-weight: bold !important; @@ -1199,7 +1214,6 @@ } } -/* ===== INSTRUCCIONES ===== */ .instructions-box { margin-top: 15px; background-color: #f5f5f5; @@ -1228,7 +1242,6 @@ line-height: 1.5; } -/* ===== RESPONSIVE ===== */ @media (max-width: 768px) { .header-container { flex-direction: column; @@ -1292,6 +1305,72 @@ } } +.partition-validation-indicator { + margin: 16px 0; + padding: 16px 20px; + border-radius: 12px; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.validation-status { + display: flex; + align-items: center; + gap: 12px; + font-size: 14px; + font-weight: 500; + padding: 12px 16px; + border-radius: 8px; + border: 2px solid; +} + +.validation-status.loading { + color: #1976d2; + background: #e3f2fd; + border-color: #bbdefb; +} + +.validation-status.success { + color: #2e7d32; + background: #e8f5e8; + border-color: #4caf50; +} + +.validation-status.error { + color: #d32f2f; + background: #ffebee; + border-color: #f44336; +} + +.validation-icon { + font-size: 20px; + width: 20px; + height: 20px; +} + +.validation-icon.loading { + color: #1976d2; + animation: spin 1s linear infinite; +} + +.validation-icon.success { + color: #2e7d32; +} + +.validation-icon.error { + color: #d32f2f; +} + +.validation-message { + flex: 1; + margin-left: 8px; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html index eadd905..74e023b 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.html @@ -16,7 +16,7 @@
- +
@@ -26,7 +26,7 @@
-
@@ -51,7 +51,7 @@
- Selecciona el disco que deseas particionar + Selecciona el disco que deseas particionar + + check_circle + Disco {{ selectedDisk.diskNumber }} seleccionado - {{ (selectedDisk.totalDiskSize / 1024).toFixed(2) }} GB + -
- info -
- Disco seleccionado: {{ selectedDisk.diskNumber }} - Tamaño total: {{ (selectedDisk.totalDiskSize / 1024).toFixed(2) }} GB -
-
-
warning
@@ -207,6 +203,16 @@
+ +
+
+ hourglass_empty + check_circle + error + {{ partitionValidationMessage }} +
+
+ diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts index eed1294..795b3d1 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts @@ -63,6 +63,12 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes partitionCode: string = ''; generatedInstructions: string = ''; + // Propiedades para validación de particiones + partitionValidationStatus: 'idle' | 'loading' | 'success' | 'error' = 'idle'; + partitionValidationMessage: string = ''; + private validationDebounceTime = 500; // ms + private validationSubject = new Subject(); + // Columnas para mat-table displayedColumns: string[] = ['partitionNumber', 'partitionCode', 'filesystem', 'size', 'percentage', 'format', 'actions']; @@ -95,10 +101,22 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null; this.clientData.forEach((client: { selected: boolean; status: string}) => { client.selected = true; }); - this.selectedClients = this.clientData.filter((client: { selected: boolean; status: string}) => client.selected); + this.selectedClients = this.clientData.filter( + (client: { selected: boolean; status: string }) => client.selected + ); + + if (this.selectedClients.length === 0 && this.clientData.length > 0) { + this.selectedClients = [this.clientData[0]]; + this.clientData[0].selected = true; + } this.selectedModelClient = this.clientData.find((client: { selected: boolean; status: string}) => client.selected) || null; + if (!this.selectedModelClient && this.clientData.length > 0) { + this.selectedModelClient = this.clientData[0]; + this.clientData[0].selected = true; + } + if (this.selectedModelClient) { this.loadPartitions(this.selectedModelClient); } @@ -115,6 +133,13 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes ).subscribe(() => { this.resizeChart(); }); + + this.validationSubject.pipe( + takeUntil(this.destroy$), + debounceTime(this.validationDebounceTime) + ).subscribe(() => { + this.validatePartitionSizes(); + }); } ngAfterViewInit(): void { @@ -293,7 +318,14 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes } updateSelectedClients() { - this.selectedClients = this.clientData.filter((client: { selected: any; }) => client.selected); + this.selectedClients = this.clientData.filter( + (client: { selected: boolean; status: string }) => client.selected + ); + + if (this.selectedClients.length === 0 && this.clientData.length > 0) { + this.selectedClients = [this.clientData[0]]; + this.clientData[0].selected = true; + } } getPartitionsTooltip(client: any): string { @@ -333,7 +365,8 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); this.updateDiskChart(disk); - // Redimensionar el gráfico después de añadir partición + this.validationSubject.next(); + setTimeout(() => { this.resizeChart(); }, 100); @@ -361,6 +394,8 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes partition.percentage = (size / disk.totalDiskSize) * 100; this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); this.updateDiskChart(disk); + + this.validationSubject.next(); } } } @@ -375,6 +410,10 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes save() { + if (this.selectedClients.length === 0 && this.clientData.length > 0) { + this.updateSelectedClients(); + } + if (!this.selectedDisk) { this.toastService.error('No se ha seleccionado un disco.'); return; @@ -454,7 +493,8 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes this.updateDiskChart(disk); this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); - // Redimensionar el gráfico después de eliminar partición + this.validationSubject.next(); + setTimeout(() => { this.resizeChart(); }, 100); @@ -470,9 +510,55 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes } this.updateDiskChart(disk); this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); + + this.validationSubject.next(); } } + validatePartitionSizes(): void { + if (!this.selectedModelClient || !this.selectedDisk) { + return; + } + + this.partitionValidationStatus = 'loading'; + this.partitionValidationMessage = ''; + + const partitions = this.selectedDisk.partitions + .filter((partition: Partition) => !partition.removed) + .map((partition: Partition) => ({ + diskNumber: this.selectedDisk.diskNumber, + partitionNumber: partition.partitionNumber, + size: partition.size, + partitionCode: partition.partitionCode, + filesystem: partition.filesystem + })); + + const payload = { + partitions: partitions + }; + + const url = `${this.baseUrl}${this.selectedModelClient.uuid}/check-partition-sizes`; + + this.http.post(url, payload).subscribe( + (response: any) => { + if (response.res === 1) { + this.partitionValidationStatus = 'success'; + this.partitionValidationMessage = 'Las particiones cumplen con los requisitos del disco.'; + } else if (response.res === 2) { + this.partitionValidationStatus = 'error'; + this.partitionValidationMessage = response.der || 'Las particiones no cumplen con los requisitos del disco.'; + } else { + this.partitionValidationStatus = 'error'; + this.partitionValidationMessage = 'Respuesta inesperada del servidor.'; + } + }, + (error) => { + this.partitionValidationStatus = 'error'; + this.partitionValidationMessage = error.error?.message || 'Error al validar las particiones.'; + } + ); + } + calculateUsedSpace(partitions: Partition[]): number { return partitions .filter(partition => !partition.removed) @@ -498,24 +584,36 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes } updateDiskChart(disk: any) { - console.log('disk', disk); disk.chartData = this.generateChartData(disk.partitions); disk.used = this.calculateUsedSpace(disk.partitions); disk.percentage = (disk.used / disk.totalDiskSize) * 100; - // Redimensionar el gráfico después de actualizar los datos setTimeout(() => { this.resizeChart(); }, 50); } openScheduleModal(): void { + let scope = this.runScriptContext?.type || 'clients'; + let selectedClients = null; + + if (this.selectedClients.length === 0 && this.clientData.length > 0) { + this.updateSelectedClients(); + } + + if (this.selectedClients && this.selectedClients.length > 0) { + scope = 'clients'; + selectedClients = this.selectedClients; + } + const dialogRef = this.dialog.open(CreateTaskComponent, { width: '800px', data: { - scope: this.runScriptContext.type, - organizationalUnit: this.runScriptContext['@id'], - source: 'assistant' + scope: scope, + selectedClients: selectedClients, + organizationalUnit: this.runScriptContext?.['@id'], + source: 'assistant', + runScriptContext: this.runScriptContext } }); @@ -577,7 +675,8 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes if (this.selectedDiskNumber) { this.scrollToPartitionTable(); - // Redimensionar el gráfico después de cambiar de disco + this.validationSubject.next(); + setTimeout(() => { this.resizeChart(); }, 150); @@ -585,7 +684,6 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes } scrollToPartitionTable() { - // Pequeño delay para asegurar que el contenido se haya renderizado setTimeout(() => { const diskInfo = document.getElementById('disk-info'); @@ -630,9 +728,7 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes ((this.selectedDisk.totalDiskSize - this.selectedDisk.used) / this.selectedDisk.totalDiskSize) * 100 : 0; } - /** - * Asigna un color único a una nueva partición - */ + private getNextPartitionColor(): string { if (!this.selectedDisk) return this.partitionColors[0]; @@ -640,20 +736,16 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes .filter((p: Partition) => !p.removed) .map((p: Partition) => p.color); - // Buscar el primer color no usado for (const color of this.partitionColors) { if (!usedColors.includes(color)) { return color; } } - // Si todos están usados, generar uno aleatorio return this.partitionColors[Math.floor(Math.random() * this.partitionColors.length)]; } - /** - * Asigna un color basado en el número de partición - */ + private getColorForPartition(partitionNumber: number): string { return this.partitionColors[(partitionNumber - 1) % this.partitionColors.length]; }