feat(commands): support offline client execution with auto-queueing and modernize components
parent
414076b45f
commit
5576757c7b
14
CHANGELOG.md
14
CHANGELOG.md
|
|
@ -1,5 +1,19 @@
|
|||
# Changelog
|
||||
|
||||
## [1.15.0] - 2026-10-20
|
||||
|
||||
### Added
|
||||
- **Ejecución y Encolado Automático en Clientes Offline (`execute-command`, `boot-so-partition`, `queue-confirmation-modal`)**:
|
||||
- Permitida la ejecución de comandos y scripts (incluyendo inicio de sesión en partición / arranque de SO) sobre equipos apagados o grupos con estados mixtos.
|
||||
- El modal de confirmación detecta automáticamente equipos apagados o estados no ejecutables de inmediato, pre-seleccionando la opción de encolado y mostrando avisos visuales informativos (disponible en ES, EN y CA).
|
||||
- Unificada y estandarizada la matriz de estados de todos los comandos en la consola de comandos.
|
||||
|
||||
### Fixed
|
||||
- **Selección de Clientes en Unidades Organizativas (`groups.component.html`)**: Solucionado el problema al invocar comandos o tareas desde el menú contextual de un Aula / OU donde solo se seleccionaban los equipos mostrados en la página actual de la tabla en lugar de todos los clientes pertenecientes a la OU.
|
||||
|
||||
### Changed
|
||||
- **Refactorización Angular (`boot-so-partition`)**: Migradas directivas estructurales (`*ngIf`, `*ngFor`) a la sintaxis moderna de control de flujo (`@if`, `@for`) y extraídos los estilos inline a clases CSS reutilizables.
|
||||
|
||||
## [1.14.4] - 2026-10-09
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -99,6 +99,10 @@
|
|||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.section-divider {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.form-field {
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -1,66 +1,70 @@
|
|||
<h2 mat-dialog-title> {{ 'selectPartitionToBootOs' | translate }}</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
@if (loading) {
|
||||
<mat-spinner class="loading-spinner"></mat-spinner>
|
||||
} @else {
|
||||
<div class="select-container">
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> {{ 'labelClients' | translate }}</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
{{ 'clientListBootOs' | translate }}<mat-icon>desktop_windows</mat-icon>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div *ngIf="!loading" class="select-container">
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> {{ 'labelClients' | translate }}</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
{{ 'clientListBootOs' | translate }}<mat-icon>desktop_windows</mat-icon>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div class="button-row">
|
||||
<button class="action-button" (click)="toggleSelectAll()">
|
||||
{{ allSelected ? 'Desmarcar todos' : 'Marcar todos' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="clients-grid">
|
||||
<div *ngFor="let client of data.clients" class="client-item">
|
||||
<div class="client-card"
|
||||
(click)="toggleClientSelection(client)"
|
||||
[ngClass]="{'selected-client': client.selected}" >
|
||||
|
||||
<img
|
||||
[src]="'assets/images/computer_' + client.status + '.svg'"
|
||||
alt="Client Icon"
|
||||
class="client-image" />
|
||||
|
||||
<div class="client-details">
|
||||
<span class="client-name">{{ client.name | slice:0:20 }}</span>
|
||||
<span class="client-ip">{{ client.ip }}</span>
|
||||
<span class="client-ip">{{ client.mac }}</span>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<mat-radio-group [(ngModel)]="selectedModelClient" (change)="loadPartitions(selectedModelClient)">
|
||||
<mat-radio-button [value]="client"
|
||||
color="primary"
|
||||
[disabled]="!client.selected"
|
||||
(click)="$event.stopPropagation()">
|
||||
{{ 'labelModel' | translate }}</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button class="action-button" (click)="toggleSelectAll()">
|
||||
{{ allSelected ? 'Desmarcar todos' : 'Marcar todos' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</mat-expansion-panel>
|
||||
</div>
|
||||
<div class="clients-grid">
|
||||
@for (client of data.clients; track client.uuid) {
|
||||
<div class="client-item">
|
||||
<div class="client-card"
|
||||
(click)="toggleClientSelection(client)"
|
||||
[ngClass]="{'selected-client': client.selected}" >
|
||||
|
||||
<mat-divider *ngIf="!loading" style="margin-top: 20px;"></mat-divider>
|
||||
<img
|
||||
[src]="'assets/images/computer_' + client.status + '.svg'"
|
||||
alt="Client Icon"
|
||||
class="client-image" />
|
||||
|
||||
<div *ngIf="!loading" class="partition-table-container">
|
||||
<app-disk-partition-selector
|
||||
[(selectedPartition)]="selectedPartition"
|
||||
[partitions]="dataSource.data"
|
||||
[columns]="columns"
|
||||
[displayedColumns]="displayedColumns"
|
||||
[disableCondition]="isPartitionDisabled">
|
||||
</app-disk-partition-selector>
|
||||
</div>
|
||||
<div class="client-details">
|
||||
<span class="client-name">{{ client.name | slice:0:20 }}</span>
|
||||
<span class="client-ip">{{ client.ip }}</span>
|
||||
<span class="client-ip">{{ client.mac }}</span>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<mat-radio-group [(ngModel)]="selectedModelClient" (change)="loadPartitions(selectedModelClient)">
|
||||
<mat-radio-button [value]="client"
|
||||
color="primary"
|
||||
[disabled]="!client.selected"
|
||||
(click)="$event.stopPropagation()">
|
||||
{{ 'labelModel' | translate }}</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</mat-expansion-panel>
|
||||
</div>
|
||||
|
||||
<mat-divider class="section-divider"></mat-divider>
|
||||
|
||||
<div class="partition-table-container">
|
||||
<app-disk-partition-selector
|
||||
[(selectedPartition)]="selectedPartition"
|
||||
[partitions]="dataSource.data"
|
||||
[columns]="columns"
|
||||
[displayedColumns]="displayedColumns"
|
||||
[disableCondition]="isPartitionDisabled">
|
||||
</app-disk-partition-selector>
|
||||
</div>
|
||||
}
|
||||
</mat-dialog-content>
|
||||
|
||||
<div mat-dialog-actions class="action-container">
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ export class BootSoPartitionComponent implements OnInit {
|
|||
|
||||
updateSelectedClients() {
|
||||
this.selectedClients = this.data.clients.filter(
|
||||
(client: { selected: boolean; state: string }) => client.selected && client.state === "og-live"
|
||||
(client: { selected: boolean }) => client.selected
|
||||
);
|
||||
|
||||
if (!this.selectedClients.includes(this.selectedModelClient)) {
|
||||
|
|
@ -124,11 +124,10 @@ export class BootSoPartitionComponent implements OnInit {
|
|||
|
||||
toggleSelectAll() {
|
||||
this.allSelected = !this.allSelected;
|
||||
this.data.clients.forEach((client: { selected: boolean; status: string }) => {
|
||||
if (client.status === "og-live") {
|
||||
client.selected = this.allSelected;
|
||||
}
|
||||
this.data.clients.forEach((client: { selected: boolean }) => {
|
||||
client.selected = this.allSelected;
|
||||
});
|
||||
this.updateSelectedClients();
|
||||
}
|
||||
|
||||
close() {
|
||||
|
|
|
|||
|
|
@ -1,24 +1,27 @@
|
|||
<ng-container [ngSwitch]="buttonType">
|
||||
<button *ngSwitchCase="'icon'" mat-icon-button color="primary" [matMenuTriggerFor]="commandMenu"
|
||||
[disabled]="disabled">
|
||||
<mat-icon>{{ icon }}</mat-icon>
|
||||
</button>
|
||||
|
||||
<button class="action-button" [disabled]="clientData.length === 0 || disabled" *ngSwitchCase="'text'"
|
||||
[matMenuTriggerFor]="commandMenu">
|
||||
{{ buttonText }}
|
||||
</button>
|
||||
|
||||
<button mat-menu-item *ngSwitchCase="'menu-item'" [matMenuTriggerFor]="commandMenu" [disabled]="disabled">
|
||||
<mat-icon>{{ icon }}</mat-icon>
|
||||
<span>{{ buttonText }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
@switch (buttonType) {
|
||||
@case ('icon') {
|
||||
<button mat-icon-button color="primary" [matMenuTriggerFor]="commandMenu" [disabled]="disabled">
|
||||
<mat-icon>{{ icon }}</mat-icon>
|
||||
</button>
|
||||
}
|
||||
@case ('text') {
|
||||
<button class="action-button" [disabled]="clientData.length === 0 || disabled" [matMenuTriggerFor]="commandMenu">
|
||||
{{ buttonText }}
|
||||
</button>
|
||||
}
|
||||
@case ('menu-item') {
|
||||
<button mat-menu-item [matMenuTriggerFor]="commandMenu" [disabled]="disabled">
|
||||
<mat-icon>{{ icon }}</mat-icon>
|
||||
<span>{{ buttonText }}</span>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
<mat-menu #commandMenu="matMenu">
|
||||
<button mat-menu-item [disabled]="command.disabled
|
||||
|| (command.slug === 'create-image' && clientData.length > 1)" *ngFor="let command of arrayCommands"
|
||||
(click)="onCommandSelect(command.slug)">
|
||||
{{ command.translationKey | translate }}
|
||||
</button>
|
||||
@for (command of arrayCommands; track command.slug) {
|
||||
<button mat-menu-item [disabled]="command.disabled || (command.slug === 'create-image' && clientData.length > 1)"
|
||||
(click)="onCommandSelect(command.slug)">
|
||||
{{ command.translationKey | translate }}
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
|
|
@ -21,6 +21,8 @@ import { ExecuteTaskNowComponent } from './execute-task-now/execute-task-now.com
|
|||
export class ExecuteCommandComponent implements OnInit {
|
||||
public translate = inject(TranslateService);
|
||||
@Input() runScriptContext: any = null;
|
||||
@Input() organizationalUnit: any = null;
|
||||
@Input() showOnlyDirectClients: boolean = false;
|
||||
@Input() clientState: string = 'off';
|
||||
@Input() clientData: any[] = [];
|
||||
@Input() buttonType: 'icon' | 'text' | 'menu-item' = 'icon';
|
||||
|
|
@ -29,12 +31,13 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
@Input() disabled: boolean = false;
|
||||
baseUrl: string;
|
||||
loading: boolean = true;
|
||||
isFetchingOuClients: boolean = false;
|
||||
|
||||
arrayCommands: any[] = [
|
||||
{ translationKey: 'executeCommands.powerOn', slug: 'power-on', disabled: false },
|
||||
{ translationKey: 'executeCommands.powerOff', slug: 'power-off', disabled: false },
|
||||
{ translationKey: 'executeCommands.reboot', slug: 'reboot', disabled: false },
|
||||
{ translationKey: 'executeCommands.login', slug: 'login', disabled: true },
|
||||
{ translationKey: 'executeCommands.login', slug: 'login', disabled: false },
|
||||
{ translationKey: 'executeCommands.createImage', slug: 'create-image', disabled: false },
|
||||
{ translationKey: 'executeCommands.deployImage', slug: 'deploy-image', disabled: false },
|
||||
{ translationKey: 'executeCommands.deleteImageCache', slug: 'remove-cache-image', disabled: false },
|
||||
|
|
@ -113,37 +116,42 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
states = [this.clientState];
|
||||
}
|
||||
|
||||
const allOffOrDisconnected = states.every(state => state === 'off' || state === 'disconnected');
|
||||
const allSameState = states.every(state => state === states[0]);
|
||||
const hasClients = this.clientData.length > 0 || !!this.clientState;
|
||||
const multipleClients = this.clientData.length > 1;
|
||||
const hasOgLive = states.some(state => state === 'og-live');
|
||||
|
||||
// Acciones que admiten encolado o son independientes del estado del cliente
|
||||
const queueableOrStateAgnostic = [
|
||||
'power-on',
|
||||
'power-off',
|
||||
'reboot',
|
||||
'login',
|
||||
'deploy-image',
|
||||
'partition',
|
||||
'run-script',
|
||||
'execute-task',
|
||||
'delete-lease'
|
||||
];
|
||||
|
||||
this.arrayCommands = this.arrayCommands.map(command => {
|
||||
if (allOffOrDisconnected) {
|
||||
command.disabled = command.slug !== 'power-on' && command.slug !== 'create-image';
|
||||
} else if (allSameState) {
|
||||
if (states[0] === 'off' || states[0] === 'disconnected') {
|
||||
command.disabled = !['power-on', 'create-image'].includes(command.slug);
|
||||
} else {
|
||||
command.disabled = !['power-off', 'reboot', 'login', 'create-image', 'deploy-image', 'remove-cache-image', 'partition', 'run-script', 'software-inventory', 'hardware-inventory', 'refresh'].includes(command.slug);
|
||||
}
|
||||
} else {
|
||||
if (command.slug === 'software-inventory') {
|
||||
command.disabled = multipleClients;
|
||||
} else if (
|
||||
['power-on', 'power-off', 'reboot', 'login', 'deploy-image', 'partition', 'remove-cache-image', 'run-script', 'create-image'].includes(command.slug)
|
||||
) {
|
||||
command.disabled = false;
|
||||
} else {
|
||||
command.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (command.slug === 'refresh') {
|
||||
command.disabled = !states.every(state => state === 'og-live');
|
||||
if (!hasClients) {
|
||||
command.disabled = true;
|
||||
return command;
|
||||
}
|
||||
|
||||
if (command.slug === 'execute-task') {
|
||||
command.disabled = this.clientData.length === 0;
|
||||
// Restricciones lógicas estrictas
|
||||
if (command.slug === 'create-image') {
|
||||
command.disabled = multipleClients || !hasOgLive;
|
||||
} else if (command.slug === 'hardware-inventory' || command.slug === 'software-inventory') {
|
||||
command.disabled = multipleClients || !hasOgLive;
|
||||
} else if (command.slug === 'remove-cache-image') {
|
||||
command.disabled = !hasOgLive;
|
||||
} else if (command.slug === 'refresh') {
|
||||
command.disabled = !hasOgLive;
|
||||
} else if (queueableOrStateAgnostic.includes(command.slug)) {
|
||||
command.disabled = false;
|
||||
} else {
|
||||
command.disabled = false;
|
||||
}
|
||||
|
||||
return command;
|
||||
|
|
@ -151,6 +159,48 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
}
|
||||
|
||||
onCommandSelect(action: any): void {
|
||||
if (this.organizationalUnit && this.organizationalUnit.id) {
|
||||
this.fetchClientsForOuAndExecute(action);
|
||||
return;
|
||||
}
|
||||
|
||||
this.executeAction(action);
|
||||
}
|
||||
|
||||
private fetchClientsForOuAndExecute(action: any): void {
|
||||
if (this.isFetchingOuClients) {
|
||||
return;
|
||||
}
|
||||
this.isFetchingOuClients = true;
|
||||
|
||||
const unitId = this.organizationalUnit.id;
|
||||
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${unitId}&page=1&itemsPerPage=10000`).subscribe({
|
||||
next: (response) => {
|
||||
this.isFetchingOuClients = false;
|
||||
let allClients: any[] = response['hydra:member'] || [];
|
||||
|
||||
if (this.showOnlyDirectClients && this.organizationalUnit['@id']) {
|
||||
allClients = allClients.filter((client: any) =>
|
||||
client.organizationalUnit?.['@id'] === this.organizationalUnit['@id']
|
||||
);
|
||||
}
|
||||
|
||||
if (allClients.length === 0) {
|
||||
this.toastService.warning(this.translate.instant('thisOrganizationalUnitDoesNotHave') || 'Esta unidad organizativa no tiene clientes.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.clientData = allClients;
|
||||
this.executeAction(action);
|
||||
},
|
||||
error: (error) => {
|
||||
this.isFetchingOuClients = false;
|
||||
this.toastService.error(this.translate.instant('errorLoadingClients1') || 'Error al cargar los clientes');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private executeAction(action: any): void {
|
||||
if (action === 'partition') {
|
||||
this.openPartitionAssistant();
|
||||
}
|
||||
|
|
@ -217,7 +267,10 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
}
|
||||
|
||||
rebootClient(): void {
|
||||
const dialogRef = this.dialog.open(QueueConfirmationModalComponent, { width: '400px' });
|
||||
const dialogRef = this.dialog.open(QueueConfirmationModalComponent, {
|
||||
width: '400px',
|
||||
data: { clients: this.clientData }
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((queue: boolean | undefined) => {
|
||||
if (queue === undefined) return;
|
||||
|
|
@ -356,7 +409,10 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
}
|
||||
|
||||
powerOffClient(): void {
|
||||
const dialogRef = this.dialog.open(QueueConfirmationModalComponent, { width: '400px' });
|
||||
const dialogRef = this.dialog.open(QueueConfirmationModalComponent, {
|
||||
width: '400px',
|
||||
data: { clients: this.clientData }
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((queue: boolean | undefined) => {
|
||||
if (queue === undefined) return;
|
||||
|
|
|
|||
|
|
@ -372,7 +372,8 @@ export class CreateClientImageComponent implements OnInit{
|
|||
width: '400px',
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop'
|
||||
backdropClass: 'non-clickable-backdrop',
|
||||
data: { clients: this.client ? [this.client] : [] }
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
|
|
|
|||
|
|
@ -481,7 +481,8 @@ export class DeployImageComponent implements OnInit {
|
|||
width: '400px',
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop'
|
||||
backdropClass: 'non-clickable-backdrop',
|
||||
data: { clients: this.clientData }
|
||||
});
|
||||
|
||||
this.toastService.info(this.translate.instant('preparingDeploymentRequest'));
|
||||
|
|
|
|||
|
|
@ -523,7 +523,8 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes
|
|||
width: '400px',
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop'
|
||||
backdropClass: 'non-clickable-backdrop',
|
||||
data: { clients: this.clientData }
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
|
|
|
|||
|
|
@ -195,7 +195,8 @@ export class RunScriptAssistantComponent implements OnInit{
|
|||
const dialogRef = this.dialog.open(QueueConfirmationModalComponent, {
|
||||
width: '400px',
|
||||
disableClose: true,
|
||||
hasBackdrop: true
|
||||
hasBackdrop: true,
|
||||
data: { clients: this.selectedClients }
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
|
|
|
|||
|
|
@ -281,10 +281,15 @@
|
|||
<mat-icon>pending_actions</mat-icon>
|
||||
<span>{{ 'colaAcciones' | translate }}</span>
|
||||
</button>
|
||||
<app-execute-command [clientData]="selectedNode?.clients || []" [buttonType]="'menu-item'"
|
||||
[buttonText]="'ejecutarComandos' | translate" [icon]="'terminal'"
|
||||
[disabled]="!((selectedNode?.clients ?? []).length > 0)" [runScriptContext]="selectedNode?.name || ''"
|
||||
[runScriptContext]="getRunScriptContext(selectedNode?.clients || [])">
|
||||
<app-execute-command
|
||||
[organizationalUnit]="selectedNode"
|
||||
[showOnlyDirectClients]="showOnlyDirectClients"
|
||||
[clientData]="selectedNode?.clients || []"
|
||||
[buttonType]="'menu-item'"
|
||||
[buttonText]="'ejecutarComandos' | translate"
|
||||
[icon]="'terminal'"
|
||||
[disabled]="!((selectedNode?.clients ?? []).length > 0)"
|
||||
[runScriptContext]="selectedNode">
|
||||
</app-execute-command>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -51,4 +51,15 @@ mat-dialog-content {
|
|||
mat-checkbox {
|
||||
margin-top: 15px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.queue-warning-info {
|
||||
background-color: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 10px 14px;
|
||||
border-radius: 4px;
|
||||
color: #856404;
|
||||
font-size: 14px;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
|
@ -1,6 +1,12 @@
|
|||
<h1 mat-dialog-title>{{ 'confirmationTitle' | translate }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>{{ 'queueActionsForFailedPcs' | translate }}</p>
|
||||
@if (allOff) {
|
||||
<p class="queue-warning-info">{{ 'queueAllOffWarning' | translate }}</p>
|
||||
} @else if (hasMixedStates) {
|
||||
<p class="queue-warning-info">{{ 'queueMixedStatesWarning' | translate: { on: onCount, off: offCount } }}</p>
|
||||
} @else {
|
||||
<p>{{ 'queueActionsForFailedPcs' | translate }}</p>
|
||||
}
|
||||
<mat-checkbox [(ngModel)]="shouldQueue">{{ 'queueActions' | translate }}</mat-checkbox>
|
||||
</div>
|
||||
<div mat-dialog-actions class="action-container">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
|
|
@ -6,14 +6,36 @@ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
|||
templateUrl: './queue-confirmation-modal.component.html',
|
||||
styleUrl: './queue-confirmation-modal.component.css'
|
||||
})
|
||||
export class QueueConfirmationModalComponent {
|
||||
export class QueueConfirmationModalComponent implements OnInit {
|
||||
shouldQueue: boolean = false;
|
||||
allOff: boolean = false;
|
||||
hasMixedStates: boolean = false;
|
||||
onCount: number = 0;
|
||||
offCount: number = 0;
|
||||
totalCount: number = 0;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<QueueConfirmationModalComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
const clients: any[] = this.data?.clients || [];
|
||||
if (clients.length > 0) {
|
||||
this.totalCount = clients.length;
|
||||
this.offCount = clients.filter(c => !c.status || c.status === 'off' || c.status === 'disconnected').length;
|
||||
this.onCount = this.totalCount - this.offCount;
|
||||
|
||||
if (this.offCount === this.totalCount) {
|
||||
this.allOff = true;
|
||||
this.shouldQueue = true;
|
||||
} else if (this.offCount > 0) {
|
||||
this.hasMixedStates = true;
|
||||
this.shouldQueue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1169,6 +1169,8 @@
|
|||
"pxeTemplateUpdatedSuccessfully": "Plantilla PXE actualitzada correctament",
|
||||
"queueActions": "Encuar accions",
|
||||
"queueActionsForFailedPcs": "Vols que s'encuin les accions per als PCs que no es pugui executar?",
|
||||
"queueAllOffWarning": "Tots els equips seleccionats estan apagats o desconnectats. L'acció s'encolarà per executar-se quan arrenquin.",
|
||||
"queueMixedStatesWarning": "Hi ha {{ on }} equips encesos i {{ off }} apagats o desconnectats. Es recomana encuar perquè els equips apagats executin l'acció en arrencar.",
|
||||
"RamUsage": "Ús de RAM",
|
||||
"readOnly": "Només Lectura",
|
||||
"realTimeDhcpLogs": "Logs en temps real del servidor DHCP",
|
||||
|
|
|
|||
|
|
@ -1169,6 +1169,8 @@
|
|||
"pxeTemplateUpdatedSuccessfully": "PXE template updated successfully",
|
||||
"queueActions": "Queue actions",
|
||||
"queueActionsForFailedPcs": "Do you want to queue actions for PCs that cannot execute?",
|
||||
"queueAllOffWarning": "All selected computers are turned off or disconnected. The action will be queued to run when they start.",
|
||||
"queueMixedStatesWarning": "There are {{ on }} computers on and {{ off }} turned off or disconnected. It is recommended to queue so that turned-off computers execute the action on startup.",
|
||||
"RamUsage": "RAM Usage",
|
||||
"readOnly": "Read Only",
|
||||
"realTimeDhcpLogs": "Real-time DHCP server logs",
|
||||
|
|
|
|||
|
|
@ -1169,6 +1169,8 @@
|
|||
"pxeTemplateUpdatedSuccessfully": "Plantilla PXE actualizada con éxito",
|
||||
"queueActions": "Encolar acciones",
|
||||
"queueActionsForFailedPcs": "¿Quieres que se encolen las acciones para los PCs que no se pueda ejecutar?",
|
||||
"queueAllOffWarning": "Todos los equipos seleccionados están apagados o desconectados. La acción se encolará para ejecutarse cuando arranquen.",
|
||||
"queueMixedStatesWarning": "Hay {{ on }} equipos encendidos y {{ off }} apagados o desconectados. Se recomienda encolar para que los equipos apagados ejecuten la acción al encender.",
|
||||
"RamUsage": "Uso de RAM",
|
||||
"readOnly": "Solo Lectura",
|
||||
"realTimeDhcpLogs": "Logs en tiempo real del servidor DHCP",
|
||||
|
|
|
|||
Loading…
Reference in New Issue