develop #51
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -1,4 +1,17 @@
|
|||
# Changelog
|
||||
## [1.2.0] - 2025-10-21
|
||||
### Added
|
||||
- Se ha añadido una nueva funcionalidad la cual permite poder borrar varios equipos a la vez.
|
||||
|
||||
### Improved
|
||||
- Ahora no se pueden añadir equipos si no hay OUs creadas en el sistema.
|
||||
- Ahora aparecen los logs del cliente en tiempo real en estados mas alla de "og-live".
|
||||
- Se ha añadido el campo "mac" en el ogbrowser
|
||||
|
||||
### Fixed
|
||||
- Se ha corregido un error de sintaxis a la hora de cargar los iframe del ogLog.
|
||||
|
||||
---
|
||||
## [1.1.0] - 2025-10-17
|
||||
### Added
|
||||
- Se han incorporado opciones para poder seleccionar tamaños de pantalla para visualizar en los menu ogBrowser
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import { MatDividerModule } from '@angular/material/divider';
|
|||
import { MatStepperModule } from '@angular/material/stepper';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { DeleteModalComponent } from './shared/delete_modal/delete-modal/delete-modal.component';
|
||||
import { DeleteResultsModalComponent } from './components/groups/shared/delete-results-modal/delete-results-modal.component';
|
||||
import { ClassroomViewComponent } from './components/groups/shared/classroom-view/classroom-view.component';
|
||||
import { MatProgressSpinner } from "@angular/material/progress-spinner";
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
|
|
@ -198,6 +199,7 @@ registerLocaleData(localeEs, 'es-ES');
|
|||
GroupsComponent,
|
||||
ManageClientComponent,
|
||||
DeleteModalComponent,
|
||||
DeleteResultsModalComponent,
|
||||
QueueConfirmationModalComponent,
|
||||
ClassroomViewComponent,
|
||||
ClientViewComponent,
|
||||
|
|
|
|||
|
|
@ -1742,3 +1742,27 @@ mat-button-toggle-group {
|
|||
}
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background-color: #f44336 !important;
|
||||
color: white !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.delete-button:hover:not(:disabled) {
|
||||
background-color: #d32f2f !important;
|
||||
}
|
||||
|
||||
.delete-button:disabled {
|
||||
background-color: #ffcdd2 !important;
|
||||
color: #9e9e9e !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.delete-button mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,11 +20,14 @@
|
|||
matTooltip="{{ 'newOrganizationalUnitTooltip' | translate }}" matTooltipShowDelay="1000">
|
||||
{{ 'newOrganizationalUnitButton' | translate }}
|
||||
</button>
|
||||
<button class="action-button" [matMenuTriggerFor]="menuClients" *ngIf="auth.userCategory !== 'ou-minimal'">{{
|
||||
<button class="action-button" [matMenuTriggerFor]="menuClients" *ngIf="auth.userCategory !== 'ou-minimal'"
|
||||
[disabled]="!hasOrganizationalUnits()"
|
||||
matTooltip="{{ hasOrganizationalUnits() ? ('newClientButtonTooltip' | translate) : ('noOrganizationalUnitsTooltip' | translate) }}"
|
||||
matTooltipShowDelay="1000">{{
|
||||
'newClientButton' | translate }}</button>
|
||||
<mat-menu #menuClients="matMenu">
|
||||
<button mat-menu-item (click)="addClient($event)">{{ 'newSingleClientButton' | translate }}</button>
|
||||
<button mat-menu-item (click)="addMultipleClients($event)">{{ 'newMultipleClientButton' | translate
|
||||
<button mat-menu-item (click)="addClient($event)" [disabled]="!hasOrganizationalUnits()">{{ 'newSingleClientButton' | translate }}</button>
|
||||
<button mat-menu-item (click)="addMultipleClients($event)" [disabled]="!hasOrganizationalUnits()">{{ 'newMultipleClientButton' | translate
|
||||
}}</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
|
@ -43,10 +46,10 @@
|
|||
<button mat-menu-item (click)="addOU($event)">
|
||||
{{ 'newOrganizationalUnitButton' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="addClient($event)">
|
||||
<button mat-menu-item (click)="addClient($event)" [disabled]="!hasOrganizationalUnits()">
|
||||
{{ 'newSingleClientButton' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="addMultipleClients($event)">
|
||||
<button mat-menu-item (click)="addMultipleClients($event)" [disabled]="!hasOrganizationalUnits()">
|
||||
{{ 'newMultipleClientButton' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="openBottomSheet()">
|
||||
|
|
@ -243,11 +246,11 @@
|
|||
<mat-icon>map</mat-icon>
|
||||
<span>{{ 'roomMap' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addClient($event, selectedNode)" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<button mat-menu-item (click)="addClient($event, selectedNode)" *ngIf="auth.userCategory !== 'ou-minimal'" [disabled]="!hasOrganizationalUnits()">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span>{{ 'newSingleClientButton' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addMultipleClients($event, selectedNode)" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<button mat-menu-item (click)="addMultipleClients($event, selectedNode)" *ngIf="auth.userCategory !== 'ou-minimal'" [disabled]="!hasOrganizationalUnits()">
|
||||
<mat-icon>playlist_add</mat-icon>
|
||||
<span>{{ 'newMultipleClientButton' | translate }}</span>
|
||||
</button>
|
||||
|
|
@ -295,11 +298,16 @@
|
|||
matTooltip="{{ 'moveClientsTooltip' | translate }}" matTooltipShowDelay="1000">
|
||||
{{ 'changeOU' | translate }}
|
||||
</button>
|
||||
|
||||
<div joyrideStep="executeCommandStep" text="{{ 'executeCommandStepText' | translate }}">
|
||||
<app-execute-command [clientData]="selection.selected" [buttonType]="'text'"
|
||||
[buttonText]="'ejecutarComandos' | translate" [disabled]="selection.selected.length === 0"
|
||||
[runScriptContext]="getRunScriptContext(selection.selected)"></app-execute-command>
|
||||
</div>
|
||||
<button class="action-button delete-button" [disabled]="selection.selected.length === 0" (click)="deleteMultipleClients($event)"
|
||||
matTooltip="{{ 'deleteMultipleClientsTooltip' | translate }}" matTooltipShowDelay="1000">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
|
||||
<mat-button-toggle-group name="viewType" aria-label="View Type" [hideSingleSelectionIndicator]="true"
|
||||
(change)="toggleView($event.value)" joyrideStep="tabsStep" text="{{ 'tabsStepText' | translate }}">
|
||||
|
|
@ -418,7 +426,7 @@
|
|||
<mat-icon>pending_actions</mat-icon>
|
||||
<span>{{ 'colaAcciones' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item *ngIf="client.status === 'og-live'" (click)="openClientLogsInNewTab($event, client)">
|
||||
<button mat-menu-item (click)="openClientLogsInNewTab($event, client)">
|
||||
<mat-icon>article</mat-icon>
|
||||
<span>Logs en tiempo real</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import {ChangeParentComponent} from "./shared/change-parent/change-parent.compon
|
|||
import { AuthService } from '@services/auth.service';
|
||||
import { ClientPendingTasksComponent } from '../task-logs/client-pending-tasks/client-pending-tasks.component';
|
||||
import { ClientLogsModalComponent } from './shared/client-logs-modal/client-logs-modal.component';
|
||||
import { DeleteResultsModalComponent } from './shared/delete-results-modal/delete-results-modal.component';
|
||||
|
||||
enum NodeType {
|
||||
OrganizationalUnit = 'organizational-unit',
|
||||
|
|
@ -1228,6 +1229,10 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
return total;
|
||||
}
|
||||
|
||||
hasOrganizationalUnits(): boolean {
|
||||
return this.originalTreeData && this.originalTreeData.length > 0;
|
||||
}
|
||||
|
||||
getTotalClassrooms(): number {
|
||||
let total = 0;
|
||||
const countClassrooms = (nodes: TreeNode[]) => {
|
||||
|
|
@ -1244,10 +1249,69 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
return total;
|
||||
}
|
||||
|
||||
// Función para actualizar estadísticas cuando cambian los datos
|
||||
private updateDashboardStats(): void {
|
||||
// Las estadísticas de equipos ya se calculan en calculateLocalStats()
|
||||
// Solo necesitamos asegurar que se actualicen cuando cambian los datos
|
||||
|
||||
this.calculateLocalStats();
|
||||
}
|
||||
|
||||
deleteMultipleClients(event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.selection.selected.length === 0) {
|
||||
this.toastr.warning('No hay clientes seleccionados para eliminar');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedClients = this.selection.selected;
|
||||
const clientNames = selectedClients.map(client => client.name).join(', ');
|
||||
|
||||
const dialogRef = this.dialog.open(DeleteModalComponent, {
|
||||
width: '500px',
|
||||
data: {
|
||||
name: `${selectedClients.length} cliente${selectedClients.length > 1 ? 's' : ''}`,
|
||||
message: `¿Está seguro de que desea eliminar los siguientes clientes?\n\n${clientNames}`,
|
||||
isMultiple: true
|
||||
},
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
if (result === true) {
|
||||
this.performMultipleDeletion(selectedClients);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private performMultipleDeletion(selectedClients: any[]): void {
|
||||
this.loading = true;
|
||||
const clientUuids = selectedClients.map((client: any) => client['@id']);
|
||||
|
||||
this.dataService.deleteMultipleClients(clientUuids).subscribe({
|
||||
next: (response) => {
|
||||
this.loading = false;
|
||||
this.showDeletionResults(response, selectedClients);
|
||||
|
||||
this.refreshData(this.selectedNode?.id);
|
||||
this.selection.clear();
|
||||
},
|
||||
error: (error) => {
|
||||
this.loading = false;
|
||||
console.error('Error deleting multiple clients:', error);
|
||||
this.toastr.error('Error al eliminar los clientes seleccionados');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private showDeletionResults(response: any, selectedClients: any[]): void {
|
||||
const dialogRef = this.dialog.open(DeleteResultsModalComponent, {
|
||||
width: '600px',
|
||||
data: {
|
||||
response: response,
|
||||
selectedClients: selectedClients,
|
||||
totalSelected: selectedClients.length
|
||||
}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -246,4 +246,18 @@ export class DataService {
|
|||
|
||||
return path.join(' / ');
|
||||
}
|
||||
|
||||
deleteMultipleClients(clientUuids: string[]): Observable<any> {
|
||||
const url = `${this.baseUrl}/clients/delete-multiple`;
|
||||
const body = {
|
||||
clients: clientUuids
|
||||
};
|
||||
|
||||
return this.http.post<any>(url, body).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error deleting multiple clients', error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
<div class="iframe-container">
|
||||
<iframe
|
||||
[src]="iframeUrl | safe"
|
||||
[src]="iframeUrl"
|
||||
width="1200"
|
||||
height="800"
|
||||
frameborder="0"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,394 @@
|
|||
.delete-results-container {
|
||||
max-width: 100%;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 24px 16px 24px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
font-size: 28px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
color: #6c757d;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
color: #dc3545;
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 24px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Resumen general */
|
||||
.summary-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
border: 2px solid;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.success-card {
|
||||
background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.warning-card {
|
||||
background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
|
||||
border-color: #ffc107;
|
||||
}
|
||||
|
||||
.error-card {
|
||||
background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.summary-icon mat-icon {
|
||||
font-size: 32px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.success-card .summary-icon mat-icon {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.warning-card .summary-icon mat-icon {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.error-card .summary-icon mat-icon {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.summary-content h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.summary-content p {
|
||||
margin: 0;
|
||||
color: #6c757d;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Estadísticas */
|
||||
.stats-section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.success-stat {
|
||||
border-left-color: #28a745;
|
||||
}
|
||||
|
||||
.error-stat {
|
||||
border-left-color: #dc3545;
|
||||
}
|
||||
|
||||
.total-stat {
|
||||
border-left-color: #6c757d;
|
||||
}
|
||||
|
||||
.stat-item mat-icon {
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.success-stat mat-icon {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.error-stat mat-icon {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.total-stat mat-icon {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
/* Listas de clientes */
|
||||
.success-list, .error-list {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.success-list h4, .error-list h4 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.success-list h4 mat-icon {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.error-list h4 mat-icon {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.client-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.client-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.client-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.client-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.client-item mat-icon {
|
||||
color: #6c757d;
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.success-item mat-icon {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.client-name {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.client-ip {
|
||||
color: #6c757d;
|
||||
font-size: 0.9rem;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* Errores */
|
||||
.error-details {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.error-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
}
|
||||
|
||||
.error-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.error-item mat-icon {
|
||||
color: #dc3545;
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.error-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.error-client {
|
||||
color: #6c757d;
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Acciones */
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
color: #dc3545;
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.modal-header {
|
||||
padding: 16px 20px 12px 20px;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.summary-icon mat-icon {
|
||||
font-size: 28px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.modal-header {
|
||||
padding: 12px 16px 8px 16px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.client-item, .error-item {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<div class="delete-results-container">
|
||||
<div class="modal-header">
|
||||
<h2 mat-dialog-title>
|
||||
<mat-icon class="header-icon" [ngClass]="{
|
||||
'success-icon': isCompleteSuccess,
|
||||
'warning-icon': isPartialSuccess,
|
||||
'error-icon': isCompleteFailure
|
||||
}">
|
||||
{{ isCompleteSuccess ? 'check_circle' : isPartialSuccess ? 'warning' : 'error' }}
|
||||
</mat-icon>
|
||||
Resultados de Eliminación
|
||||
</h2>
|
||||
<button mat-icon-button (click)="onClose()" class="close-button">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div mat-dialog-content class="modal-content">
|
||||
<!-- Resumen general -->
|
||||
<div class="summary-section">
|
||||
<div class="summary-card" [ngClass]="{
|
||||
'success-card': isCompleteSuccess,
|
||||
'warning-card': isPartialSuccess,
|
||||
'error-card': isCompleteFailure
|
||||
}">
|
||||
<div class="summary-icon">
|
||||
<mat-icon>{{ isCompleteSuccess ? 'check_circle' : isPartialSuccess ? 'warning' : 'error' }}</mat-icon>
|
||||
</div>
|
||||
<div class="summary-content">
|
||||
<h3>{{ getSummaryTitle() }}</h3>
|
||||
<p>{{ getSummaryMessage() }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lista de clientes eliminados exitosamente -->
|
||||
<div class="success-list" *ngIf="successCount > 0">
|
||||
<h4>
|
||||
Clientes Eliminados ({{ successCount }})
|
||||
</h4>
|
||||
<div class="client-list">
|
||||
<div class="client-item success-item" *ngFor="let client of getSuccessfullyDeletedClients()">
|
||||
<mat-icon>computer</mat-icon>
|
||||
<span class="client-name">{{ client.name }}</span>
|
||||
<span class="client-ip">{{ client.ip }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lista de errores si los hay -->
|
||||
<div class="error-list" *ngIf="hasErrors">
|
||||
<h4>
|
||||
<mat-icon>error</mat-icon>
|
||||
Errores Encontrados ({{ errorCount }})
|
||||
</h4>
|
||||
<div class="error-details">
|
||||
<div class="error-item" *ngFor="let error of data.response.errors">
|
||||
<mat-icon>warning</mat-icon>
|
||||
<div class="error-content">
|
||||
<span class="error-message">{{ error.message || 'Error desconocido' }}</span>
|
||||
<span class="error-client" *ngIf="error.client">Cliente: {{ error.client }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div mat-dialog-actions class="modal-actions">
|
||||
<button mat-button (click)="onClose()" class="close-btn">
|
||||
<mat-icon>close</mat-icon>
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
export interface DeleteResultsData {
|
||||
response: {
|
||||
deleted: number;
|
||||
total: number;
|
||||
success: boolean;
|
||||
errors?: any[];
|
||||
};
|
||||
selectedClients: any[];
|
||||
totalSelected: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-delete-results-modal',
|
||||
templateUrl: './delete-results-modal.component.html',
|
||||
styleUrls: ['./delete-results-modal.component.css']
|
||||
})
|
||||
export class DeleteResultsModalComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<DeleteResultsModalComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: DeleteResultsData
|
||||
) {}
|
||||
|
||||
onClose(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
get successCount(): number {
|
||||
return this.data.response.deleted;
|
||||
}
|
||||
|
||||
get errorCount(): number {
|
||||
return this.data.totalSelected - this.data.response.deleted;
|
||||
}
|
||||
|
||||
get hasErrors(): boolean {
|
||||
return !!(this.data.response.errors && this.data.response.errors.length > 0);
|
||||
}
|
||||
|
||||
get isPartialSuccess(): boolean {
|
||||
return this.successCount > 0 && this.errorCount > 0;
|
||||
}
|
||||
|
||||
get isCompleteSuccess(): boolean {
|
||||
return this.successCount === this.data.totalSelected && this.errorCount === 0;
|
||||
}
|
||||
|
||||
get isCompleteFailure(): boolean {
|
||||
return this.successCount === 0 && this.errorCount === this.data.totalSelected;
|
||||
}
|
||||
|
||||
getSummaryTitle(): string {
|
||||
if (this.isCompleteSuccess) {
|
||||
return 'Eliminación Completada';
|
||||
} else if (this.isPartialSuccess) {
|
||||
return 'Eliminación Parcial';
|
||||
} else {
|
||||
return 'Error en Eliminación';
|
||||
}
|
||||
}
|
||||
|
||||
getSummaryMessage(): string {
|
||||
if (this.isCompleteSuccess) {
|
||||
return `Se eliminaron exitosamente todos los ${this.data.totalSelected} cliente${this.data.totalSelected > 1 ? 's' : ''} seleccionado${this.data.totalSelected > 1 ? 's' : ''}.`;
|
||||
} else if (this.isPartialSuccess) {
|
||||
return `Se eliminaron ${this.successCount} de ${this.data.totalSelected} cliente${this.data.totalSelected > 1 ? 's' : ''} seleccionado${this.data.totalSelected > 1 ? 's' : ''}. ${this.errorCount} cliente${this.errorCount > 1 ? 's' : ''} no se pudieron eliminar.`;
|
||||
} else {
|
||||
return `No se pudo eliminar ninguno de los ${this.data.totalSelected} cliente${this.data.totalSelected > 1 ? 's' : ''} seleccionado${this.data.totalSelected > 1 ? 's' : ''}.`;
|
||||
}
|
||||
}
|
||||
|
||||
getSuccessfullyDeletedClients(): any[] {
|
||||
// En una implementación real, esto debería comparar con la respuesta del servidor
|
||||
// Por ahora, asumimos que todos los clientes seleccionados fueron eliminados exitosamente
|
||||
// si no hay errores específicos
|
||||
if (this.data.response.errors && this.data.response.errors.length > 0) {
|
||||
// Si hay errores, necesitaríamos lógica más compleja para determinar cuáles se eliminaron
|
||||
// Por simplicidad, mostramos todos los seleccionados
|
||||
return this.data.selectedClients.slice(0, this.successCount);
|
||||
}
|
||||
return this.data.selectedClients;
|
||||
}
|
||||
}
|
||||
|
|
@ -173,6 +173,8 @@
|
|||
"newClientButton": "New Client",
|
||||
"newSingleClientButton": "Add single client",
|
||||
"newMultipleClientButton": "Add numerous clients",
|
||||
"newClientButtonTooltip": "Create a new client",
|
||||
"noOrganizationalUnitsTooltip": "You must create at least one organizational unit before adding clients",
|
||||
"keyStepText": "The legend will show you the types of organizational units and their corresponding icons",
|
||||
"legendButton": "Legend",
|
||||
"unitStepText": "This is the section where 'Center' type organizational units will be displayed",
|
||||
|
|
@ -519,6 +521,8 @@
|
|||
},
|
||||
"changeOU": "Change Organizational Unit",
|
||||
"moveClientsTooltip": "Move clients to another organizational unit",
|
||||
"deleteMultipleClients": "Delete selected",
|
||||
"deleteMultipleClientsTooltip": "Delete all selected clients",
|
||||
"remoteAccess": "Remote access available",
|
||||
"noRemoteAccess": "Remote access not available",
|
||||
"capacityWarning": "The capacity cannot be negative",
|
||||
|
|
|
|||
|
|
@ -173,6 +173,8 @@
|
|||
"newClientButton": "Nuevo Cliente",
|
||||
"newSingleClientButton": "Añadir cliente unitario",
|
||||
"newMultipleClientButton": "Añadir clientes masivamente",
|
||||
"newClientButtonTooltip": "Crear un nuevo cliente",
|
||||
"noOrganizationalUnitsTooltip": "Debe crear al menos una unidad organizativa antes de añadir clientes",
|
||||
"keyStepText": "La leyenda te mostrará los tipos de unidades organizativas y sus iconos correspondientes",
|
||||
"legendButton": "Leyenda",
|
||||
"unitStepText": "Esta es la sección donde se mostrarán las unidades organizativas de tipo 'Centro'",
|
||||
|
|
@ -518,8 +520,10 @@
|
|||
"hardwareInventory": "Inventario Hardware",
|
||||
"runScript": "Ejecutar script"
|
||||
},
|
||||
"changeOU": "Mover clientes",
|
||||
"changeOU": "Mover",
|
||||
"moveClientsTooltip": "Mover clientes a otra unidad organizativa",
|
||||
"deleteMultipleClients": "Eliminar seleccionados",
|
||||
"deleteMultipleClientsTooltip": "Eliminar todos los clientes seleccionados",
|
||||
"remoteAccess": "Disponible acceso remoto",
|
||||
"noRemoteAccess": "No disponible acceso remoto",
|
||||
"capacityWarning": "El aforo no puede ser",
|
||||
|
|
|
|||
Loading…
Reference in New Issue