refs #2339. Assistants new UX
testing/ogGui-multibranch/pipeline/head There was a failure building this commit
Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit
Details
parent
212c4f9eec
commit
ce00b92751
|
@ -22,8 +22,8 @@
|
|||
<div class="clients-grid">
|
||||
<div *ngFor="let client of data.clients" class="client-item">
|
||||
<div class="client-card"
|
||||
(click)="client.status === 'og-live' && toggleClientSelection(client)"
|
||||
[ngClass]="{'selected-client': client.selected, 'disabled-client': client.status !== 'og-live'}" >
|
||||
(click)="toggleClientSelection(client)"
|
||||
[ngClass]="{'selected-client': client.selected}" >
|
||||
|
||||
<img
|
||||
[src]="'assets/images/computer_' + client.status + '.svg'"
|
||||
|
|
|
@ -66,19 +66,11 @@ export class BootSoPartitionComponent implements OnInit {
|
|||
this.baseUrl = this.configService.apiUrl;
|
||||
this.clientId = this.data.clients?.length ? this.data.clients[0]['@id'] : null;
|
||||
|
||||
this.data.clients.forEach((client: { selected: boolean; status: string }) => {
|
||||
if (client.status === 'og-live') {
|
||||
client.selected = true;
|
||||
}
|
||||
});
|
||||
this.data.clients.forEach((client: { selected: boolean; status: string }) => client.selected = true);
|
||||
|
||||
this.selectedClients = this.data.clients.filter(
|
||||
(client: { status: string }) => client.status === 'og-live'
|
||||
);
|
||||
this.selectedClients = this.data.clients.filter((client: { selected: boolean }) => client.selected);
|
||||
|
||||
this.selectedModelClient = this.data.clients.find(
|
||||
(client: { status: string }) => client.status === 'og-live'
|
||||
) || null;
|
||||
this.selectedModelClient = this.data.clients.find((client: { selected: boolean }) => client.selected) || null;
|
||||
|
||||
if (this.selectedModelClient) {
|
||||
this.loadPartitions(this.selectedModelClient);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,9 +5,15 @@
|
|||
<h2>
|
||||
{{ 'partitionTitle' | translate }}
|
||||
</h2>
|
||||
<h4>
|
||||
{{ runScriptTitle }}
|
||||
</h4>
|
||||
<div class="destination-info">
|
||||
<div class="destination-badge">
|
||||
<mat-icon class="destination-icon">cloud_download</mat-icon>
|
||||
<div class="destination-content">
|
||||
<span class="destination-label">Destino</span>
|
||||
<span class="destination-value">{{ runScriptTitle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
@ -19,9 +25,9 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button mat-stroked-button color="accent" [disabled]="data.status === 'busy' || !selectedModelClient || !allSelected || !selectedDisk || (selectedDisk.totalDiskSize - selectedDisk.used) <= 0" (click)="openScheduleModal()">
|
||||
<mat-icon>schedule</mat-icon> Opciones de programación
|
||||
<div class="button-row">
|
||||
<button class="action-button" color="accent" [disabled]="data.status === 'busy' || !selectedModelClient || !allSelected || !selectedDisk || (selectedDisk.totalDiskSize - selectedDisk.used) <= 0" (click)="openScheduleModal()">
|
||||
Opciones de programación
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -46,7 +52,7 @@
|
|||
<div *ngFor="let client of clientData" class="client-item">
|
||||
<div class="client-card"
|
||||
(click)="client.status === 'og-live' && toggleClientSelection(client)"
|
||||
[ngClass]="{'selected-client': client.selected, 'disabled-client': client.status !== 'og-live'}"
|
||||
[ngClass]="{'selected-client': client.selected}"
|
||||
[matTooltip]="getPartitionsTooltip(client)"
|
||||
matTooltipPosition="above"
|
||||
matTooltipClass="custom-tooltip">
|
||||
|
@ -77,18 +83,52 @@
|
|||
</mat-expansion-panel>
|
||||
</div>
|
||||
|
||||
<mat-divider style="margin-top: 20px;"></mat-divider>
|
||||
|
||||
<mat-dialog-content>
|
||||
<div class="disk-select">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Seleccionar disco</mat-label>
|
||||
<mat-select [(ngModel)]="selectedDiskNumber">
|
||||
<mat-option *ngFor="let disk of disks" [value]="disk.diskNumber">
|
||||
Disco {{ disk.diskNumber }} ({{ (disk.totalDiskSize / 1024).toFixed(2) }} GB)
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div class="disk-selector-card">
|
||||
<div class="card-header">
|
||||
<mat-icon class="card-icon">storage</mat-icon>
|
||||
<div class="card-title">
|
||||
<h3>Selección de Disco</h3>
|
||||
<p>Elige el disco donde se realizarán las operaciones de particionado</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<mat-form-field appearance="fill" class="disk-select-field">
|
||||
<mat-label>Seleccionar disco</mat-label>
|
||||
<mat-select [(ngModel)]="selectedDiskNumber" (selectionChange)="onDiskSelectionChange()">
|
||||
<mat-option *ngFor="let disk of disks" [value]="disk.diskNumber">
|
||||
<div class="disk-option">
|
||||
<div class="disk-info">
|
||||
<span class="disk-name">Disco {{ disk.diskNumber }}</span>
|
||||
<span class="disk-size"> {{ (disk.totalDiskSize / 1024).toFixed(2) }} GB</span>
|
||||
</div>
|
||||
<div class="disk-details">
|
||||
<span class="usage-percent"> {{ (disk.percentage || 0).toFixed(1) }}% usado</span>
|
||||
</div>
|
||||
</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint>Selecciona el disco que deseas particionar</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">
|
||||
<span class="message-title">No hay discos disponibles</span>
|
||||
<span class="message-subtitle">Asegúrate de que el cliente modelo tenga discos configurados</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="partition-assistant" *ngIf="selectedDisk">
|
||||
|
@ -108,33 +148,66 @@
|
|||
|
||||
<div class="row-button">
|
||||
<button class="action-button" [disabled]="partitionCode === 'MSDOS'" (click)="addPartition(selectedDisk.diskNumber)">Añadir partición</button>
|
||||
<mat-chip *ngIf="selectedModelClient.firmwareType">
|
||||
Firmware: {{ selectedModelClient.firmwareType }}
|
||||
</mat-chip>
|
||||
<mat-chip color="info" *ngIf="partitionCode">
|
||||
Tabla de particiones: {{ partitionCode }}
|
||||
</mat-chip>
|
||||
|
||||
<div class="info-badge" *ngIf="selectedModelClient.firmwareType">
|
||||
<mat-icon class="info-icon">memory</mat-icon>
|
||||
<div class="info-content">
|
||||
<span class="info-label">Firmware</span>
|
||||
<span class="info-value">{{ selectedModelClient.firmwareType }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-badge" *ngIf="partitionCode">
|
||||
<mat-icon class="info-icon">storage</mat-icon>
|
||||
<div class="info-content">
|
||||
<span class="info-label">Tabla de particiones</span>
|
||||
<span class="info-value">{{ partitionCode }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider style="padding: 10px;"></mat-divider>
|
||||
|
||||
<div class="disk-space-info-container">
|
||||
<div class="disk-space-info" [ngClass]="selectedDisk.used < selectedDisk.totalDiskSize ? 'chip-free' : 'chip-full'">
|
||||
Espacio usado: {{ selectedDisk.used | number:'1.2-2' }} MB
|
||||
</div>
|
||||
|
||||
<div class="disk-space-info" [ngClass]="selectedDisk.used < selectedDisk.totalDiskSize ? 'chip-free' : 'chip-full'">
|
||||
Espacio libre: {{ (selectedDisk.totalDiskSize - selectedDisk.used) | number:'1.2-2' }} MB
|
||||
</div>
|
||||
|
||||
<div class="disk-space-info">
|
||||
Espacio total: {{ selectedDisk.totalDiskSize | number:'1.2-2' }} MB
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-container">
|
||||
<table class="partition-table">
|
||||
<div class="disk-space-info-container" id="disk-info">
|
||||
<div class="disk-space-card">
|
||||
<div class="space-info-item">
|
||||
<mat-icon class="space-icon used-icon">storage</mat-icon>
|
||||
<div class="space-details">
|
||||
<span class="space-label">Espacio usado</span>
|
||||
<span class="space-value">{{ selectedDisk.used | number:'1.2-2' }} MB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-info-item">
|
||||
<mat-icon class="space-icon free-icon" [ngClass]="{'warning': (selectedDisk.used / selectedDisk.totalDiskSize) >= 0.9 && (selectedDisk.used / selectedDisk.totalDiskSize) < 1, 'danger': (selectedDisk.used / selectedDisk.totalDiskSize) >= 1}">cloud_done</mat-icon>
|
||||
<div class="space-details">
|
||||
<span class="space-label">Espacio libre</span>
|
||||
<span class="space-value" [ngClass]="{'warning': (selectedDisk.used / selectedDisk.totalDiskSize) >= 0.9 && (selectedDisk.used / selectedDisk.totalDiskSize) < 1, 'danger': (selectedDisk.used / selectedDisk.totalDiskSize) >= 1}">{{ (selectedDisk.totalDiskSize - selectedDisk.used) | number:'1.2-2' }} MB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-info-item">
|
||||
<mat-icon class="space-icon total-icon">dns</mat-icon>
|
||||
<div class="space-details">
|
||||
<span class="space-label">Espacio total</span>
|
||||
<span class="space-value">{{ selectedDisk.totalDiskSize | number:'1.2-2' }} MB</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="disk-usage-bar">
|
||||
<div class="usage-bar-container">
|
||||
<div class="usage-bar-fill"
|
||||
[style.width.%]="(selectedDisk.used / selectedDisk.totalDiskSize) * 100"
|
||||
[ngClass]="{'warning': (selectedDisk.used / selectedDisk.totalDiskSize) >= 0.9 && (selectedDisk.used / selectedDisk.totalDiskSize) < 1, 'danger': (selectedDisk.used / selectedDisk.totalDiskSize) >= 1}"></div>
|
||||
</div>
|
||||
<span class="usage-percentage" [ngClass]="{'warning': (selectedDisk.used / selectedDisk.totalDiskSize) >= 0.9 && (selectedDisk.used / selectedDisk.totalDiskSize) < 1, 'danger': (selectedDisk.used / selectedDisk.totalDiskSize) >= 1}">{{ ((selectedDisk.used / selectedDisk.totalDiskSize) * 100) | number:'1.1-1' }}% usado</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="partition-table" id="partition-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Partición</th>
|
||||
|
@ -182,11 +255,29 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
<ngx-charts-pie-chart [view]="view" [results]="selectedDisk.chartData" [doughnut]="true">
|
||||
|
||||
<div class="chart-container" *ngIf="selectedDisk">
|
||||
<div class="chart-header">
|
||||
<h3>Distribución de Particiones</h3>
|
||||
</div>
|
||||
<ngx-charts-pie-chart
|
||||
[results]="selectedDisk.chartData"
|
||||
[doughnut]="true"
|
||||
[gradient]="true"
|
||||
[labels]="true"
|
||||
[tooltipDisabled]="false"
|
||||
[animations]="true">
|
||||
</ngx-charts-pie-chart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<app-scroll-to-top
|
||||
[threshold]="200"
|
||||
targetElement=".header-container"
|
||||
position="bottom-right"
|
||||
[showTooltip]="true"
|
||||
tooltipText="Volver arriba"
|
||||
tooltipPosition="left">
|
||||
</app-scroll-to-top>
|
||||
|
|
|
@ -7,6 +7,7 @@ import { FILESYSTEM_TYPES } from '../../../../../shared/constants/filesystem-typ
|
|||
import { ConfigService } from '@services/config.service';
|
||||
import {CreateTaskComponent} from "../../../../commands/commands-task/create-task/create-task.component";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {QueueConfirmationModalComponent} from "../../../../../shared/queue-confirmation-modal/queue-confirmation-modal.component";
|
||||
|
||||
interface Partition {
|
||||
uuid?: string;
|
||||
|
@ -46,7 +47,7 @@ export class PartitionAssistantComponent implements OnInit{
|
|||
runScriptContext: any = null;
|
||||
showInstructions = false;
|
||||
|
||||
view: [number, number] = [400, 300];
|
||||
view: [number, number] = [300, 200];
|
||||
showLegend = true;
|
||||
showLabels = true;
|
||||
allSelected = true;
|
||||
|
@ -74,19 +75,11 @@ export class PartitionAssistantComponent implements OnInit{
|
|||
}
|
||||
});
|
||||
this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null;
|
||||
this.clientData.forEach((client: { selected: boolean; status: string}) => {
|
||||
if (client.status === 'og-live') {
|
||||
client.selected = true;
|
||||
}
|
||||
});
|
||||
this.clientData.forEach((client: { selected: boolean; status: string}) => { client.selected = true; });
|
||||
|
||||
this.selectedClients = this.clientData.filter(
|
||||
(client: { status: string }) => client.status === 'og-live'
|
||||
);
|
||||
this.selectedClients = this.clientData.filter((client: { selected: boolean; status: string}) => client.selected);
|
||||
|
||||
this.selectedModelClient = this.clientData.find(
|
||||
(client: { status: string }) => client.status === 'og-live'
|
||||
) || null;
|
||||
this.selectedModelClient = this.clientData.find((client: { selected: boolean; status: string}) => client.selected) || null;
|
||||
|
||||
if (this.selectedModelClient) {
|
||||
this.loadPartitions(this.selectedModelClient);
|
||||
|
@ -136,15 +129,18 @@ export class PartitionAssistantComponent implements OnInit{
|
|||
|
||||
toggleSelectAll() {
|
||||
this.allSelected = !this.allSelected;
|
||||
this.clientData.forEach((client: { selected: boolean; status: string }) => {
|
||||
if (client.status === "og-live") {
|
||||
client.selected = this.allSelected;
|
||||
}
|
||||
});
|
||||
this.clientData.forEach((client: { selected: boolean; status: string }) => { client.selected = this.allSelected; });
|
||||
}
|
||||
|
||||
initializeDisks() {
|
||||
this.disks = [];
|
||||
|
||||
// Verificar que hay datos válidos
|
||||
if (!this.data || !this.data.partitions || !Array.isArray(this.data.partitions)) {
|
||||
console.warn('No hay datos de particiones válidos');
|
||||
return;
|
||||
}
|
||||
|
||||
const partitionsFromData = this.data.partitions;
|
||||
this.originalPartitions = JSON.parse(JSON.stringify(partitionsFromData));
|
||||
|
||||
|
@ -320,56 +316,63 @@ export class PartitionAssistantComponent implements OnInit{
|
|||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
const totalPartitionSize = this.selectedDisk.partitions
|
||||
.filter((partition: any) => !partition.removed)
|
||||
.reduce((sum: any, partition: any) => sum + partition.size, 0);
|
||||
|
||||
if (totalPartitionSize > this.selectedDisk.totalDiskSize) {
|
||||
this.toastService.error('El tamaño total de las particiones en el disco seleccionado excede el tamaño total del disco.');
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const modifiedPartitions = this.selectedDisk.partitions.filter((partition: { removed: any; format: any; }) => !partition.removed || partition.format);
|
||||
|
||||
if (modifiedPartitions.length === 0) {
|
||||
this.loading = false;
|
||||
this.toastService.info('No hay cambios para guardar en el disco seleccionado.');
|
||||
return;
|
||||
}
|
||||
|
||||
const newPartitions = modifiedPartitions.map((partition: { partitionNumber: any; memoryUsage: any; size: any; partitionCode: any; filesystem: any; uuid: any; removed: any; format: any; }) => ({
|
||||
diskNumber: this.selectedDisk.diskNumber,
|
||||
partitionNumber: partition.partitionNumber,
|
||||
memoryUsage: partition.memoryUsage,
|
||||
size: partition.size,
|
||||
partitionCode: partition.partitionCode,
|
||||
filesystem: partition.filesystem,
|
||||
uuid: partition.uuid,
|
||||
removed: partition.removed || false,
|
||||
format: partition.format || false,
|
||||
}));
|
||||
const dialogRef = this.dialog.open(QueueConfirmationModalComponent, {
|
||||
width: '400px',
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop'
|
||||
});
|
||||
|
||||
if (newPartitions.length > 0) {
|
||||
const bulkPayload = {
|
||||
partitions: newPartitions,
|
||||
clients: this.selectedClients.map((client: any) => client.uuid),
|
||||
};
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result !== undefined) {
|
||||
this.loading = true;
|
||||
const newPartitions = modifiedPartitions.map((partition: { partitionNumber: any; memoryUsage: any; size: any; partitionCode: any; filesystem: any; uuid: any; removed: any; format: any; }) => ({
|
||||
diskNumber: this.selectedDisk.diskNumber,
|
||||
partitionNumber: partition.partitionNumber,
|
||||
memoryUsage: partition.memoryUsage,
|
||||
size: partition.size,
|
||||
partitionCode: partition.partitionCode,
|
||||
filesystem: partition.filesystem,
|
||||
uuid: partition.uuid,
|
||||
removed: partition.removed || false,
|
||||
format: partition.format || false,
|
||||
}));
|
||||
|
||||
this.http.post(this.apiUrl, bulkPayload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Particiones creadas exitosamente para el disco seleccionado.');
|
||||
this.loading = false;
|
||||
this.router.navigate(['/commands-logs']);
|
||||
},
|
||||
(error) => {
|
||||
this.loading = false;
|
||||
this.toastService.error('Error al crear las particiones.');
|
||||
}
|
||||
);
|
||||
}
|
||||
const bulkPayload = {
|
||||
partitions: newPartitions,
|
||||
clients: this.selectedClients.map((client: any) => client.uuid),
|
||||
queue: result
|
||||
};
|
||||
|
||||
this.http.post(this.apiUrl, bulkPayload).subscribe(
|
||||
(response) => {
|
||||
this.toastService.success('Particiones creadas exitosamente para el disco seleccionado.');
|
||||
this.loading = false;
|
||||
this.router.navigate(['/commands-logs']);
|
||||
},
|
||||
(error) => {
|
||||
this.loading = false;
|
||||
this.toastService.error('Error al crear las particiones.');
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -409,11 +412,20 @@ export class PartitionAssistantComponent implements OnInit{
|
|||
|
||||
|
||||
generateChartData(partitions: Partition[]): any[] {
|
||||
return partitions.map((partition) => ({
|
||||
name: `Partición ${partition.partitionNumber}`,
|
||||
value: partition.percentage,
|
||||
color: partition.color
|
||||
}));
|
||||
const colors = [
|
||||
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
|
||||
'#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9',
|
||||
'#F8C471', '#82E0AA', '#F1948A', '#85C1E9', '#D7BDE2'
|
||||
];
|
||||
|
||||
return partitions
|
||||
.filter(partition => !partition.removed)
|
||||
.map((partition, index) => ({
|
||||
name: `Partición ${partition.partitionNumber}`,
|
||||
value: partition.size,
|
||||
color: colors[index % colors.length],
|
||||
partition: partition
|
||||
}));
|
||||
}
|
||||
|
||||
updateDiskChart(disk: any) {
|
||||
|
@ -478,38 +490,50 @@ export class PartitionAssistantComponent implements OnInit{
|
|||
}
|
||||
|
||||
generateInstructions(): void {
|
||||
if (!this.selectedDisk || !this.selectedDisk.partitions) {
|
||||
this.generatedInstructions = 'No hay particiones configuradas para generar instrucciones.';
|
||||
return;
|
||||
this.showInstructions = true;
|
||||
this.generatedInstructions = `og-partition --disk ${this.selectedDiskNumber} --partitions ${this.selectedDisk.partitions.map((p: Partition) => `${p.partitionNumber}:${p.size}:${p.partitionCode}:${p.filesystem}:${p.format}`).join(',')}`;
|
||||
}
|
||||
|
||||
onDiskSelected(diskNumber: number) {
|
||||
this.selectedDiskNumber = diskNumber;
|
||||
this.scrollToPartitionTable();
|
||||
}
|
||||
|
||||
onDiskSelectionChange() {
|
||||
if (this.selectedDiskNumber) {
|
||||
this.scrollToPartitionTable();
|
||||
}
|
||||
}
|
||||
|
||||
const diskNumber = this.selectedDisk.diskNumber;
|
||||
const partitionTable = this.partitionCode || 'MSDOS';
|
||||
scrollToPartitionTable() {
|
||||
// Pequeño delay para asegurar que el contenido se haya renderizado
|
||||
setTimeout(() => {
|
||||
const diskInfo = document.getElementById('disk-info');
|
||||
|
||||
if (diskInfo) {
|
||||
diskInfo.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'nearest'
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
let instructions = `ogCreatePartitionTable ${diskNumber} ${partitionTable}\n`;
|
||||
instructions += `ogEcho log session "[0] $MSG_HELP_ogCreatePartitions"\n`;
|
||||
instructions += `ogEcho session "[10] $MSG_HELP_ogUnmountAll ${diskNumber}"\n`;
|
||||
instructions += `ogUnmountAll ${diskNumber} 2>/dev/null\n`;
|
||||
instructions += `ogUnmountCache\n`;
|
||||
instructions += `ogEcho session "[30] $MSG_HELP_ogUpdatePartitionTable ${diskNumber}"\n`;
|
||||
instructions += `ogDeletePartitionTable ${diskNumber}\n`;
|
||||
instructions += `ogUpdatePartitionTable ${diskNumber}\n`;
|
||||
|
||||
this.selectedDisk.partitions.forEach((partition: { removed: any; partitionNumber: any; partitionCode: any; filesystem: any; size: any; format: any; }, index: any) => {
|
||||
if (partition.removed) return;
|
||||
|
||||
const partNumber = partition.partitionNumber;
|
||||
const partType = partition.partitionCode;
|
||||
const fs = partition.filesystem;
|
||||
const size = partition.size;
|
||||
const shouldFormat = partition.format ? 'yes' : 'no';
|
||||
|
||||
instructions += `ogCreatePartition ${diskNumber} ${partNumber} ${partType} ${fs} ${size}MB ${shouldFormat}\n`;
|
||||
});
|
||||
|
||||
instructions += `ogExecAndLog command session ogListPartitions ${diskNumber}\n`;
|
||||
|
||||
this.generatedInstructions = instructions;
|
||||
this.showInstructions = true
|
||||
scrollToExecuteButton() {
|
||||
console.log('scrollToExecuteButton llamado');
|
||||
|
||||
const executeButton = document.getElementById('execute-button');
|
||||
console.log('Botón ejecutar encontrado:', executeButton);
|
||||
|
||||
if (executeButton) {
|
||||
executeButton.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center'
|
||||
});
|
||||
console.log('Scroll hacia botón ejecutar completado');
|
||||
} else {
|
||||
console.error('No se encontró el botón execute-button');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
@ -102,10 +101,287 @@ table {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 24px 32px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.header-container-title h2 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-container-title h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
padding-right: 1em;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-button:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.action-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.select-container {
|
||||
background: white !important;
|
||||
margin-top: 20px;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: white !important;
|
||||
border-radius: 16px;
|
||||
padding: 20px !important;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #bbdefb;
|
||||
}
|
||||
|
||||
.form-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 2px solid #f8f9fa;
|
||||
}
|
||||
|
||||
.form-section-title mat-icon {
|
||||
color: #667eea;
|
||||
font-size: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
/* Badges y chips */
|
||||
.destination-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
padding: 12px 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #bbdefb;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.destination-icon {
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 12px;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.destination-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.destination-label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: #1976d2;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.destination-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #0d47a1;
|
||||
}
|
||||
|
||||
.info-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: #e8f5e8;
|
||||
color: #2e7d32;
|
||||
padding: 12px 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #c8e6c9;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s ease;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.info-badge:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.info-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: #388e3c;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
max-width: 150px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #1b5e20;
|
||||
}
|
||||
|
||||
/* Clientes y tarjetas */
|
||||
.clients-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.client-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.client-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.client-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.client-image {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.client-name {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 2px;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.client-ip {
|
||||
font-size: 10px;
|
||||
color: #6c757d;
|
||||
display: block;
|
||||
margin-bottom: 1px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.selected-client {
|
||||
background: linear-gradient(135deg, #8fa1f0 0%, #9b7bc8 100%);
|
||||
color: white;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.selected-client .client-name,
|
||||
.selected-client .client-ip {
|
||||
color: white;
|
||||
}
|
||||
|
||||
::ng-deep .mat-expansion-panel {
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important;
|
||||
border-radius: 12px !important;
|
||||
margin-bottom: 20px;
|
||||
background: #f7fbff !important;
|
||||
border: 1px solid #bbdefb !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-expansion-panel-header {
|
||||
padding: 20px 24px !important;
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-expansion-panel-header-title {
|
||||
font-weight: 600 !important;
|
||||
color: #2c3e50 !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-expansion-panel-header-description {
|
||||
color: #6c757d !important;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header-description {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
@ -116,117 +392,11 @@ table {
|
|||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.clients-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.client-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.client-card {
|
||||
background: #ffffff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, transform 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f0f0;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
.client-details {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.client-name {
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 150px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client-ip {
|
||||
display: block;
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.client-card {
|
||||
background: #ffffff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, transform 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f0f0;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .custom-tooltip {
|
||||
white-space: pre-line !important;
|
||||
max-width: 200px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.selected-client {
|
||||
background-color: #a0c2e5 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.disabled-client {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header-description {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.new-command-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -261,15 +431,85 @@ table {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.script-selector-card {
|
||||
margin: 20px 20px;
|
||||
padding: 16px;
|
||||
/* Secciones del formulario */
|
||||
.form-section {
|
||||
background: white !important;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #bbdefb;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.form-section-title mat-icon {
|
||||
color: #2196f3;
|
||||
}
|
||||
.toggle-options {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
margin: 16px 0;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.selected-toggle {
|
||||
background: linear-gradient(135deg, #8fa1f0 0%, #9b7bc8 100%) !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
mat-spinner {
|
||||
margin: 20px auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Estilo para hacer el backdrop no clickeable */
|
||||
::ng-deep .non-clickable-backdrop {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
::ng-deep .action-chip {
|
||||
margin: 8px !important;
|
||||
padding: 12px 20px !important;
|
||||
border-radius: px !important;
|
||||
font-weight: 500 !important;
|
||||
font-size: 14px !important;
|
||||
transition: all 0.3s ease !important;
|
||||
border: 2px solid transparent !important;
|
||||
background: white !important;
|
||||
color: #6c757d !important;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
cursor: pointer !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 8px !important;
|
||||
min-height: 48px !important;
|
||||
}
|
||||
|
||||
::ng-deep .action-chip:hover {
|
||||
transform: translateY(-2px) !important;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
::ng-deep .action-chip.mat-mdc-chip-selected {
|
||||
border-color: #667eea !important;
|
||||
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.2) !important;
|
||||
}
|
||||
|
||||
::ng-deep .create-chip.mat-mdc-chip-selected {
|
||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%) !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
::ng-deep .update-chip.mat-mdc-chip-selected {
|
||||
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%) !important;
|
||||
color: white !important;
|
||||
}
|
|
@ -10,12 +10,12 @@
|
|||
</h4>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button class="action-button" [disabled]="selectedClients.length < 1 || (commandType === 'existing' && !selectedScript)" (click)="save()">Ejecutar</button>
|
||||
<button class="action-button" [disabled]="selectedClients.length < 1 || (commandType === 'existing' && !selectedScript) || loading" (click)="save()">Ejecutar</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button mat-stroked-button color="accent" [disabled]="selectedClients.length < 1 || (commandType === 'existing' && !selectedScript)" (click)="openScheduleModal()">
|
||||
<mat-icon>schedule</mat-icon> Opciones de programación
|
||||
<div class="button-row">
|
||||
<button color="accent" class="action-button" [disabled]="selectedClients.length < 1 || (commandType === 'existing' && !selectedScript) || loading" (click)="openScheduleModal()">
|
||||
Opciones de programación
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -40,8 +40,8 @@
|
|||
<div class="clients-grid">
|
||||
<div *ngFor="let client of clientData" class="client-item">
|
||||
<div class="client-card"
|
||||
(click)="client.status === 'og-live' && toggleClientSelection(client)"
|
||||
[ngClass]="{'selected-client': client.selected, 'disabled-client': client.status !== 'og-live'}"
|
||||
(click)="toggleClientSelection(client)"
|
||||
[ngClass]="{'selected-client': client.selected}"
|
||||
[matTooltip]="getPartitionsTooltip(client)"
|
||||
matTooltipPosition="above"
|
||||
matTooltipClass="custom-tooltip">
|
||||
|
@ -62,55 +62,69 @@
|
|||
</mat-expansion-panel>
|
||||
</div>
|
||||
|
||||
<mat-divider style="margin-top: 20px;"></mat-divider>
|
||||
<div class="select-container">
|
||||
|
||||
<mat-card class="script-selector-card">
|
||||
<mat-card-title>Seleccione el tipo de comando</mat-card-title>
|
||||
|
||||
<div class="toggle-options">
|
||||
<mat-button-toggle-group [(ngModel)]="commandType" exclusive>
|
||||
<mat-button-toggle value="new">
|
||||
<mat-icon>edit</mat-icon> Nuevo Script
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle value="existing">
|
||||
<mat-icon>storage</mat-icon> Script Guardado
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
|
||||
<div *ngIf="commandType === 'new'" class="new-command-container">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Ingrese el script</mat-label>
|
||||
<textarea matInput [(ngModel)]="newScript" rows="6" placeholder="Escriba su script aquí"></textarea>
|
||||
</mat-form-field>
|
||||
<button mat-flat-button color="primary" (click)="saveNewScript()">Guardar Script</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="commandType === 'existing'">
|
||||
<mat-form-field appearance="fill" class="custom-width">
|
||||
<mat-label>Seleccione script a ejecutar</mat-label>
|
||||
<mat-select [(ngModel)]="selectedScript" (selectionChange)="onScriptChange()">
|
||||
<mat-option *ngFor="let script of scripts" [value]="script">{{ script.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectedScript && commandType === 'existing'" class="script-container">
|
||||
<div class="script-content">
|
||||
<h3>Script:</h3>
|
||||
<div class="script-preview" [innerHTML]="scriptContent"></div>
|
||||
<div class="form-section">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>code</mat-icon>
|
||||
Configuración de script
|
||||
</div>
|
||||
|
||||
<div class="script-params" *ngIf="parameterNames.length > 0 && selectedScript.parameters">
|
||||
<h3>Ingrese los parámetros:</h3>
|
||||
<div *ngFor="let paramName of parameterNames">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ paramName }}</mat-label>
|
||||
<input matInput [ngModel]="parameters[paramName]" (ngModelChange)="onParamChange(paramName, $event)" placeholder="Valor para {{ paramName }}">
|
||||
</mat-form-field>
|
||||
<div class="action-chips-container">
|
||||
<mat-chip-listbox [(ngModel)]="commandType" required class="action-chip-listbox">
|
||||
<mat-chip-option value="new" class="action-chip create-chip firmware-chip" (click)="commandType = 'new'">
|
||||
<span>Nuevo Script</span>
|
||||
</mat-chip-option>
|
||||
<mat-chip-option value="existing" class="action-chip update-chip firmware-chip" (click)="commandType = 'existing'">
|
||||
<span>Script Guardado</span>
|
||||
</mat-chip-option>
|
||||
</mat-chip-listbox>
|
||||
</div>
|
||||
|
||||
|
||||
<div *ngIf="commandType === 'new'" class="new-command-container">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Ingrese el script</mat-label>
|
||||
<textarea matInput [(ngModel)]="newScript" rows="6" placeholder="Escriba su script aquí"></textarea>
|
||||
</mat-form-field>
|
||||
<button mat-flat-button color="primary" (click)="saveNewScript()">Guardar Script</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="commandType === 'existing'">
|
||||
<mat-form-field appearance="fill" class="custom-width">
|
||||
<mat-label>Seleccione script a ejecutar</mat-label>
|
||||
<mat-select [(ngModel)]="selectedScript" (selectionChange)="onScriptChange()">
|
||||
<mat-option *ngFor="let script of scripts" [value]="script">{{ script.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectedScript && commandType === 'existing'" class="script-container">
|
||||
<div class="script-content">
|
||||
<h3>Script:</h3>
|
||||
<div class="script-preview" [innerHTML]="scriptContent"></div>
|
||||
</div>
|
||||
|
||||
<div class="script-params" *ngIf="parameterNames.length > 0 && selectedScript.parameters">
|
||||
<h3>Ingrese los parámetros:</h3>
|
||||
<div *ngFor="let paramName of parameterNames">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>{{ paramName }}</mat-label>
|
||||
<input matInput [ngModel]="parameters[paramName]" (ngModelChange)="onParamChange(paramName, $event)" placeholder="Valor para {{ paramName }}">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<app-scroll-to-top
|
||||
[threshold]="200"
|
||||
targetElement=".header-container"
|
||||
position="bottom-right"
|
||||
[showTooltip]="true"
|
||||
tooltipText="Volver arriba"
|
||||
tooltipPosition="left">
|
||||
</app-scroll-to-top>
|
|
@ -28,6 +28,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|||
import {MatIconModule} from "@angular/material/icon";
|
||||
import {MatCardModule} from "@angular/material/card";
|
||||
import {MatButtonToggleModule} from "@angular/material/button-toggle";
|
||||
import { MatChipsModule } from "@angular/material/chips";
|
||||
|
||||
export function HttpLoaderFactory(http: HttpClient) {
|
||||
return new TranslateHttpLoader(http);
|
||||
|
@ -63,6 +64,7 @@ describe('RunScriptAssistantComponent', () => {
|
|||
MatIconModule,
|
||||
MatCardModule,
|
||||
MatButtonToggleModule,
|
||||
MatChipsModule,
|
||||
ToastrModule.forRoot(),
|
||||
HttpClientTestingModule,
|
||||
TranslateModule.forRoot({
|
||||
|
|
|
@ -7,6 +7,7 @@ import { ActivatedRoute, Router } from "@angular/router";
|
|||
import { SaveScriptComponent } from "./save-script/save-script.component";
|
||||
import { MatDialog } from "@angular/material/dialog";
|
||||
import {CreateTaskComponent} from "../../../../commands/commands-task/create-task/create-task.component";
|
||||
import {QueueConfirmationModalComponent} from "../../../../../shared/queue-confirmation-modal/queue-confirmation-modal.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-run-script-assistant',
|
||||
|
@ -53,15 +54,9 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
}
|
||||
});
|
||||
this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null;
|
||||
this.clientData.forEach((client: { selected: boolean; status: string}) => {
|
||||
if (client.status === 'og-live') {
|
||||
client.selected = true;
|
||||
}
|
||||
});
|
||||
this.clientData.forEach((client: { selected: boolean; status: string}) => { client.selected = true; });
|
||||
|
||||
this.selectedClients = this.clientData.filter(
|
||||
(client: { status: string }) => client.status === 'og-live'
|
||||
);
|
||||
this.selectedClients = this.clientData.filter((client: { selected: boolean; status: string}) => client.selected);
|
||||
|
||||
this.loadScripts()
|
||||
}
|
||||
|
@ -122,18 +117,12 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
}
|
||||
|
||||
updateSelectedClients() {
|
||||
this.selectedClients = this.clientData.filter(
|
||||
(client: { selected: boolean; status: string }) => client.selected && client.status === "og-live"
|
||||
);
|
||||
this.selectedClients = this.clientData.filter((client: { selected: boolean; status: string}) => client.selected);
|
||||
}
|
||||
|
||||
toggleSelectAll() {
|
||||
this.allSelected = !this.allSelected;
|
||||
this.clientData.forEach((client: { selected: boolean; status: string }) => {
|
||||
if (client.status === "og-live") {
|
||||
client.selected = this.allSelected;
|
||||
}
|
||||
});
|
||||
this.clientData.forEach((client: { selected: boolean; status: string }) => { client.selected = this.allSelected; });
|
||||
}
|
||||
|
||||
getPartitionsTooltip(client: any): string {
|
||||
|
@ -179,23 +168,33 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
}
|
||||
|
||||
save(): void {
|
||||
this.loading = true;
|
||||
const dialogRef = this.dialog.open(QueueConfirmationModalComponent, {
|
||||
width: '400px',
|
||||
disableClose: true,
|
||||
hasBackdrop: true
|
||||
});
|
||||
|
||||
this.http.post(`${this.baseUrl}/commands/run-script`, {
|
||||
clients: this.selectedClients.map((client: any) => client.uuid),
|
||||
script: this.commandType === 'existing' ? this.scriptContent : this.newScript,
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Script ejecutado correctamente');
|
||||
this.dataChange.emit();
|
||||
this.router.navigate(['/commands-logs']);
|
||||
},
|
||||
error => {
|
||||
this.toastService.error('Error al ejecutar el script');
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result !== undefined) {
|
||||
this.loading = true;
|
||||
this.http.post(`${this.baseUrl}/commands/run-script`, {
|
||||
clients: this.selectedClients.map((client: any) => client.uuid),
|
||||
script: this.commandType === 'existing' ? this.scriptContent : this.newScript,
|
||||
queue: result
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.toastService.success('Script ejecutado correctamente');
|
||||
this.dataChange.emit();
|
||||
this.router.navigate(['/commands-logs']);
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
this.toastService.error('Error al ejecutar el script');
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
openScheduleModal(): void {
|
||||
|
|
|
@ -92,7 +92,7 @@ export class ShowOrganizationalUnitComponent implements OnInit {
|
|||
{ property: 'Router', value: this.ou.networkSettings.router },
|
||||
{ property: 'NTP', value: this.ou.networkSettings.ntp },
|
||||
{ property: 'Modo P2P', value: this.ou.networkSettings.p2pMode },
|
||||
{ property: 'Tiempo P2P', value: this.ou.networkSettings.p2pTime },
|
||||
...(this.ou.networkSettings.p2pMode === 'seeder' ? [{ property: 'Tiempo P2P (minutos)', value: this.ou.networkSettings.p2pTime }] : []),
|
||||
{ property: 'Mcast IP', value: this.ou.networkSettings.mcastIp },
|
||||
{ property: 'Mcast Speed', value: this.ou.networkSettings.mcastSpeed },
|
||||
{ property: 'Mcast Port', value: this.ou.networkSettings.mcastPort },
|
||||
|
|
|
@ -65,7 +65,7 @@ export class LoginComponent {
|
|||
this.openSnackBar(false, 'Bienvenido ' + this.auth.username);
|
||||
this.router.navigateByUrl('/groups');
|
||||
this.dialog.open(GlobalStatusComponent, {
|
||||
width: '45vw',
|
||||
width: '65vw',
|
||||
height: '80vh',
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,8 +2,52 @@ mat-toolbar {
|
|||
/*height: 7vh;*/
|
||||
min-height: 65px;
|
||||
min-width: 375px;
|
||||
background-color: #e2e8f0;
|
||||
background: rgba(226, 232, 240, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
color: black;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 16px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
/* Estilos específicos para el botón del sidebar */
|
||||
.navbar-icon {
|
||||
color: #3f51b5;
|
||||
font-size: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
/* Asegurar que el botón del sidebar sea visible */
|
||||
mat-toolbar button[mat-icon-button] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s ease;
|
||||
color: #3f51b5;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
mat-toolbar button[mat-icon-button]:hover {
|
||||
background-color: rgba(63, 81, 181, 0.1);
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 2px 8px rgba(63, 81, 181, 0.2);
|
||||
}
|
||||
|
||||
.navbar-actions-row {
|
||||
|
@ -11,6 +55,7 @@ mat-toolbar {
|
|||
justify-content: end;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.navbar-buttons-row {
|
||||
|
@ -71,6 +116,7 @@ mat-toolbar {
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.trace-button .mat-icon {
|
||||
|
@ -81,3 +127,17 @@ mat-toolbar {
|
|||
margin-right: 2vh;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-toggle-right {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1100;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.menu-toggle-right {
|
||||
right: 8px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,11 @@
|
|||
matTooltipShowDelay="1000">
|
||||
</span>
|
||||
|
||||
<button mat-icon-button (click)="onToggleSidebar()" matTooltip="Abrir o cerrar la barra lateral"
|
||||
matTooltipShowDelay="1000">
|
||||
<mat-icon class="navbar-icon">menu</mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="navbar-actions-row" *ngIf="!isSmallScreen">
|
||||
<button mat-icon-button (click)="onToggleSidebar()" matTooltip="Abrir o cerrar la barra lateral"
|
||||
matTooltipShowDelay="1000">
|
||||
<mat-icon class="navbar-icon">menu</mat-icon>
|
||||
</button>
|
||||
<button routerLink="/commands-logs" mat-button>
|
||||
<mat-icon class="trace-button" >notifications</mat-icon>
|
||||
</button>
|
||||
|
@ -37,6 +36,10 @@
|
|||
|
||||
<!-- Menú desplegable para pantallas pequeñas -->
|
||||
<div *ngIf="isSmallScreen" class="isSmallScreenButtons">
|
||||
<button mat-icon-button (click)="onToggleSidebar()" matTooltip="Abrir o cerrar la barra lateral"
|
||||
matTooltipShowDelay="1000">
|
||||
<mat-icon class="navbar-icon">menu</mat-icon>
|
||||
</button>
|
||||
<button class="trace-button" routerLink="/commands-logs" mat-button>
|
||||
<mat-icon>notifications</mat-icon>
|
||||
</button>
|
||||
|
|
|
@ -42,7 +42,7 @@ export class HeaderComponent implements OnInit {
|
|||
|
||||
showGlobalStatus() {
|
||||
this.dialog.open(GlobalStatusComponent, {
|
||||
width: '45vw',
|
||||
width: '5vw',
|
||||
height: '80vh',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ html, body {
|
|||
|
||||
.container {
|
||||
height: 100%;
|
||||
padding-top: 65px; /* Asegurar que el contenido no se superponga con el header fijo */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
|
@ -28,3 +30,10 @@ html, body {
|
|||
.mat-list-item:hover {
|
||||
background-color: #2a2a40;
|
||||
}
|
||||
|
||||
/* Asegurar que el contenido principal tenga el espacio correcto */
|
||||
.content {
|
||||
padding-top: 0; /* El padding ya está en el container */
|
||||
height: calc(100vh - 65px); /* Altura total menos el header */
|
||||
overflow: auto;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue