refs #2496. Check partition sizes. Integration UX
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details

pull/33/head
Manuel Aranda Rosales 2025-08-05 10:34:34 +02:00
parent 847504f286
commit 48a2a7f061
3 changed files with 215 additions and 38 deletions

View File

@ -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); }
}

View File

@ -16,7 +16,7 @@
</div>
</div>
<div class="button-row">
<button class="action-button" [disabled]="data.status === 'busy' || !selectedModelClient || !allSelected || !selectedDisk || (selectedDisk.totalDiskSize - selectedDisk.used) <= 0" (click)="save()">Ejecutar</button>
<button class="action-button" [disabled]="!selectedModelClient || !allSelected || !selectedDisk || (selectedDisk.totalDiskSize - selectedDisk.used) <= 0 || partitionValidationStatus === 'error'" (click)="save()">Ejecutar</button>
</div>
<div class="button-row">
@ -26,7 +26,7 @@
</div>
<div class="button-row">
<button class="action-button" color="accent" [disabled]="data.status === 'busy' || !selectedModelClient || !allSelected || !selectedDisk || (selectedDisk.totalDiskSize - selectedDisk.used) <= 0" (click)="openScheduleModal()">
<button class="action-button" color="accent" [disabled]="!selectedModelClient || !allSelected || !selectedDisk || (selectedDisk.totalDiskSize - selectedDisk.used) <= 0" (click)="openScheduleModal()">
Opciones de programación
</button>
</div>
@ -51,7 +51,7 @@
<div class="clients-grid">
<div *ngFor="let client of clientData" class="client-item">
<div class="client-card"
(click)="client.status === 'og-live' && toggleClientSelection(client)"
(click)="toggleClientSelection(client)"
[ngClass]="{'selected-client': client.selected}"
[matTooltip]="getPartitionsTooltip(client)"
matTooltipPosition="above"
@ -110,17 +110,13 @@
</div>
</mat-option>
</mat-select>
<mat-hint>Selecciona el disco que deseas particionar</mat-hint>
<mat-hint *ngIf="!selectedDisk">Selecciona el disco que deseas particionar</mat-hint>
<mat-hint *ngIf="selectedDisk" class="selected-disk-hint">
<mat-icon class="hint-icon">check_circle</mat-icon>
Disco {{ selectedDisk.diskNumber }} seleccionado - {{ (selectedDisk.totalDiskSize / 1024).toFixed(2) }} GB
</mat-hint>
</mat-form-field>
<div class="selection-info" *ngIf="selectedDisk">
<mat-icon class="info-icon">info</mat-icon>
<div class="info-text">
<span class="info-title">Disco seleccionado: {{ selectedDisk.diskNumber }}</span>
<span class="info-subtitle">Tamaño total: {{ (selectedDisk.totalDiskSize / 1024).toFixed(2) }} GB</span>
</div>
</div>
<div class="no-disks-message" *ngIf="!disks || disks.length === 0">
<mat-icon class="warning-icon">warning</mat-icon>
<div class="message-text">
@ -207,6 +203,16 @@
</div>
</div>
<!-- Indicador de validación de particiones -->
<div class="partition-validation-indicator" *ngIf="selectedDisk && selectedDisk.partitions.length > 0">
<div class="validation-status" [ngClass]="partitionValidationStatus">
<mat-icon *ngIf="partitionValidationStatus === 'loading'" class="validation-icon loading">hourglass_empty</mat-icon>
<mat-icon *ngIf="partitionValidationStatus === 'success'" class="validation-icon success">check_circle</mat-icon>
<mat-icon *ngIf="partitionValidationStatus === 'error'" class="validation-icon error">error</mat-icon>
<span class="validation-message" *ngIf="partitionValidationMessage">{{ partitionValidationMessage }}</span>
</div>
</div>
<table class="partition-table" id="partition-table">
<thead>
<tr>

View File

@ -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<void>();
// 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];
}