Refactor trace

oggui/calendar
Manuel Aranda Rosales 2024-10-03 19:04:16 +02:00
parent a6f0d30c21
commit 0bbe428739
4 changed files with 283 additions and 158 deletions

View File

@ -42,7 +42,7 @@ import { EditClientComponent } from './components/groups/shared/clients/edit-cli
import { ClassroomViewComponent } from './components/groups/shared/classroom-view/classroom-view.component'; import { ClassroomViewComponent } from './components/groups/shared/classroom-view/classroom-view.component';
import { MatProgressSpinner } from "@angular/material/progress-spinner"; import { MatProgressSpinner } from "@angular/material/progress-spinner";
import { MatMenu, MatMenuItem, MatMenuTrigger } from "@angular/material/menu"; import { MatMenu, MatMenuItem, MatMenuTrigger } from "@angular/material/menu";
import { MatAutocomplete } from "@angular/material/autocomplete"; import {MatAutocomplete, MatAutocompleteTrigger} from "@angular/material/autocomplete";
import { MatChip, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule } from "@angular/material/chips"; import { MatChip, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule } from "@angular/material/chips";
import { ClientViewComponent } from './components/groups/shared/client-view/client-view.component'; import { ClientViewComponent } from './components/groups/shared/client-view/client-view.component';
import { MatTab, MatTabGroup } from "@angular/material/tabs"; import { MatTab, MatTabGroup } from "@angular/material/tabs";
@ -159,42 +159,42 @@ import { OrganizationalUnitTabViewComponent } from './components/groups/componen
OrganizationalUnitTabViewComponent OrganizationalUnitTabViewComponent
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
imports: [BrowserModule, imports: [BrowserModule,
AppRoutingModule, AppRoutingModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
MatToolbarModule, MatToolbarModule,
MatIconModule, MatIconModule,
MatButtonModule, MatButtonModule,
MatSidenavModule, MatSidenavModule,
NoopAnimationsModule, NoopAnimationsModule,
MatCardModule, MatCardModule,
MatCheckboxModule, MatCheckboxModule,
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,
MatListModule, MatListModule,
MatTableModule, MatTableModule,
MatDialogModule, MatDialogModule,
MatSelectModule, MatSelectModule,
MatDividerModule, MatDividerModule,
MatStepperModule, MatStepperModule,
DragDropModule, DragDropModule,
MatSlideToggleModule, MatMenu, MatMenuTrigger, MatMenuItem, MatAutocomplete, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule, MatChip, MatProgressSpinner, MatTabGroup, MatTab, MatTooltip, MatSlideToggleModule, MatMenu, MatMenuTrigger, MatMenuItem, MatAutocomplete, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule, MatChip, MatProgressSpinner, MatTabGroup, MatTab, MatTooltip,
MatExpansionModule, MatExpansionModule,
NgxChartsModule, NgxChartsModule,
MatDatepickerModule, MatDatepickerModule,
MatNativeDateModule, MatNativeDateModule,
ToastrModule.forRoot( ToastrModule.forRoot(
{ {
timeOut: 5000, timeOut: 5000,
positionClass: 'toast-bottom-right', positionClass: 'toast-bottom-right',
preventDuplicates: true, preventDuplicates: true,
progressBar: true, progressBar: true,
progressAnimation: 'increasing', progressAnimation: 'increasing',
closeButton: true closeButton: true
} }
), MatGridList, MatTree, MatTreeNode, MatNestedTreeNode, MatTreeNodeToggle, MatTreeNodeDef, MatTreeNodePadding, MatTreeNodeOutlet, MatPaginator, MatGridTile, MatExpansionPanel, MatExpansionPanelTitle, MatExpansionPanelDescription, MatRadioGroup, MatRadioButton ), MatGridList, MatTree, MatTreeNode, MatNestedTreeNode, MatTreeNodeToggle, MatTreeNodeDef, MatTreeNodePadding, MatTreeNodeOutlet, MatPaginator, MatGridTile, MatExpansionPanel, MatExpansionPanelTitle, MatExpansionPanelDescription, MatRadioGroup, MatRadioButton, MatAutocompleteTrigger
], ],
schemas: [ schemas: [
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
], ],

View File

@ -1,3 +1,82 @@
mat-form-field{ .title {
margin: 10px; font-size: 24px;
} }
.calendar-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
}
.divider {
margin: 20px 0;
}
.lists-container {
padding: 16px;
}
.imagesLists-container {
flex: 1;
}
.card.unidad-card {
height: 100%;
box-sizing: border-box;
}
table {
width: 100%;
margin-top: 50px;
}
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 5px;
box-sizing: border-box;
}
.search-string {
flex: 1;
padding: 5px;
}
.search-boolean {
flex: 1;
padding: 5px;
}
.search-select {
flex: 2;
padding: 5px;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
}
.paginator-container {
display: flex;
justify-content: end;
margin-bottom: 30px;
}
.mat-chip-readonly-true {
background-color: #4CAF50 !important;
color: white !important;
}
.mat-chip-readonly-false {
background-color: #F44336 !important;
color: white !important;
}

View File

@ -1,91 +1,47 @@
<div class="header-container"> <div class="header-container">
<h2 class="title">Trazas de ejecuciones</h2> <h2 class="title" i18n="@@adminCommandsTitle">Trazas de comandos y procedimientos</h2>
</div>
<mat-form-field appearance="fill"> <mat-divider class="divider"></mat-divider>
<mat-label>Buscar por comando</mat-label> <div class="search-container">
<input matInput [(ngModel)]="searchCommand" (ngModelChange)="filterTraces()" placeholder="Escribe el nombre del comando"> <mat-form-field appearance="fill" class="search-select">
</mat-form-field> <input type="text" matInput [formControl]="clientControl" [matAutocomplete]="clientAuto" placeholder="Seleccione un cliente">
<mat-autocomplete #clientAuto="matAutocomplete" [displayWith]="displayFnClient" (optionSelected)="onOptionClientSelected($event.option.value)">
<mat-form-field appearance="fill"> <mat-option *ngFor="let client of filteredClients | async" [value]="client">
<mat-label>Buscar por cliente</mat-label> {{ client.name }}
<input matInput [(ngModel)]="searchClient" (ngModelChange)="filterTraces()" placeholder="Escribe el nombre del cliente"> </mat-option>
</mat-form-field> </mat-autocomplete>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Buscar por estado</mat-label> <!-- Autocomplete para seleccionar un comando -->
<input matInput [(ngModel)]="searchStatus" (ngModelChange)="filterTraces()" placeholder="Escribe el estado"> <mat-form-field appearance="fill" class="search-select">
</mat-form-field> <input type="text" matInput [formControl]="commandControl" [matAutocomplete]="commandAuto" placeholder="Seleccione un comando">
<mat-autocomplete #commandAuto="matAutocomplete" [displayWith]="displayFnCommand" (optionSelected)="onOptionCommandSelected($event.option.value)">
<mat-form-field appearance="fill"> <mat-option *ngFor="let command of filteredCommands | async" [value]="command">
<mat-label>Buscar por usuario</mat-label> {{ command.name }}
<input matInput [(ngModel)]="searchCreatedBy" (ngModelChange)="filterTraces()" placeholder="Escribe quien creó"> </mat-option>
</mat-form-field> </mat-autocomplete>
</mat-form-field>
<mat-form-field appearance="fill"> </div>
<mat-label>Buscar por fecha </mat-label>
<input matInput [(ngModel)]="searchExecutedAt" (ngModelChange)="filterTraces()" placeholder="Escribe la fecha ejecutada"> <table mat-table [dataSource]="traces" class="mat-elevation-z8">
</mat-form-field> <ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
</div> <th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let trace" >
<table mat-table [dataSource]="groupedTraces" class="mat-elevation-z8"> <ng-container >
<ng-container matColumnDef="group"> {{ column.cell(trace) }}
<th mat-header-cell *matHeaderCellDef> Ejecución</th> </ng-container>
<td mat-cell *matCellDef="let group"> {{ group.commandId }} </td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="commandName"> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<th mat-header-cell *matHeaderCellDef> Comando </th> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<td mat-cell *matCellDef="let group; let i = index"> </table>
<ng-container *ngFor="let trace of group.traces">
<div *ngIf="i === 0">{{ trace.command.name }}</div> <div class="paginator-container">
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="clientName">
<th mat-header-cell *matHeaderCellDef> Cliente </th>
<td mat-cell *matCellDef="let group; let i = index">
<ng-container *ngFor="let trace of group.traces">
<div>{{ trace.client.name }}</div>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef> Estado </th>
<td mat-cell *matCellDef="let group; let i = index">
<ng-container *ngFor="let trace of group.traces">
<div>{{ trace.status }}</div>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="executedAt">
<th mat-header-cell *matHeaderCellDef> Ejecutado En </th>
<td mat-cell *matCellDef="let group; let i = index">
<ng-container *ngFor="let trace of group.traces">
<div>{{ trace.executedAt | date:'short' }}</div>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="createdBy">
<th mat-header-cell *matHeaderCellDef> Creado Por </th>
<td mat-cell *matCellDef="let group; let i = index">
<ng-container *ngFor="let trace of group.traces">
<div>{{ trace.createdBy }}</div>
</ng-container>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="['group', 'commandName', 'clientName', 'status', 'executedAt', 'createdBy']"></tr>
<tr mat-row *matRowDef="let row; columns: ['group', 'commandName', 'clientName', 'status', 'executedAt', 'createdBy'];"></tr>
</table>
<mat-paginator [length]="length" <mat-paginator [length]="length"
[pageSize]="itemsPerPage" [pageSize]="itemsPerPage"
[pageSizeOptions]="pageSizeOptions" [pageIndex]="page"
(page)="onPageChange($event)"> [pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator> </mat-paginator>
</div>

View File

@ -1,5 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import {Observable, startWith} from 'rxjs';
import {FormControl} from "@angular/forms";
import {map} from "rxjs/operators";
import {DatePipe} from "@angular/common";
@Component({ @Component({
selector: 'app-task-logs', selector: 'app-task-logs',
@ -7,36 +11,111 @@ import { HttpClient } from '@angular/common/http';
styleUrls: ['./task-logs.component.css'] styleUrls: ['./task-logs.component.css']
}) })
export class TaskLogsComponent implements OnInit { export class TaskLogsComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
traces: any[] = []; traces: any[] = [];
groupedTraces: any[] = []; groupedTraces: any[] = [];
commands: any[] = [];
clients: any[] = [];
length: number = 0; length: number = 0;
itemsPerPage: number = 20; itemsPerPage: number = 20;
page: number = 1; page: number = 0;
loading: boolean = true;
pageSizeOptions: number[] = [10, 20, 30, 50]; pageSizeOptions: number[] = [10, 20, 30, 50];
displayedColumns: string[] = ['commandId','commandName', 'clientName', 'status', 'executedAt', 'createdBy']; datePipe: DatePipe = new DatePipe('es-ES');
searchCommand: string = ''; columns = [
searchClient: string = ''; {
searchStatus: string = ''; columnDef: 'id',
searchCreatedBy: string = ''; header: 'ID',
searchExecutedAt: string = ''; cell: (trace: any) => `${trace.id}`,
},
{
columnDef: 'command',
header: 'Comando',
cell: (trace: any) => `${trace.command?.name}`
},
{
columnDef: 'client',
header: 'Client',
cell: (trace: any) => `${trace.client?.name}`
},
{
columnDef: 'status',
header: 'Estado',
cell: (trace: any) => `${trace.status}`
},
{
columnDef: 'executedAt',
header: 'Programacion de ejecución',
cell: (trace: any) => `${this.datePipe.transform(trace.executedAt, 'dd/MM/yyyy hh:mm:ss')}`,
},
{
columnDef: 'createdAt',
header: 'Fecha de creación',
cell: (trace: any) => `${this.datePipe.transform(trace.createdAt, 'dd/MM/yyyy hh:mm:ss')}`,
}
];
displayedColumns = [...this.columns.map(column => column.columnDef)];
filters: { [key: string]: string } = {};
filteredClients!: Observable<any[]>;
clientControl = new FormControl();
filteredCommands!: Observable<any[]>;
commandControl = new FormControl();
constructor(private http: HttpClient) {} constructor(private http: HttpClient) {}
ngOnInit(): void { ngOnInit(): void {
this.loadTraces(); this.loadTraces();
this.loadCommands();
this.loadClients();
this.filteredCommands = this.commandControl.valueChanges.pipe(
startWith(''),
map(value => (typeof value === 'string' ? value : value?.name)),
map(name => (name ? this._filterCommands(name) : this.commands.slice()))
);
this.filteredClients = this.clientControl.valueChanges.pipe(
startWith(''),
map(value => (typeof value === 'string' ? value : value?.name)),
map(name => (name ? this._filterClients(name) : this.clients.slice()))
);
}
private _filterClients(name: string): any[] {
const filterValue = name.toLowerCase();
return this.clients.filter(client => client.name.toLowerCase().includes(filterValue));
}
private _filterCommands(name: string): any[] {
const filterValue = name.toLowerCase();
return this.commands.filter(command => command.name.toLowerCase().includes(filterValue));
}
displayFnClient(client: any): string {
return client && client.name ? client.name : '';
}
displayFnCommand(command: any): string {
return command && command.name ? command.name : '';
}
onOptionCommandSelected(selectedCommand: any): void {
this.filters['command.id'] = selectedCommand.id;
this.loadTraces();
}
onOptionClientSelected(selectedClient: any): void {
this.filters['client.id'] = selectedClient.id;
this.loadTraces();
} }
loadTraces(): void { loadTraces(): void {
const url = `${this.baseUrl}/traces?page=${this.page}&itemsPerPage=${this.itemsPerPage}`; const url = `${this.baseUrl}/traces?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`;
this.http.get<any>(url, { params: this.filters }).subscribe(
this.http.get<any>(url).subscribe(
(data) => { (data) => {
this.traces = data['hydra:member']; this.traces = data['hydra:member'];
this.length = data['hydra:totalItems']; this.length = data['hydra:totalItems'];
this.groupedTraces = this.groupByCommandId(this.traces); this.groupedTraces = this.groupByCommandId(this.traces);
}, },
(error) => { (error) => {
console.error('Error fetching traces', error); console.error('Error fetching traces', error);
@ -44,6 +123,32 @@ export class TaskLogsComponent implements OnInit {
); );
} }
loadCommands() {
this.http.get<any>( `${this.baseUrl}/commands?&page=1&itemsPerPage=10000`).subscribe(
response => {
this.commands = response['hydra:member'];
this.loading = false;
},
error => {
console.error('Error fetching parent units:', error);
this.loading = false;
}
);
}
loadClients() {
this.http.get<any>( `${this.baseUrl}/clients?&page=1&itemsPerPage=10000`).subscribe(
response => {
this.clients = response['hydra:member'];
this.loading = false;
},
error => {
console.error('Error fetching parent units:', error);
this.loading = false;
}
);
}
groupByCommandId(traces: any[]): any[] { groupByCommandId(traces: any[]): any[] {
const grouped: { [key: string]: any[] } = {}; const grouped: { [key: string]: any[] } = {};
@ -62,23 +167,8 @@ export class TaskLogsComponent implements OnInit {
} }
onPageChange(event: any): void { onPageChange(event: any): void {
this.page = event.pageIndex + 1; this.page = event.pageIndex + 1;
this.itemsPerPage = event.pageSize; this.itemsPerPage = event.pageSize;
this.loadTraces(); this.loadTraces();
}
filterTraces(): void {
const filtered = this.traces.filter(trace => {
const commandMatch = trace.command.name.toLowerCase().includes(this.searchCommand.toLowerCase());
const clientMatch = trace.client.name.toLowerCase().includes(this.searchClient.toLowerCase());
const statusMatch = trace.status.toLowerCase().includes(this.searchStatus.toLowerCase());
const createdByMatch = trace.createdBy.toLowerCase().includes(this.searchCreatedBy.toLowerCase());
const executedAtMatch = trace.executedAt.toLowerCase().includes(this.searchExecutedAt.toLowerCase());
return commandMatch && clientMatch && statusMatch && createdByMatch && executedAtMatch;
});
this.length = filtered.length;
this.groupedTraces = this.groupByCommandId(filtered);
} }
} }