parent
f0e3a34f00
commit
1962beaf8a
|
@ -1,20 +1,18 @@
|
||||||
<ng-container [ngSwitch]="buttonType">
|
<ng-container [ngSwitch]="buttonType">
|
||||||
<button *ngSwitchCase="'icon'" mat-icon-button color="primary" [matMenuTriggerFor]="commandMenu">
|
<button *ngSwitchCase="'icon'" mat-icon-button color="primary" [matMenuTriggerFor]="commandMenu"
|
||||||
|
[disabled]="disabled">
|
||||||
<mat-icon>{{ icon }}</mat-icon>
|
<mat-icon>{{ icon }}</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="action-button" [disabled]="clientData.length === 0" *ngSwitchCase="'text'" [matMenuTriggerFor]="commandMenu">
|
<button class="action-button" [disabled]="clientData.length === 0 || disabled" *ngSwitchCase="'text'"
|
||||||
|
[matMenuTriggerFor]="commandMenu">
|
||||||
{{ buttonText }}
|
{{ buttonText }}
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<mat-menu #commandMenu="matMenu" >
|
<mat-menu #commandMenu="matMenu">
|
||||||
<button
|
<button mat-menu-item [disabled]="command.disabled || (command.slug === 'create-image' && clientData.length > 1)"
|
||||||
mat-menu-item
|
*ngFor="let command of arrayCommands" (click)="onCommandSelect(command.slug)">
|
||||||
[disabled]="command.disabled || (command.slug === 'create-image' && clientData.length > 1)"
|
|
||||||
*ngFor="let command of arrayCommands"
|
|
||||||
(click)="onCommandSelect(command.slug)"
|
|
||||||
>
|
|
||||||
{{ command.name }}
|
{{ command.name }}
|
||||||
</button>
|
</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
|
@ -15,6 +15,7 @@ export class ExecuteCommandComponent implements OnInit {
|
||||||
@Input() buttonType: 'icon' | 'text' = 'icon';
|
@Input() buttonType: 'icon' | 'text' = 'icon';
|
||||||
@Input() buttonText: string = 'Ejecutar Comandos';
|
@Input() buttonText: string = 'Ejecutar Comandos';
|
||||||
@Input() icon: string = 'terminal';
|
@Input() icon: string = 'terminal';
|
||||||
|
@Input() disabled: boolean = false;
|
||||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||||
loading: boolean = true;
|
loading: boolean = true;
|
||||||
|
|
||||||
|
|
|
@ -328,12 +328,6 @@ mat-tree mat-tree-node.disabled:hover {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clients-container {
|
|
||||||
width: 75%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-item {
|
.client-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -378,13 +372,6 @@ mat-tree mat-tree-node.disabled:hover {
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clients-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
||||||
gap: 4px;
|
|
||||||
/* Espaciado reducido entre cards */
|
|
||||||
}
|
|
||||||
|
|
||||||
.clients-list {
|
.clients-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -405,15 +392,6 @@ mat-tree mat-tree-node.disabled:hover {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clients-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(6, 1fr);
|
|
||||||
/* 6 columnas por fila */
|
|
||||||
gap: 16px;
|
|
||||||
/* Espaciado entre tarjetas */
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clients-list .list-item-content {
|
.clients-list .list-item-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -568,4 +546,24 @@ mat-button-toggle-group {
|
||||||
|
|
||||||
.mat-button-toggle-group .mat-button-toggle.mat-button-toggle-disabled {
|
.mat-button-toggle-group .mat-button-toggle.mat-button-toggle-disabled {
|
||||||
background-color: #c7c7c7;
|
background-color: #c7c7c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clients-container {
|
||||||
|
width: 75%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards-view {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clients-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
padding: 8px 20px 20px 20px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
|
@ -171,8 +171,8 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="view-type-container">
|
<div class="view-type-container">
|
||||||
<app-execute-command [clientData]="arrayClients" [buttonType]="'text'"
|
<app-execute-command [clientData]="selection.selected" [buttonType]="'text'"
|
||||||
[buttonText]="'Ejecutar comandos'"></app-execute-command>
|
[buttonText]="'Ejecutar comandos'" [disabled]="selection.selected.length === 0"></app-execute-command>
|
||||||
<mat-button-toggle-group name="viewType" aria-label="View Type" [hideSingleSelectionIndicator]="true"
|
<mat-button-toggle-group name="viewType" aria-label="View Type" [hideSingleSelectionIndicator]="true"
|
||||||
(change)="toggleView($event.value)">
|
(change)="toggleView($event.value)">
|
||||||
<mat-button-toggle value="list" [disabled]="currentView === 'list'">
|
<mat-button-toggle value="list" [disabled]="currentView === 'list'">
|
||||||
|
@ -190,55 +190,63 @@
|
||||||
<div *ngIf="!isLoadingClients">
|
<div *ngIf="!isLoadingClients">
|
||||||
<div *ngIf="hasClients; else noClientsTemplate">
|
<div *ngIf="hasClients; else noClientsTemplate">
|
||||||
<!-- Cards view -->
|
<!-- Cards view -->
|
||||||
<div class="clients-grid" *ngIf="currentView === 'card'">
|
<div *ngIf="currentView === 'card'" class="cards-view">
|
||||||
<div *ngFor="let client of arrayClients" class="client-item">
|
<mat-checkbox (change)="toggleAllCards()" [checked]="selection.hasValue() && isAllSelected()"
|
||||||
<div class="client-card">
|
[indeterminate]="selection.hasValue() && !isAllSelected()" style="margin-left: 1em; margin-top: 0.5rem;">
|
||||||
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(client)"
|
</mat-checkbox>
|
||||||
[checked]="selection.isSelected(client)" [disabled]="client.status === 'busy'">
|
<div class="clients-grid">
|
||||||
</mat-checkbox>
|
<div *ngFor="let client of arrayClients" class="client-item">
|
||||||
<img [src]="'assets/images/ordenador_' + client.status + '.png'" alt="Client Icon"
|
<div class="client-card">
|
||||||
class="client-image" />
|
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(client)"
|
||||||
|
[checked]="selection.isSelected(client)" [disabled]="client.status === 'busy'">
|
||||||
|
</mat-checkbox>
|
||||||
|
<img [src]="'assets/images/ordenador_' + client.status + '.png'" alt="Client Icon"
|
||||||
|
class="client-image" />
|
||||||
|
|
||||||
<div class="client-details">
|
<div class="client-details">
|
||||||
<span class="client-name">{{ client.name }}</span>
|
<span class="client-name">{{ client.name }}</span>
|
||||||
<span class="client-ip">{{ client.ip }}</span>
|
<span class="client-ip">{{ client.ip }}</span>
|
||||||
<span class="client-ip">{{ client.mac }}</span>
|
<span class="client-ip">{{ client.mac }}</span>
|
||||||
<div class="action-icons">
|
<div class="action-icons">
|
||||||
<button *ngIf="(!syncStatus || syncingClientId !== client.uuid)" mat-icon-button color="primary"
|
<button *ngIf="(!syncStatus || syncingClientId !== client.uuid)" mat-icon-button color="primary"
|
||||||
(click)="getStatus(client, selectedNode)">
|
(click)="getStatus(client, selectedNode)">
|
||||||
<mat-icon>sync</mat-icon>
|
<mat-icon>sync</mat-icon>
|
||||||
</button>
|
|
||||||
|
|
||||||
<button *ngIf="syncStatus && syncingClientId === client.uuid" mat-icon-button color="primary">
|
|
||||||
<mat-spinner diameter="24"></mat-spinner>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<app-execute-command [clientData]="[client]" [buttonType]="'icon'"
|
|
||||||
[icon]="'terminal'"></app-execute-command>
|
|
||||||
|
|
||||||
<button mat-icon-button [matMenuTriggerFor]="clientMenu" color="primary">
|
|
||||||
<mat-icon>more_vert</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<mat-menu #clientMenu="matMenu">
|
|
||||||
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
|
|
||||||
<mat-icon>edit</mat-icon>
|
|
||||||
<span>{{ 'edit' | translate }}</span>
|
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item (click)="onShowClientDetail($event, client)">
|
|
||||||
<mat-icon>visibility</mat-icon>
|
<button *ngIf="syncStatus && syncingClientId === client.uuid" mat-icon-button color="primary">
|
||||||
<span>{{ 'viewDetails' | translate }}</span>
|
<mat-spinner diameter="24"></mat-spinner>
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item (click)="onDeleteClick($event, client)">
|
|
||||||
<mat-icon>delete</mat-icon>
|
<app-execute-command [clientData]="[client]" [buttonType]="'icon'" [icon]="'terminal'"
|
||||||
<span>{{ 'delete' | translate }}</span>
|
[disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"></app-execute-command>
|
||||||
|
|
||||||
|
<button
|
||||||
|
[disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"
|
||||||
|
mat-icon-button [matMenuTriggerFor]="clientMenu" color="primary">
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</mat-menu>
|
|
||||||
|
<mat-menu #clientMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
<span>{{ 'edit' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onShowClientDetail($event, client)">
|
||||||
|
<mat-icon>visibility</mat-icon>
|
||||||
|
<span>{{ 'viewDetails' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onDeleteClick($event, client)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
<span>{{ 'delete' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- List view -->
|
<!-- List view -->
|
||||||
<div class="clients-table" *ngIf="currentView === 'list'">
|
<div class="clients-table" *ngIf="currentView === 'list'">
|
||||||
<table mat-table matSort [dataSource]="selectedClients" class="mat-elevation-z8">
|
<table mat-table matSort [dataSource]="selectedClients" class="mat-elevation-z8">
|
||||||
|
@ -314,11 +322,13 @@
|
||||||
<ng-container matColumnDef="actions">
|
<ng-container matColumnDef="actions">
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'actions' | translate }} </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'actions' | translate }} </th>
|
||||||
<td mat-cell *matCellDef="let client">
|
<td mat-cell *matCellDef="let client">
|
||||||
<button mat-icon-button [matMenuTriggerFor]="clientMenu" color="primary">
|
<button
|
||||||
|
[disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"
|
||||||
|
mat-icon-button [matMenuTriggerFor]="clientMenu" color="primary">
|
||||||
<mat-icon>more_vert</mat-icon>
|
<mat-icon>more_vert</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<app-execute-command [clientData]="[client]" [buttonType]="'icon'"
|
<app-execute-command [clientData]="[client]" [buttonType]="'icon'" [icon]="'terminal'"
|
||||||
[icon]="'terminal'"></app-execute-command>
|
[disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"></app-execute-command>
|
||||||
<mat-menu #clientMenu="matMenu">
|
<mat-menu #clientMenu="matMenu">
|
||||||
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
|
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
|
||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
|
|
|
@ -244,7 +244,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private refreshData(selectedNodeIdOrUuid?: string): void {
|
private refreshData(selectedNodeIdOrUuid?: string, selectedClientsBeforeEdit: string[] = []): void {
|
||||||
this.dataService.getOrganizationalUnits().subscribe({
|
this.dataService.getOrganizationalUnits().subscribe({
|
||||||
next: (data) => {
|
next: (data) => {
|
||||||
this.originalTreeData = data.map((unidad) => this.convertToTreeData(unidad));
|
this.originalTreeData = data.map((unidad) => this.convertToTreeData(unidad));
|
||||||
|
@ -255,13 +255,13 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
if (this.selectedNode) {
|
if (this.selectedNode) {
|
||||||
this.treeControl.collapseAll();
|
this.treeControl.collapseAll();
|
||||||
this.expandPathToNode(this.selectedNode);
|
this.expandPathToNode(this.selectedNode);
|
||||||
this.fetchClientsForNode(this.selectedNode);
|
this.fetchClientsForNode(this.selectedNode, selectedClientsBeforeEdit);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.treeControl.collapseAll();
|
this.treeControl.collapseAll();
|
||||||
if (this.treeDataSource.data.length > 0) {
|
if (this.treeDataSource.data.length > 0) {
|
||||||
this.selectedNode = this.treeDataSource.data[0];
|
this.selectedNode = this.treeDataSource.data[0];
|
||||||
this.fetchClientsForNode(this.selectedNode);
|
this.fetchClientsForNode(this.selectedNode, selectedClientsBeforeEdit);
|
||||||
} else {
|
} else {
|
||||||
this.selectedNode = null;
|
this.selectedNode = null;
|
||||||
this.selectedClients.data = [];
|
this.selectedClients.data = [];
|
||||||
|
@ -333,7 +333,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public fetchClientsForNode(node: TreeNode): void {
|
public fetchClientsForNode(node: TreeNode, selectedClientsBeforeEdit: string[] = []): void {
|
||||||
this.isLoadingClients = true;
|
this.isLoadingClients = true;
|
||||||
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}`).subscribe({
|
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}`).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
|
@ -342,6 +342,13 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
this.hasClients = node.hasClients ?? false;
|
this.hasClients = node.hasClients ?? false;
|
||||||
this.isLoadingClients = false;
|
this.isLoadingClients = false;
|
||||||
this.initialLoading = false;
|
this.initialLoading = false;
|
||||||
|
this.selection.clear();
|
||||||
|
selectedClientsBeforeEdit.forEach(uuid => {
|
||||||
|
const client = this.selectedClients.data.find(client => client.uuid === uuid);
|
||||||
|
if (client) {
|
||||||
|
this.selection.select(client);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
this.isLoadingClients = false;
|
this.isLoadingClients = false;
|
||||||
|
@ -483,12 +490,13 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
onEditClick(event: MouseEvent, type: string, uuid: string): void {
|
onEditClick(event: MouseEvent, type: string, uuid: string): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
const selectedClientsBeforeEdit = this.selection.selected.map(client => client.uuid);
|
||||||
const dialogRef = type !== NodeType.Client
|
const dialogRef = type !== NodeType.Client
|
||||||
? this.dialog.open(ManageOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
|
? this.dialog.open(ManageOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
|
||||||
: this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
: this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(() => {
|
dialogRef.afterClosed().subscribe(() => {
|
||||||
this.refreshData(this.selectedNode?.id);
|
this.refreshData(this.selectedNode?.id, selectedClientsBeforeEdit);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,12 +676,19 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
toggleAllRows() {
|
toggleAllRows() {
|
||||||
if (this.isAllSelected()) {
|
if (this.isAllSelected()) {
|
||||||
this.selection.clear();
|
this.selection.clear();
|
||||||
this.arrayClients = []
|
} else {
|
||||||
return;
|
this.selection.select(...this.selectedClients.data);
|
||||||
}
|
}
|
||||||
|
this.updateSelectedClients();
|
||||||
|
}
|
||||||
|
|
||||||
this.selection.select(...this.selectedClients.data);
|
toggleAllCards() {
|
||||||
this.arrayClients = [...this.selection.selected];
|
if (this.isAllSelected()) {
|
||||||
|
this.selection.clear();
|
||||||
|
} else {
|
||||||
|
this.selection.select(...this.selectedClients.data);
|
||||||
|
}
|
||||||
|
this.updateSelectedClients();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -684,7 +699,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
|
||||||
updateSelectedClients() {
|
updateSelectedClients() {
|
||||||
this.arrayClients = [...this.selection.selected];
|
this.arrayClients = this.selectedClients.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue