refs 2335. Groups 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
d526bb851a
commit
90d969ccd3
|
@ -31,7 +31,10 @@
|
|||
</div>
|
||||
<div class="table-header-container">
|
||||
<h2 class="title" i18n="@@adminImagesTitle">Discos/Particiones</h2>
|
||||
<mat-chip> {{ clientData.firmwareType }}</mat-chip>
|
||||
<mat-chip *ngIf="clientData.firmwareType" class="firmware-chip">
|
||||
<mat-icon>memory</mat-icon>
|
||||
{{ clientData.firmwareType }}
|
||||
</mat-chip>
|
||||
</div>
|
||||
|
||||
<div class="disk-container">
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,8 @@
|
|||
<app-modal-overlay
|
||||
[isVisible]="loading"
|
||||
message="Cargando...">
|
||||
</app-modal-overlay>
|
||||
|
||||
<div class="groups-container">
|
||||
<!-- HEADER -->
|
||||
<div class="header-container">
|
||||
|
@ -96,8 +101,6 @@
|
|||
</button>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-divider class="tree-mat-divider" style="padding-top: 10px;"></mat-divider>
|
||||
|
||||
<!-- Funcionalidad actualmente deshabilitada-->
|
||||
<!-- <mat-form-field appearance="outline">
|
||||
<mat-select (selectionChange)="loadSelectedFilter($event.value)" placeholder="Cargar filtro" disabled>
|
||||
|
@ -120,48 +123,107 @@
|
|||
|
||||
<!-- Tree -->
|
||||
<div class="tree-container" joyrideStep="treePanelStep" text="{{ 'treePanelStepText' | translate }}">
|
||||
<mat-tree [dataSource]="treeDataSource" [treeControl]="treeControl">
|
||||
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id}"
|
||||
<div class="tree-header">
|
||||
<h3 class="tree-title">
|
||||
<mat-icon>account_tree</mat-icon>
|
||||
{{ 'organizationalStructure' | translate }}
|
||||
</h3>
|
||||
<div class="tree-actions">
|
||||
<button mat-icon-button (click)="expandAll()" matTooltip="{{ 'expandAll' | translate }}">
|
||||
<mat-icon>unfold_more</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button (click)="collapseAll()" matTooltip="{{ 'collapseAll' | translate }}">
|
||||
<mat-icon>unfold_less</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-tree [dataSource]="treeDataSource" [treeControl]="treeControl" class="modern-tree">
|
||||
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id, 'tree-node': true}"
|
||||
*matTreeNodeDef="let node; when: hasChild" matTreeNodePadding (click)="onNodeClick($event, node)">
|
||||
<button mat-icon-button matTreeNodeToggle [disabled]="!node.expandable"
|
||||
[ngClass]="{'disabled-toggle': !node.expandable}">
|
||||
<mat-icon>{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}</mat-icon>
|
||||
</button>
|
||||
<mat-icon class="node-icon {{ node.type }}">
|
||||
{{
|
||||
node.type === 'organizational-unit' ? 'apartment'
|
||||
: node.type === 'classrooms-group' ? 'meeting_room'
|
||||
: node.type === 'classroom' ? 'school'
|
||||
: node.type === 'clients-group' ? 'lan'
|
||||
: node.type === 'client' ? 'computer'
|
||||
: 'group'
|
||||
}}
|
||||
</mat-icon>
|
||||
<span>{{ node.name }}</span>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuNode" (click)="onMenuClick($event, node)">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="node-content">
|
||||
<button mat-icon-button matTreeNodeToggle [disabled]="!node.expandable"
|
||||
[ngClass]="{'disabled-toggle': !node.expandable}" class="expand-button">
|
||||
<mat-icon class="expand-icon">{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}</mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="node-info">
|
||||
<div class="node-main">
|
||||
<mat-icon class="node-icon {{ node.type }}" [matTooltip]="getNodeTypeTooltip(node.type)">
|
||||
{{
|
||||
node.type === 'organizational-unit' ? 'business'
|
||||
: node.type === 'classrooms-group' ? 'meeting_room'
|
||||
: node.type === 'classroom' ? 'school'
|
||||
: node.type === 'clients-group' ? 'dns'
|
||||
: node.type === 'client' ? 'computer'
|
||||
: 'folder'
|
||||
}}
|
||||
</mat-icon>
|
||||
<span class="node-name" [matTooltip]="node.name">{{ node.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="node-details">
|
||||
<ng-container *ngIf="node.type === 'client'">
|
||||
<span class="node-ip">{{ node.ip }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="node.children && node.children.length > 0">
|
||||
<span class="node-count">{{ node.children.length }} {{ getNodeCountLabel(node.children.length) }}</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="node-actions">
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuNode" (click)="onMenuClick($event, node)"
|
||||
class="menu-button" matTooltip="{{ 'moreActions' | translate }}">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tree-node>
|
||||
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id}"
|
||||
|
||||
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id, 'tree-node': true}"
|
||||
*matTreeNodeDef="let node; when: isLeafNode" matTreeNodePadding (click)="onNodeClick($event, node)">
|
||||
<button mat-icon-button matTreeNodeToggle [disabled]="true" class="disabled-toggle"></button>
|
||||
<mat-icon style="color: green;">
|
||||
{{
|
||||
node.type === 'organizational-unit' ? 'apartment'
|
||||
: node.type === 'classrooms-group' ? 'meeting_room'
|
||||
: node.type === 'classroom' ? 'school'
|
||||
: node.type === 'clients-group' ? 'lan'
|
||||
: node.type === 'client' ? 'computer'
|
||||
: 'group'
|
||||
}}
|
||||
</mat-icon>
|
||||
<span>{{ node.name }}</span>
|
||||
<ng-container *ngIf="node.type === 'client'">
|
||||
<span> - IP: {{ node.ip }}</span>
|
||||
</ng-container>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuNode" (click)="onMenuClick($event, node)">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="node-content">
|
||||
<button mat-icon-button matTreeNodeToggle [disabled]="true" class="disabled-toggle expand-button">
|
||||
<mat-icon class="expand-icon">chevron_right</mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="node-info">
|
||||
<div class="node-main">
|
||||
<mat-icon class="node-icon {{ node.type }}" [ngClass]="{'client-status': node.type === 'client'}"
|
||||
[matTooltip]="getNodeTypeTooltip(node.type)">
|
||||
{{
|
||||
node.type === 'organizational-unit' ? 'business'
|
||||
: node.type === 'classrooms-group' ? 'meeting_room'
|
||||
: node.type === 'classroom' ? 'school'
|
||||
: node.type === 'clients-group' ? 'dns'
|
||||
: node.type === 'client' ? 'computer'
|
||||
: 'folder'
|
||||
}}
|
||||
</mat-icon>
|
||||
<span class="node-name" [matTooltip]="node.name">{{ node.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="node-details">
|
||||
<ng-container *ngIf="node.type === 'client'">
|
||||
<span class="node-ip">{{ node.ip }}</span>
|
||||
<span class="node-mac">{{ node.mac }}</span>
|
||||
<span class="node-status" [ngClass]="'status-' + (node.status || 'off')">
|
||||
{{ getStatusLabel(node.status) }}
|
||||
</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="node-actions">
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuNode" (click)="onMenuClick($event, node)"
|
||||
class="menu-button" matTooltip="{{ 'moreActions' | translate }}">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tree-node>
|
||||
</mat-tree>
|
||||
</div>
|
||||
|
@ -205,6 +267,10 @@
|
|||
<mat-icon>storage</mat-icon>
|
||||
<span>{{ 'partitions' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="openOUPendingTasks($event, selectedNode)">
|
||||
<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 || ''"
|
||||
|
@ -254,6 +320,45 @@
|
|||
text="{{ 'clientsViewStepText' | translate }}">
|
||||
<div *ngIf="hasClients; else noClientsTemplate">
|
||||
|
||||
<div class="stats-container" *ngIf="currentView === 'list'">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<mat-icon>computer</mat-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ totalStats.total }}</div>
|
||||
<div class="stat-label">{{ 'totalClients' | translate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon offline">
|
||||
<mat-icon>wifi_off</mat-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ getStatusCount('off') }}</div>
|
||||
<div class="stat-label">{{ 'offline' | translate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon online">
|
||||
<mat-icon>wifi</mat-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ getStatusCount('og-live') + getStatusCount('linux') + getStatusCount('windows') }}</div>
|
||||
<div class="stat-label">{{ 'online' | translate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon busy">
|
||||
<mat-icon>hourglass_empty</mat-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ getStatusCount('busy') }}</div>
|
||||
<div class="stat-label">{{ 'busy' | translate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cards view -->
|
||||
<div *ngIf="currentView === 'card'">
|
||||
<section class="cards-view">
|
||||
|
@ -262,7 +367,7 @@
|
|||
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||
</mat-checkbox>
|
||||
<div class="clients-grid">
|
||||
<div *ngFor="let client of arrayClients" class="client-item">
|
||||
<div *ngFor="let client of arrayClients" class="client-item" [ngClass]="'status-' + client.status">
|
||||
<div class="client-card">
|
||||
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(client)"
|
||||
[checked]="selection.isSelected(client)">
|
||||
|
@ -271,9 +376,9 @@
|
|||
alt="Client Icon" class="client-image" />
|
||||
|
||||
<div class="client-details">
|
||||
<span class="client-name">{{ client.name }}</span>
|
||||
<span class="client-name truncate-cell-wide" [matTooltip]="client.name">{{ client.name }}</span>
|
||||
<span class="client-ip">{{ client.ip }}</span>
|
||||
<span class="client-ip">{{ client.mac }}</span>
|
||||
<span class="client-mac">{{ client.mac }}</span>
|
||||
<div class="action-icons">
|
||||
|
||||
<app-execute-command [clientState]="client.status" [clientData]="[client]"
|
||||
|
@ -309,6 +414,10 @@
|
|||
<mat-icon>list_alt</mat-icon>
|
||||
<span>{{ 'procedimientosCliente' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="openClientPendingTasks($event, client)">
|
||||
<mat-icon>pending_actions</mat-icon>
|
||||
<span>{{ 'colaAcciones' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, client)" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>{{ 'delete' | translate }}</span>
|
||||
|
@ -327,10 +436,24 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List view -->
|
||||
<!-- List view mejorada -->
|
||||
<div *ngIf="currentView === 'list'" class="list-view">
|
||||
<div class="table-header">
|
||||
<div class="table-info">
|
||||
<span>{{ 'showingResults' | translate: { from: getPaginationFrom(), to: getPaginationTo(), total: getPaginationTotal() } }}</span>
|
||||
</div>
|
||||
<div class="table-actions">
|
||||
<button mat-icon-button (click)="refreshClientData()" matTooltip="{{ 'refresh' | translate }}">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button (click)="exportToCSV()" matTooltip="{{ 'exportCSV' | translate }}">
|
||||
<mat-icon>download</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="clients-table" tabindex="0">
|
||||
<table mat-table matSort [dataSource]="selectedClients">
|
||||
<table mat-table [dataSource]="selectedClients" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox (change)="$event ? toggleAllRows() : null"
|
||||
|
@ -345,7 +468,14 @@
|
|||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="status">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'status' | translate }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<div class="column-header">
|
||||
<span>{{ 'status' | translate }}</span>
|
||||
<button mat-icon-button (click)="sortColumn('status')" class="sort-button">
|
||||
<mat-icon>{{ getSortIcon('status') }}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}"
|
||||
matTooltipPosition="left" matTooltipShowDelay="500">
|
||||
<div class="client-status-container">
|
||||
|
@ -359,72 +489,101 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'name' | translate }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<div class="column-header">
|
||||
<span>{{ 'name' | translate }}</span>
|
||||
<button mat-icon-button (click)="sortColumn('name')" class="sort-button">
|
||||
<mat-icon>{{ getSortIcon('name') }}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}"
|
||||
matTooltipPosition="left" matTooltipShowDelay="500">
|
||||
<p>{{ client.name }}</p>
|
||||
<div class="client-cell">
|
||||
<span class="client-name truncate-cell-wide" [matTooltip]="client.name">{{ client.name }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="ip">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>IP </th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<div class="column-header">
|
||||
<span>IP</span>
|
||||
<button mat-icon-button (click)="sortColumn('ip')" class="sort-button">
|
||||
<mat-icon>{{ getSortIcon('ip') }}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}"
|
||||
matTooltipPosition="left" matTooltipShowDelay="500">
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<span>{{ client.ip }}</span>
|
||||
<span style="font-size: 0.75rem; color: gray;">{{ client.mac }}</span>
|
||||
<div class="client-cell">
|
||||
<span class="client-ip">{{ client.ip }}</span>
|
||||
<span class="client-mac">{{ client.mac }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="firmwareType">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'firmwareType' | translate }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<div class="column-header">
|
||||
<span>{{ 'firmwareType' | translate }}</span>
|
||||
<button mat-icon-button (click)="sortColumn('firmwareType')" class="sort-button">
|
||||
<mat-icon>{{ getSortIcon('firmwareType') }}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let client">
|
||||
<mat-chip *ngIf="client.firmwareType">
|
||||
<mat-chip *ngIf="client.firmwareType" class="firmware-chip">
|
||||
{{ client.firmwareType }}
|
||||
</mat-chip>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="oglive">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> OG Live </th>
|
||||
<th mat-header-cell *matHeaderCellDef> OG Live </th>
|
||||
<td mat-cell *matCellDef="let client">
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<span>{{ client.ogLive?.kernel }} </span>
|
||||
<span style="font-size: 0.75rem; color: gray;"> {{ client.ogLive?.date | date }}</span>
|
||||
<div class="oglive-cell">
|
||||
<span class="oglive-kernel">{{ client.ogLive?.kernel }}</span>
|
||||
<span class="oglive-date">{{ client.ogLive?.date | date }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="maintenace">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'maintenance' | translate }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'maintenance' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.maintenance }} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="subnet">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'subnet' | translate }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'subnet' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.subnet }} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="pxeTemplate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'pxeTemplate' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.pxeTemplate?.name }} </td>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'pxeTemplate' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client" class="truncate-cell-medium" [matTooltip]="client.pxeTemplate?.name"> {{ client.pxeTemplate?.name }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="parentName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'parent' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.parentName }} </td>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'parent' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client" class="truncate-cell-medium" [matTooltip]="client.parentName"> {{ client.parentName }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'actions' | translate }} </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'actions' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client">
|
||||
<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>
|
||||
<app-execute-command [clientState]="client.status" [clientData]="[client]" [buttonType]="'icon'"
|
||||
[icon]="'terminal'"
|
||||
[disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"
|
||||
[runScriptContext]="getRunScriptContext([client])">
|
||||
</app-execute-command>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
[disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"
|
||||
mat-icon-button [matMenuTriggerFor]="clientMenu" color="primary" matTooltip="{{ 'moreActions' | translate }}">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<app-execute-command [clientState]="client.status" [clientData]="[client]" [buttonType]="'icon'"
|
||||
[icon]="'terminal'"
|
||||
[disabled]="selection.selected.length > 1 || (selection.selected.length === 1 && !selection.isSelected(client))"
|
||||
[runScriptContext]="getRunScriptContext([client])" matTooltip="{{ 'executeCommand' | translate }}">
|
||||
</app-execute-command>
|
||||
<button mat-icon-button color="primary" (click)="onShowClientDetail($event, client)" matTooltip="{{ 'viewDetails' | translate }}">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<mat-menu #clientMenu="matMenu">
|
||||
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<mat-icon>edit</mat-icon>
|
||||
|
@ -442,6 +601,10 @@
|
|||
<mat-icon>list_alt</mat-icon>
|
||||
<span>{{ 'procedimientosCliente' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="openClientPendingTasks($event, client)">
|
||||
<mat-icon>pending_actions</mat-icon>
|
||||
<span>{{ 'colaAcciones' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, client)" *ngIf="auth.userCategory !== 'ou-minimal'">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>{{ 'delete' | translate }}</span>
|
||||
|
@ -449,9 +612,11 @@
|
|||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row style="background-color: #f3f3f3;"
|
||||
*matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" class="mat-row"
|
||||
[ngClass]="'status-' + row.status"
|
||||
[class.selected-row]="selectedClient?.uuid === row.uuid"
|
||||
(click)="selectClient(row)"></tr>
|
||||
</table>
|
||||
</section>
|
||||
<mat-paginator class="list-paginator" [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page"
|
||||
|
|
|
@ -27,6 +27,7 @@ import { TreeNode } from './model/model';
|
|||
import { LoadingComponent } from '../../shared/loading/loading.component';
|
||||
import { ExecuteCommandComponent } from '../commands/main-commands/execute-command/execute-command.component';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { ModalOverlayComponent } from '../../shared/modal-overlay/modal-overlay.component';
|
||||
|
||||
describe('GroupsComponent', () => {
|
||||
let component: GroupsComponent;
|
||||
|
@ -39,7 +40,7 @@ describe('GroupsComponent', () => {
|
|||
};
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [GroupsComponent, ExecuteCommandComponent, LoadingComponent],
|
||||
declarations: [GroupsComponent, ExecuteCommandComponent, LoadingComponent, ModalOverlayComponent],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
ToastrModule.forRoot(),
|
||||
|
|
|
@ -15,7 +15,6 @@ import { ShowOrganizationalUnitComponent } from './shared/organizational-units/s
|
|||
import { LegendComponent } from './shared/legend/legend.component';
|
||||
import { DeleteModalComponent } from '../../shared/delete_modal/delete-modal/delete-modal.component';
|
||||
import { ClassroomViewDialogComponent } from './shared/classroom-view/classroom-view-modal';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { PageEvent } from '@angular/material/paginator';
|
||||
import { CreateMultipleClientComponent } from "./shared/clients/create-multiple-client/create-multiple-client.component";
|
||||
|
@ -31,6 +30,7 @@ import { PartitionTypeOrganizatorComponent } from './shared/partition-type-organ
|
|||
import { ClientTaskLogsComponent } from '../task-logs/client-task-logs/client-task-logs.component';
|
||||
import {ChangeParentComponent} from "./shared/change-parent/change-parent.component";
|
||||
import { AuthService } from '@services/auth.service';
|
||||
import { ClientPendingTasksComponent } from '../task-logs/client-pending-tasks/client-pending-tasks.component';
|
||||
|
||||
enum NodeType {
|
||||
OrganizationalUnit = 'organizational-unit',
|
||||
|
@ -79,6 +79,29 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
arrayClients: any[] = [];
|
||||
filters: { [key: string]: string } = {};
|
||||
private clientFilterSubject = new Subject<string>();
|
||||
loading = false;
|
||||
|
||||
// Nuevas propiedades para funcionalidades mejoradas
|
||||
selectedClient: any = null;
|
||||
sortBy: string = 'name';
|
||||
sortDirection: 'asc' | 'desc' = 'asc';
|
||||
currentSortColumn: string = 'name';
|
||||
|
||||
// Estadísticas totales
|
||||
totalStats: {
|
||||
total: number;
|
||||
off: number;
|
||||
online: number;
|
||||
busy: number;
|
||||
} = {
|
||||
total: 0,
|
||||
off: 0,
|
||||
online: 0,
|
||||
busy: 0
|
||||
};
|
||||
|
||||
// Tipos de firmware disponibles
|
||||
firmwareTypes: string[] = [];
|
||||
|
||||
protected status = [
|
||||
{ value: 'off', name: 'Apagado' },
|
||||
|
@ -95,16 +118,6 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
|
||||
displayedColumns: string[] = ['select', 'status', 'ip', 'firmwareType', 'name', 'oglive', 'subnet', 'pxeTemplate', 'actions'];
|
||||
|
||||
private _sort!: MatSort;
|
||||
|
||||
@ViewChild(MatSort)
|
||||
set matSort(ms: MatSort) {
|
||||
this._sort = ms;
|
||||
if (this.selectedClients) {
|
||||
this.selectedClients.sort = this._sort;
|
||||
}
|
||||
}
|
||||
|
||||
private subscriptions: Subscription = new Subscription();
|
||||
|
||||
constructor(
|
||||
|
@ -404,8 +417,14 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
public fetchClientsForNode(node: any, selectedClientsBeforeEdit: string[] = []): void {
|
||||
const params = new HttpParams({ fromObject: this.filters });
|
||||
|
||||
// Agregar parámetros de ordenamiento al backend
|
||||
let backendParams = { ...this.filters };
|
||||
if (this.sortBy) {
|
||||
backendParams['order[' + this.sortBy + ']'] = this.sortDirection;
|
||||
}
|
||||
|
||||
this.isLoadingClients = true;
|
||||
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}&page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params }).subscribe({
|
||||
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}&page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: backendParams }).subscribe({
|
||||
next: (response: any) => {
|
||||
this.selectedClients.data = response['hydra:member'];
|
||||
if (this.selectedNode) {
|
||||
|
@ -423,6 +442,9 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
this.selection.select(client);
|
||||
}
|
||||
});
|
||||
|
||||
// Calcular estadísticas después de cargar los clientes
|
||||
this.calculateLocalStats();
|
||||
},
|
||||
error: () => {
|
||||
this.isLoadingClients = false;
|
||||
|
@ -438,25 +460,35 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
addOU(event: MouseEvent, parent: TreeNode | null = null): void {
|
||||
this.loading = true;
|
||||
event.stopPropagation();
|
||||
const dialogRef = this.dialog.open(ManageOrganizationalUnitComponent, {
|
||||
data: { parent },
|
||||
width: '900px',
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop',
|
||||
});
|
||||
dialogRef.afterClosed().subscribe((newUnit) => {
|
||||
if (newUnit) {
|
||||
this.refreshData(newUnit.uuid);
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
addClient(event: MouseEvent, organizationalUnit: TreeNode | null = null): void {
|
||||
this.loading = true;
|
||||
event.stopPropagation();
|
||||
const targetNode = organizationalUnit || this.selectedNode;
|
||||
const dialogRef = this.dialog.open(ManageClientComponent, {
|
||||
data: { organizationalUnit: targetNode },
|
||||
width: '900px',
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
|
@ -469,17 +501,22 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
this.refreshData(parentNode.uuid);
|
||||
}
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
addMultipleClients(event: MouseEvent, organizationalUnit: TreeNode | null = null): void {
|
||||
this.loading = true;
|
||||
event.stopPropagation();
|
||||
const targetNode = organizationalUnit || this.selectedNode;
|
||||
|
||||
const dialogRef = this.dialog.open(CreateMultipleClientComponent, {
|
||||
data: { organizationalUnit: targetNode },
|
||||
width: '900px',
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop',
|
||||
});
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
if (result?.success) {
|
||||
|
@ -494,29 +531,33 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
console.error('No se encontró el nodo padre después de la creación masiva.');
|
||||
}
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onEditNode(event: MouseEvent, node: TreeNode | null): void {
|
||||
event.stopPropagation();
|
||||
this.loading = true;
|
||||
const uuid = node ? this.extractUuid(node['@id']) : null;
|
||||
if (!uuid) return;
|
||||
|
||||
const dialogRef = node?.type !== NodeType.Client
|
||||
? this.dialog.open(ManageOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
|
||||
: this.dialog.open(ManageClientComponent, { data: { uuid }, width: '900px' });
|
||||
? this.dialog.open(ManageOrganizationalUnitComponent, { data: { uuid }, width: '900px', disableClose: true, hasBackdrop: true, backdropClass: 'non-clickable-backdrop' })
|
||||
: this.dialog.open(ManageClientComponent, { data: { uuid }, width: '900px', disableClose: true, hasBackdrop: true, backdropClass: 'non-clickable-backdrop' });
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
if (result?.success) {
|
||||
this.refreshData(node?.id);
|
||||
}
|
||||
this.menuTriggers.forEach(trigger => trigger.closeMenu());
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onDeleteClick(event: MouseEvent, entity: TreeNode | Client | null): void {
|
||||
event.stopPropagation();
|
||||
this.loading = true;
|
||||
if (!entity) return;
|
||||
|
||||
const uuid = entity['@id'] ? this.extractUuid(entity['@id']) : null;
|
||||
|
@ -533,6 +574,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
if (result === true) {
|
||||
this.deleteEntityorClient(uuid, type);
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -570,16 +612,18 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
|
||||
onEditClick(event: MouseEvent, type: string, uuid: string): void {
|
||||
event.stopPropagation();
|
||||
this.loading = true;
|
||||
const selectedClientsBeforeEdit = this.selection.selected.map(client => client.uuid);
|
||||
const dialogRef = type !== NodeType.Client
|
||||
? this.dialog.open(ManageOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
|
||||
: this.dialog.open(ManageClientComponent, { data: { uuid }, width: '900px' });
|
||||
? this.dialog.open(ManageOrganizationalUnitComponent, { data: { uuid }, width: '900px', disableClose: true, hasBackdrop: true, backdropClass: 'non-clickable-backdrop' })
|
||||
: this.dialog.open(ManageClientComponent, { data: { uuid }, width: '900px', disableClose: true, hasBackdrop: true, backdropClass: 'non-clickable-backdrop' });
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
if (result?.success) {
|
||||
this.refreshData(this.selectedNode?.id, selectedClientsBeforeEdit);
|
||||
}
|
||||
this.menuTriggers.forEach(trigger => trigger.closeMenu());
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -592,6 +636,9 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
this.dialog.open(ClassroomViewDialogComponent, {
|
||||
width: '90vw',
|
||||
data: { clients: response['hydra:member'] },
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop',
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
|
@ -603,35 +650,46 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
|
||||
|
||||
executeCommand(command: Command, selectedNode: TreeNode | null): void {
|
||||
|
||||
this.loading = true;
|
||||
if (!selectedNode) {
|
||||
this.toastr.error('No hay un nodo seleccionado.');
|
||||
return;
|
||||
} else {
|
||||
this.toastr.success(`Ejecutando comando: ${command.name} en ${selectedNode.name}`);
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
|
||||
onShowClientDetail(event: MouseEvent, client: Client): void {
|
||||
event.stopPropagation();
|
||||
this.dialog.open(ClientDetailsComponent, {
|
||||
this.loading = true;
|
||||
const dialogRef = this.dialog.open(ClientDetailsComponent, {
|
||||
width: '70vw',
|
||||
height: '90vh',
|
||||
data: { clientData: client },
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop',
|
||||
})
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onShowDetailsClick(event: MouseEvent, data: TreeNode | null): void {
|
||||
event.stopPropagation();
|
||||
this.loading = true;
|
||||
if (data && data.type !== NodeType.Client) {
|
||||
this.dialog.open(ShowOrganizationalUnitComponent, { data: { data }, width: '800px' });
|
||||
this.dialog.open(ShowOrganizationalUnitComponent, { data: { data }, width: '800px', disableClose: true, hasBackdrop: true, backdropClass: 'non-clickable-backdrop' });
|
||||
} else {
|
||||
if (data) {
|
||||
this.router.navigate(['clients', this.extractUuid(data['@id'])], { state: { clientData: data } });
|
||||
}
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -873,6 +931,9 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
const dialogRef = this.dialog.open(ChangeParentComponent, {
|
||||
data: { clients: this.selection.selected },
|
||||
width: '700px',
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
|
@ -883,11 +944,269 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
openClientTaskLogs(event: MouseEvent, client: Client): void {
|
||||
this.loading = true;
|
||||
event.stopPropagation();
|
||||
|
||||
this.dialog.open(ClientTaskLogsComponent, {
|
||||
const dialogRef = this.dialog.open(ClientTaskLogsComponent, {
|
||||
width: '1200px',
|
||||
data: { client }
|
||||
data: { client },
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop',
|
||||
})
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
openClientPendingTasks(event: MouseEvent, client: Client): void {
|
||||
this.loading = true;
|
||||
event.stopPropagation();
|
||||
|
||||
const dialogRef = this.dialog.open(ClientPendingTasksComponent, {
|
||||
width: '1200px',
|
||||
data: { client },
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop',
|
||||
})
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
openOUPendingTasks(event: MouseEvent, node: any): void {
|
||||
event.stopPropagation();
|
||||
this.loading = true;
|
||||
|
||||
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}&page=1&itemsPerPage=10000`).subscribe({
|
||||
next: (response) => {
|
||||
const allClients = response['hydra:member'] || [];
|
||||
|
||||
if (allClients.length === 0) {
|
||||
this.toastr.warning('Esta unidad organizativa no tiene clientes');
|
||||
return;
|
||||
}
|
||||
|
||||
const ouClientData = {
|
||||
name: node.name,
|
||||
id: node.id,
|
||||
uuid: node.uuid,
|
||||
type: 'organizational-unit',
|
||||
clients: allClients
|
||||
};
|
||||
|
||||
const dialogRef = this.dialog.open(ClientPendingTasksComponent, {
|
||||
width: '1200px',
|
||||
data: { client: ouClientData, isOrganizationalUnit: true },
|
||||
disableClose: true,
|
||||
hasBackdrop: true,
|
||||
backdropClass: 'non-clickable-backdrop',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al obtener los clientes de la unidad organizativa:', error);
|
||||
this.toastr.error('Error al cargar los clientes de la unidad organizativa');
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Métodos para paginación
|
||||
getPaginationFrom(): number {
|
||||
return (this.page * this.itemsPerPage) + 1;
|
||||
}
|
||||
|
||||
getPaginationTo(): number {
|
||||
return Math.min((this.page + 1) * this.itemsPerPage, this.length);
|
||||
}
|
||||
|
||||
getPaginationTotal(): number {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
refreshClientData(): void {
|
||||
this.fetchClientsForNode(this.selectedNode);
|
||||
this.toastr.success('Datos actualizados', 'Éxito');
|
||||
}
|
||||
|
||||
exportToCSV(): void {
|
||||
const headers = ['Nombre', 'IP', 'MAC', 'Estado', 'Firmware', 'Subnet', 'Parent'];
|
||||
const csvData = this.arrayClients.map(client => [
|
||||
client.name,
|
||||
client.ip || '',
|
||||
client.mac || '',
|
||||
client.status || '',
|
||||
client.firmwareType || '',
|
||||
client.subnet || '',
|
||||
client.parentName || ''
|
||||
]);
|
||||
|
||||
const csvContent = [headers, ...csvData]
|
||||
.map(row => row.map(cell => `"${cell}"`).join(','))
|
||||
.join('\n');
|
||||
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `clients_${new Date().toISOString().split('T')[0]}.csv`);
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
this.toastr.success('Archivo CSV exportado correctamente', 'Éxito');
|
||||
}
|
||||
|
||||
private calculateLocalStats(): void {
|
||||
const clients = this.arrayClients;
|
||||
this.totalStats = {
|
||||
total: clients.length,
|
||||
off: clients.filter(client => client.status === 'off').length,
|
||||
online: clients.filter(client => ['og-live', 'linux', 'windows', 'linux-session', 'windows-session'].includes(client.status)).length,
|
||||
busy: clients.filter(client => client.status === 'busy').length
|
||||
};
|
||||
|
||||
// Actualizar tipos de firmware disponibles
|
||||
this.firmwareTypes = [...new Set(clients.map(client => client.firmwareType).filter(Boolean))];
|
||||
}
|
||||
|
||||
// Métodos para funcionalidades mejoradas
|
||||
|
||||
selectClient(client: any): void {
|
||||
this.selectedClient = client;
|
||||
}
|
||||
|
||||
sortColumn(columnDef: string): void {
|
||||
if (this.currentSortColumn === columnDef) {
|
||||
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
this.currentSortColumn = columnDef;
|
||||
this.sortDirection = 'asc';
|
||||
}
|
||||
this.sortBy = columnDef;
|
||||
this.onSortChange();
|
||||
}
|
||||
|
||||
getSortIcon(columnDef: string): string {
|
||||
if (this.currentSortColumn !== columnDef) {
|
||||
return 'unfold_more';
|
||||
}
|
||||
return this.sortDirection === 'asc' ? 'expand_less' : 'expand_more';
|
||||
}
|
||||
|
||||
onSortChange(): void {
|
||||
// Hacer nueva llamada al backend con el ordenamiento actualizado
|
||||
this.fetchClientsForNode(this.selectedNode);
|
||||
}
|
||||
|
||||
getStatusCount(status: string): number {
|
||||
switch(status) {
|
||||
case 'off':
|
||||
return this.totalStats.off;
|
||||
case 'online':
|
||||
return this.totalStats.online;
|
||||
case 'busy':
|
||||
return this.totalStats.busy;
|
||||
default:
|
||||
return this.arrayClients.filter(client => client.status === status).length;
|
||||
}
|
||||
}
|
||||
|
||||
// Métodos para el árbol mejorado
|
||||
|
||||
expandAll(): void {
|
||||
this.treeControl.expandAll();
|
||||
}
|
||||
|
||||
collapseAll(): void {
|
||||
this.treeControl.collapseAll();
|
||||
}
|
||||
|
||||
getNodeTypeTooltip(nodeType: string): string {
|
||||
switch (nodeType) {
|
||||
case 'organizational-unit':
|
||||
return 'Unidad Organizacional - Estructura principal de la organización';
|
||||
case 'classrooms-group':
|
||||
return 'Grupo de Aulas - Conjunto de aulas relacionadas';
|
||||
case 'classroom':
|
||||
return 'Aula - Espacio físico con equipos informáticos';
|
||||
case 'clients-group':
|
||||
return 'Grupo de Equipos - Conjunto de equipos informáticos';
|
||||
case 'client':
|
||||
return 'Equipo Informático - Computadora o dispositivo individual';
|
||||
case 'group':
|
||||
return 'Grupo - Agrupación lógica de elementos';
|
||||
default:
|
||||
return 'Elemento del árbol organizacional';
|
||||
}
|
||||
}
|
||||
|
||||
getNodeCountLabel(count: number): string {
|
||||
if (count === 1) return 'elemento';
|
||||
return 'elementos';
|
||||
}
|
||||
|
||||
getStatusLabel(status: string): string {
|
||||
const statusLabels: { [key: string]: string } = {
|
||||
'off': 'Apagado',
|
||||
'og-live': 'OG Live',
|
||||
'linux': 'Linux',
|
||||
'linux-session': 'Linux Session',
|
||||
'windows': 'Windows',
|
||||
'windows-session': 'Windows Session',
|
||||
'busy': 'Ocupado',
|
||||
'mac': 'Mac',
|
||||
'disconnected': 'Desconectado',
|
||||
'initializing': 'Inicializando'
|
||||
};
|
||||
return statusLabels[status] || status;
|
||||
}
|
||||
|
||||
// Funciones para el dashboard de estadísticas
|
||||
getTotalOrganizationalUnits(): number {
|
||||
let total = 0;
|
||||
const countOrganizationalUnits = (nodes: TreeNode[]) => {
|
||||
nodes.forEach(node => {
|
||||
if (node.type === 'organizational-unit') {
|
||||
total += 1;
|
||||
}
|
||||
if (node.children) {
|
||||
countOrganizationalUnits(node.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
countOrganizationalUnits(this.originalTreeData);
|
||||
return total;
|
||||
}
|
||||
|
||||
getTotalClassrooms(): number {
|
||||
let total = 0;
|
||||
const countClassrooms = (nodes: TreeNode[]) => {
|
||||
nodes.forEach(node => {
|
||||
if (node.type === 'classroom') {
|
||||
total += 1;
|
||||
}
|
||||
if (node.children) {
|
||||
countClassrooms(node.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
countClassrooms(this.originalTreeData);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ export class ClientViewComponent {
|
|||
{ property: 'Fecha de creación', value: this.data.client.createdAt },
|
||||
{ property: 'NTP', value: this.data.client.organizationalUnit?.networkSettings?.ntp || '' },
|
||||
{ property: 'Modo p2p', value: this.data.client.organizationalUnit?.networkSettings?.p2pMode || '' },
|
||||
{ property: 'Tiempo p2p', value: this.data.client.organizationalUnit?.networkSettings?.p2pTime || '' },
|
||||
...(this.data.client.organizationalUnit?.networkSettings?.p2pMode === 'seeder' ? [{ property: 'Tiempo p2p (minutos)', value: this.data.client.organizationalUnit?.networkSettings?.p2pTime || '' }] : []),
|
||||
{ property: 'IP multicast', value: this.data.client.organizationalUnit?.networkSettings?.mcastIp || '' },
|
||||
{ property: 'Modo multicast', value: this.data.client.organizationalUnit?.networkSettings?.mcastMode || '' },
|
||||
{ property: 'Puerto multicast', value: this.data.client.organizationalUnit?.networkSettings?.mcastPort || '' },
|
||||
|
@ -51,7 +51,7 @@ export class ClientViewComponent {
|
|||
{ property: 'Router', value: this.data.client.organizationalUnit?.networkSettings?.router || '' },
|
||||
{ property: 'NTP', value: this.data.client.organizationalUnit?.networkSettings?.ntp || '' },
|
||||
{ property: 'Modo p2p', value: this.data.client.organizationalUnit?.networkSettings?.p2pMode || '' },
|
||||
{ property: 'Tiempo p2p', value: this.data.client.organizationalUnit?.networkSettings?.p2pTime || '' },
|
||||
...(this.data.client.organizationalUnit?.networkSettings?.p2pMode === 'seeder' ? [{ property: 'Tiempo p2p (minutos)', value: this.data.client.organizationalUnit?.networkSettings?.p2pTime || '' }] : []),
|
||||
{ property: 'IP multicast', value: this.data.client.organizationalUnit?.networkSettings?.mcastIp || '' },
|
||||
{ property: 'Modo multicast', value: this.data.client.organizationalUnit?.networkSettings?.mcastMode || '' },
|
||||
{ property: 'Puerto multicast', value: this.data.client.organizationalUnit?.networkSettings?.mcastPort || '' },
|
||||
|
|
|
@ -134,8 +134,8 @@
|
|||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'p2pTimeLabel' | translate }}</mat-label>
|
||||
<mat-form-field class="form-field" *ngIf="networkSettingsFormGroup.get('p2pMode')?.value === 'seeder'">
|
||||
<mat-label>{{ 'p2pTimeLabel' | translate }} (minutos)</mat-label>
|
||||
<input matInput formControlName="p2pTime" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
|
|
|
@ -271,6 +271,13 @@ export class ManageOrganizationalUnitComponent implements OnInit {
|
|||
onSubmit() {
|
||||
if (this.generalFormGroup.valid && this.additionalInfoFormGroup.valid && this.networkSettingsFormGroup.valid) {
|
||||
const parentValue = this.generalFormGroup.value.parent;
|
||||
|
||||
// Preparar networkSettings con lógica condicional para p2pTime
|
||||
const networkSettings = { ...this.networkSettingsFormGroup.value };
|
||||
if (networkSettings.p2pMode !== 'seeder') {
|
||||
networkSettings.p2pTime = null;
|
||||
}
|
||||
|
||||
const formData = {
|
||||
name: this.generalFormGroup.value.name,
|
||||
excludeParentChanges: this.generalFormGroup.value.excludeParentChanges,
|
||||
|
@ -279,7 +286,7 @@ export class ManageOrganizationalUnitComponent implements OnInit {
|
|||
comments: this.additionalInfoFormGroup.value.comments,
|
||||
remoteCalendar: this.generalFormGroup.value.remoteCalendar,
|
||||
type: this.generalFormGroup.value.type,
|
||||
networkSettings: this.networkSettingsFormGroup.value,
|
||||
networkSettings: networkSettings,
|
||||
location: this.classroomInfoFormGroup.value.location,
|
||||
projector: this.classroomInfoFormGroup.value.projector,
|
||||
board: this.classroomInfoFormGroup.value.board,
|
||||
|
|
Loading…
Reference in New Issue