699 lines
36 KiB
HTML
699 lines
36 KiB
HTML
<app-modal-overlay
|
|
[isVisible]="loading"
|
|
message="Cargando...">
|
|
</app-modal-overlay>
|
|
|
|
<div class="groups-container">
|
|
<!-- HEADER -->
|
|
<div class="header-container">
|
|
<button mat-icon-button color="primary" (click)="initTour()">
|
|
<mat-icon>help</mat-icon>
|
|
</button>
|
|
<div class="header-container-title">
|
|
<h2 joyrideStep="groupsTitleStep" text="{{ 'groupsTitleStepText' | translate }}">
|
|
{{ 'adminGroupsTitle' | translate }}
|
|
</h2>
|
|
</div>
|
|
<div class="groups-button-row">
|
|
<div joyrideStep="addStep" text="{{ 'groupsAddStepText' | translate }}" style="display: flex; gap: 8px;">
|
|
@if (auth.userCategory !== 'ou-minimal') {
|
|
<button class="action-button" (click)="addOU($event)"
|
|
matTooltip="{{ 'newOrganizationalUnitTooltip' | translate }}" matTooltipShowDelay="1000">
|
|
{{ 'newOrganizationalUnitButton' | translate }}
|
|
</button>
|
|
}
|
|
@if (auth.userCategory !== 'ou-minimal') {
|
|
<button class="action-button" [matMenuTriggerFor]="menuClients"
|
|
[disabled]="!hasOrganizationalUnits()"
|
|
matTooltip="{{ hasOrganizationalUnits() ? ('newClientButtonTooltip' | translate) : ('noOrganizationalUnitsTooltip' | translate) }}"
|
|
matTooltipShowDelay="1000">{{
|
|
'newClientButton' | translate }}</button>
|
|
}
|
|
<mat-menu #menuClients="matMenu">
|
|
<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>
|
|
<button class="ordinary-button" (click)="openBottomSheet()" joyrideStep="keyStep"
|
|
text="{{ 'keyStepText' | translate }}" matTooltipShowDelay="1000">
|
|
{{ 'Legend' | translate }}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Menú desplegable para pantallas pequeñas -->
|
|
@if (isSmallScreen) {
|
|
<div class="groups-menu">
|
|
<button mat-icon-button [matMenuTriggerFor]="smallScreenMenu" [matTooltip]="'labelOptions' | translate" matTooltipShowDelay="1000">
|
|
<mat-icon>menu</mat-icon>
|
|
</button>
|
|
<mat-menu #smallScreenMenu="matMenu">
|
|
<button mat-menu-item (click)="addOU($event)">
|
|
{{ 'newOrganizationalUnitButton' | translate }}
|
|
</button>
|
|
<button mat-menu-item (click)="addClient($event)" [disabled]="!hasOrganizationalUnits()">
|
|
{{ 'newSingleClientButton' | translate }}
|
|
</button>
|
|
<button mat-menu-item (click)="addMultipleClients($event)" [disabled]="!hasOrganizationalUnits()">
|
|
{{ 'newMultipleClientButton' | translate }}
|
|
</button>
|
|
<button mat-menu-item (click)="openBottomSheet()">
|
|
{{ 'Legend' | translate }}
|
|
</button>
|
|
</mat-menu>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@if (initialLoading) {
|
|
<div>
|
|
<app-loading [isLoading]="initialLoading"></app-loading>
|
|
</div>
|
|
} @else {
|
|
<div class="main-container">
|
|
<!-- FILTERS AND TREE VIEW -->
|
|
<div class="filters-and-tree-container">
|
|
<!-- Filters -->
|
|
<div class="filters-panel" joyrideStep="filtersPanelStep" text="{{ 'filtersPanelStepText' | translate }}">
|
|
<div class="filters-container">
|
|
<mat-form-field class="filter-form-field" appearance="outline">
|
|
<mat-label>{{ 'searchTree' | translate }}</mat-label>
|
|
<input matInput #treeSearchInput (input)="onTreeFilterInput($event)"
|
|
[placeholder]="'placeholderOrgUnitTypes' | translate" />
|
|
@if (treeSearchInput.value) {
|
|
<button mat-icon-button matSuffix [attr.aria-label]="'clearTreeSearch' | translate"
|
|
(click)="clearTreeSearch(treeSearchInput)">
|
|
<mat-icon>close</mat-icon>
|
|
</button>
|
|
}
|
|
</mat-form-field>
|
|
<mat-form-field class="filter-form-field" appearance="outline">
|
|
<mat-label>{{ 'searchClient' | translate }}</mat-label>
|
|
<input matInput #clientSearchInput (input)="onClientFilterInput($event)"
|
|
[placeholder]="'searchNameIpStatusMac' | translate" />
|
|
@if (clientSearchInput.value) {
|
|
<button mat-icon-button matSuffix [attr.aria-label]="'clearClientSearch' | translate"
|
|
(click)="clearClientSearch(clientSearchInput)">
|
|
<mat-icon>close</mat-icon>
|
|
</button>
|
|
}
|
|
</mat-form-field>
|
|
<mat-form-field class="form-field search-select" appearance="outline">
|
|
<mat-select placeholder="{{ 'searchState' | translate }}" #clientSearchStatusInput
|
|
(selectionChange)="onClientFilterStatusInput($event.value)">
|
|
@for (option of status; track option) {
|
|
<mat-option [value]="option.value">
|
|
{{ option.name }}
|
|
</mat-option>
|
|
}
|
|
</mat-select>
|
|
@if (clientSearchStatusInput.value) {
|
|
<button mat-icon-button matSuffix [attr.aria-label]="'clearTreeSearch' | translate"
|
|
(click)="clearStatusFilter($event, clientSearchStatusInput)">
|
|
<mat-icon>close</mat-icon>
|
|
</button>
|
|
}
|
|
</mat-form-field>
|
|
<div class="direct-clients-toggle">
|
|
<mat-slide-toggle
|
|
[(ngModel)]="showOnlyDirectClients"
|
|
(change)="onDirectClientsToggleChange()"
|
|
matTooltip="{{ 'directClientsToggleTooltip' | translate }}"
|
|
matTooltipShowDelay="1000">
|
|
{{ 'directClientsToggle' | translate }}
|
|
</mat-slide-toggle>
|
|
</div>
|
|
<!-- Funcionalidad actualmente deshabilitada-->
|
|
<!-- <mat-form-field appearance="outline">
|
|
<mat-select (selectionChange)="loadSelectedFilter($event.value)" [placeholder]="'buttonLoadFilter' | translate" disabled>
|
|
<mat-option *ngFor="let savedFilter of savedFilterNames" [value]="savedFilter">
|
|
{{ savedFilter[0] }}
|
|
</mat-option>
|
|
</mat-select>
|
|
</mat-form-field>
|
|
<mat-form-field appearance="outline">
|
|
<mat-label>{{ 'filterByType' | translate }}</mat-label>
|
|
<mat-select [(value)]="selectedTreeFilter" (selectionChange)="filterTree(searchTerm, $event.value)" disabled>
|
|
<mat-option [value]=""> {{ 'all' | translate }} </mat-option>
|
|
<mat-option value="classrooms-group">{{ 'classroomsGroup' | translate }}</mat-option>
|
|
<mat-option value="classroom">{{ 'classrooms' | translate }}</mat-option>
|
|
<mat-option value="group">{{ 'computerGroups' | translate }}</mat-option>
|
|
</mat-select>
|
|
</mat-form-field> -->
|
|
</div>
|
|
</div>
|
|
<!-- Tree -->
|
|
<div class="tree-container" joyrideStep="treePanelStep" text="{{ 'treePanelStepText' | translate }}">
|
|
<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)">
|
|
<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">
|
|
@if (node.type === 'client') {
|
|
<span class="node-ip">{{ node.ip }}</span>
|
|
}
|
|
@if (node.children && node.children.length > 0) {
|
|
<span class="node-count">{{ node.children.length }} {{ getNodeCountLabel(node.children.length) }}</span>
|
|
}
|
|
</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, 'tree-node': true}"
|
|
*matTreeNodeDef="let node; when: isLeafNode" matTreeNodePadding (click)="onNodeClick($event, node)">
|
|
<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">
|
|
@if (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>
|
|
}
|
|
</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>
|
|
<!-- Tree node actions -->
|
|
<mat-menu restoreFocus=false #commandMenu="matMenu">
|
|
@for (command of commands; track command) {
|
|
<button mat-menu-item (click)="executeCommand(command, selectedNode)">
|
|
<span>{{ command.name }}</span>
|
|
</button>
|
|
}
|
|
</mat-menu>
|
|
<mat-menu #menuNode="matMenu">
|
|
<button mat-menu-item (click)="onShowDetailsClick($event, selectedNode)">
|
|
<mat-icon matTooltip="{{ 'viewUnitTooltip' | translate }}" matTooltipHideDelay="0">visibility</mat-icon>
|
|
<span>{{ 'viewUnitMenu' | translate }}</span>
|
|
</button>
|
|
@if (selectedNode?.type === 'classroom') {
|
|
<button mat-menu-item (click)="onRoomMap(selectedNode)">
|
|
<mat-icon>map</mat-icon>
|
|
<span>{{ 'roomMap' | translate }}</span>
|
|
</button>
|
|
}
|
|
@if (auth.userCategory !== 'ou-minimal') {
|
|
<button mat-menu-item (click)="addClient($event, selectedNode)" [disabled]="!hasOrganizationalUnits()">
|
|
<mat-icon>add</mat-icon>
|
|
<span>{{ 'newSingleClientButton' | translate }}</span>
|
|
</button>
|
|
}
|
|
@if (auth.userCategory !== 'ou-minimal') {
|
|
<button mat-menu-item (click)="addMultipleClients($event, selectedNode)" [disabled]="!hasOrganizationalUnits()">
|
|
<mat-icon>playlist_add</mat-icon>
|
|
<span>{{ 'newMultipleClientButton' | translate }}</span>
|
|
</button>
|
|
}
|
|
@if (auth.userCategory !== 'ou-minimal') {
|
|
<button mat-menu-item (click)="addOU($event, selectedNode)">
|
|
<mat-icon>account_tree</mat-icon>
|
|
<span>{{ 'addOrganizationalUnit' | translate }}</span>
|
|
</button>
|
|
}
|
|
@if (auth.userCategory !== 'ou-minimal') {
|
|
<button mat-menu-item (click)="onEditNode($event, selectedNode)">
|
|
<mat-icon>edit</mat-icon>
|
|
<span>{{ 'buttonEdit' | translate }}</span>
|
|
</button>
|
|
}
|
|
@if (auth.userCategory !== 'ou-minimal' && auth.userCategory !== 'ou-operator') {
|
|
<button mat-menu-item (click)="onDeleteClick($event, selectedNode)">
|
|
<mat-icon>delete</mat-icon>
|
|
<span>{{ 'buttonDelete' | translate }}</span>
|
|
</button>
|
|
}
|
|
<button mat-menu-item (click)="openPartitionTypeModal($event, selectedNode)">
|
|
<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 || ''"
|
|
[runScriptContext]="getRunScriptContext(selectedNode?.clients || [])">
|
|
</app-execute-command>
|
|
</mat-menu>
|
|
</div>
|
|
<mat-divider [vertical]="true"></mat-divider>
|
|
<!-- CLIENTS -->
|
|
<div class="clients-container">
|
|
<mat-divider class="clients-mat-divider"></mat-divider>
|
|
<!-- CLIENTS HEADER -->
|
|
<div class="clients-view-header">
|
|
<span [ngStyle]="{ visibility: isLoadingClients ? 'hidden' : 'visible' }" class="clients-title-name">
|
|
{{ 'labelClients' | translate }}
|
|
<strong>{{ selectedNode?.name }}</strong>
|
|
</span>
|
|
<div class="view-type-container">
|
|
@if (currentView === 'card') {
|
|
<mat-checkbox class="cards-select-all-inline"
|
|
(change)="toggleAllCards()"
|
|
[checked]="selection.hasValue() && isAllSelected()"
|
|
[indeterminate]="selection.hasValue() && !isAllSelected()"
|
|
[matTooltip]="'buttonSelectAll' | translate">
|
|
</mat-checkbox>
|
|
}
|
|
<button class="action-button" [disabled]="selection.selected.length === 0" (click)="changeParent($event)"
|
|
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>
|
|
@if (classroomCalendarUuid && !syncingUds) {
|
|
<button class="action-button sync-uds-button" (click)="syncUds($event)"
|
|
[disabled]="!classroomHasRemotePc"
|
|
matTooltip="Sincronizar calendarios con UDS" matTooltipShowDelay="500">
|
|
<mat-icon>sync</mat-icon>
|
|
</button>
|
|
}
|
|
@if (syncingUds) {
|
|
<mat-spinner diameter="24" style="margin: 0 8px;"></mat-spinner>
|
|
}
|
|
<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" [attr.aria-label]="'viewType' | translate" [hideSingleSelectionIndicator]="true"
|
|
(change)="toggleView($event.value)" joyrideStep="tabsStep" text="{{ 'tabsStepText' | translate }}">
|
|
<mat-button-toggle value="list" [disabled]="currentView === 'list'">
|
|
<mat-icon>list</mat-icon> <span class="type-view-text">{{ 'vistalista' | translate }}</span>
|
|
</mat-button-toggle>
|
|
<mat-button-toggle value="card" [disabled]="currentView === 'card'">
|
|
<mat-icon>grid_view</mat-icon> <span class="type-view-text">{{ 'vistatarjeta' | translate }}</span>
|
|
</mat-button-toggle>
|
|
</mat-button-toggle-group>
|
|
</div>
|
|
</div>
|
|
<app-loading [isLoading]="isLoadingClients"></app-loading>
|
|
<!-- CLIENTS VIEWS-->
|
|
@if (!isLoadingClients) {
|
|
<div class="clients-view" joyrideStep="clientsViewStep"
|
|
text="{{ 'clientsViewStepText' | translate }}">
|
|
@if (hasClients) {
|
|
<div>
|
|
@if (currentView === 'list') {
|
|
<div class="stats-container">
|
|
<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 -->
|
|
@if (currentView === 'card') {
|
|
<div>
|
|
<section class="cards-view">
|
|
<div class="clients-grid">
|
|
@for (client of arrayClients; track client) {
|
|
<div class="client-item" [ngClass]="'status-' + client.status">
|
|
<div class="client-card">
|
|
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(client)"
|
|
[checked]="selection.isSelected(client)">
|
|
</mat-checkbox>
|
|
<img style="margin-top: 0.5em;" [src]="'assets/images/computer_' + client.status + '.svg'"
|
|
alt="Client Icon" class="client-image" />
|
|
<div class="client-details">
|
|
<span class="client-name truncate-cell-wide" [matTooltip]="client.name">{{ client.name }}</span>
|
|
<span class="client-ip">{{ client.ip }}</span>
|
|
<span class="client-mac">{{ client.mac }}</span>
|
|
@if (client.netSpeed) {
|
|
<span class="badge-net-speed" [matTooltip]="client.netSpeed + ' Mbps'">
|
|
<mat-icon class="net-speed-icon">speed</mat-icon> {{ client.netSpeed }} Mbps
|
|
</span>
|
|
}
|
|
@if (client.isRemotePcActive) {
|
|
<span class="remote-pc-badge">
|
|
<mat-icon class="remote-pc-icon">computer</mat-icon>
|
|
RemotePC
|
|
</span>
|
|
}
|
|
<div class="action-icons">
|
|
<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>
|
|
<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>
|
|
<mat-menu #clientMenu="matMenu">
|
|
<ng-container *ngTemplateOutlet="sharedMenuItems; context: {client: client}"></ng-container>
|
|
</mat-menu>
|
|
@if (syncingClientIds.has(client.uuid)) {
|
|
<span class="sync-spinner">
|
|
<mat-spinner diameter="24"></mat-spinner>
|
|
</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</section>
|
|
<div class="paginator-container">
|
|
<mat-paginator class="cards-paginator" [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page"
|
|
[pageSizeOptions]="pageSizeOptions" (page)="onPageChange($event)">
|
|
</mat-paginator>
|
|
</div>
|
|
</div>
|
|
}
|
|
<!-- List view mejorada -->
|
|
@if (currentView === 'list') {
|
|
<div 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="{{ 'buttonUpdate' | 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 [dataSource]="selectedClients" class="mat-elevation-z8">
|
|
<ng-container matColumnDef="select">
|
|
<th mat-header-cell *matHeaderCellDef>
|
|
<mat-checkbox (change)="$event ? toggleAllRows() : null"
|
|
[checked]="selection.hasValue() && isAllSelected()"
|
|
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
|
</mat-checkbox>
|
|
</th>
|
|
<td mat-cell *matCellDef="let row">
|
|
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(row)"
|
|
[checked]="selection.isSelected(row)">
|
|
</mat-checkbox>
|
|
</td>
|
|
</ng-container>
|
|
<ng-container matColumnDef="status">
|
|
<th mat-header-cell *matHeaderCellDef>
|
|
<div class="column-header">
|
|
<span>{{ 'labelState' | 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">
|
|
<img [src]="'assets/images/computer_' + client.status + '.svg'" alt="Client Icon"
|
|
class="client-image" />
|
|
@if (syncingClientIds.has(client.uuid)) {
|
|
<span>
|
|
<mat-spinner diameter="24"></mat-spinner>
|
|
</span>
|
|
}
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
<ng-container matColumnDef="name">
|
|
<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">
|
|
<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>
|
|
<div class="column-header">
|
|
<span>{{ 'labelIpCaps' | translate }}</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 class="client-cell">
|
|
<span class="client-ip">{{ client.ip }}</span>
|
|
<span class="client-mac">{{ client.mac }}</span>
|
|
@if (client.netSpeed) {
|
|
<span class="badge-net-speed list-view-badge" [matTooltip]="client.netSpeed + ' Mbps'">
|
|
<mat-icon class="net-speed-icon">speed</mat-icon> {{ client.netSpeed }} Mbps
|
|
</span>
|
|
}
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
<ng-container matColumnDef="firmwareType">
|
|
<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">
|
|
@if (client.firmwareType) {
|
|
<mat-chip class="firmware-chip">
|
|
{{ client.firmwareType }}
|
|
</mat-chip>
|
|
}
|
|
</td>
|
|
</ng-container>
|
|
<ng-container matColumnDef="oglive">
|
|
<th mat-header-cell *matHeaderCellDef> {{ 'ogLiveTitle' | translate }}</th>
|
|
<td mat-cell *matCellDef="let client">
|
|
<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> {{ 'maintenance' | translate }} </th>
|
|
<td mat-cell *matCellDef="let client"> {{ client.maintenance }} </td>
|
|
</ng-container>
|
|
<ng-container matColumnDef="subnet">
|
|
<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> {{ '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> {{ 'labelEditOrgUnitParent' | 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> {{ 'actions' | translate }} </th>
|
|
<td mat-cell *matCellDef="let client">
|
|
<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>
|
|
<mat-menu #clientMenu="matMenu">
|
|
<ng-container *ngTemplateOutlet="sharedMenuItems; context: {client: client}"></ng-container>
|
|
</mat-menu>
|
|
<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="{{ 'buttonViewDetails' | translate }}">
|
|
<mat-icon>visibility</mat-icon>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
<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"
|
|
[pageSizeOptions]="pageSizeOptions" (page)="onPageChange($event)">
|
|
</mat-paginator>
|
|
</div>
|
|
}
|
|
</div>
|
|
} @else {
|
|
@if (!initialLoading) {
|
|
<div class="no-clients-info">
|
|
<span>{{ 'noClients' | translate }}</span>
|
|
<mat-icon>error_outline</mat-icon>
|
|
</div>
|
|
}
|
|
}
|
|
<!-- No clients view -->
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- MAIN VIEW -->
|
|
</div>
|
|
|
|
<!-- Shared Menu Items Template -->
|
|
<ng-template #sharedMenuItems let-client="client">
|
|
@if (auth.userCategory !== 'ou-minimal') {
|
|
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
|
|
<mat-icon>edit</mat-icon>
|
|
<span>{{ 'buttonEdit' | translate }}</span>
|
|
</button>
|
|
}
|
|
<button mat-menu-item (click)="onShowClientDetail($event, client)">
|
|
<mat-icon>visibility</mat-icon>
|
|
<span>{{ 'buttonViewDetails' | translate }}</span>
|
|
</button>
|
|
@if (!syncingClientIds.has(client.uuid)) {
|
|
<button mat-menu-item (click)="getStatus(client, selectedNode)">
|
|
<mat-icon>sync</mat-icon>
|
|
<span>{{ 'buttonSync' | translate }}</span>
|
|
</button>
|
|
}
|
|
<button mat-menu-item (click)="openClientTaskLogs($event, client)">
|
|
<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)="openClientLogsInNewTab($event, client)">
|
|
<mat-icon>article</mat-icon>
|
|
<span>{{ 'realTimeLogs' | translate }}</span>
|
|
</button>
|
|
<button mat-menu-item (click)="openClientLogsModal($event, client)">
|
|
<mat-icon>analytics</mat-icon>
|
|
<span>{{ 'clientLogs' | translate }}</span>
|
|
</button>
|
|
<button mat-menu-item (click)="openClientDiskLogsModal($event, client)">
|
|
<mat-icon>storage</mat-icon>
|
|
<span>{{ 'diskLogs' | translate }}</span>
|
|
</button>
|
|
@if (auth.userCategory !== 'ou-minimal') {
|
|
<button mat-menu-item (click)="onDeleteClick($event, client)">
|
|
<mat-icon>delete</mat-icon>
|
|
<span>{{ 'buttonDelete' | translate }}</span>
|
|
</button>
|
|
}
|
|
</ng-template>
|
|
|
|
|