refs #1705 Add Global Status component with styling and integration in header
testing/ogGui-multibranch/pipeline/head This commit looks good Details

pull/18/head
Lucas Lara García 2025-03-18 09:56:36 +01:00
parent d1af610e93
commit 984e4fe4db
10 changed files with 242 additions and 16 deletions

View File

@ -133,6 +133,7 @@ import { ManageClientComponent } from './components/groups/shared/clients/manage
import { ConvertImageComponent } from './components/repositories/convert-image/convert-image.component';
import { registerLocaleData } from '@angular/common';
import localeEs from '@angular/common/locales/es';
import { GlobalStatusComponent } from './components/global-status/global-status.component';
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './locale/', '.json');
@ -225,7 +226,8 @@ registerLocaleData(localeEs, 'es-ES');
BackupImageComponent,
ShowClientsComponent,
OperationResultDialogComponent,
ConvertImageComponent
ConvertImageComponent,
GlobalStatusComponent
],
bootstrap: [AppComponent],
imports: [BrowserModule,

View File

@ -0,0 +1,22 @@
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}
.status-led {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
margin-right: 10px;
}
.status-led.active {
background-color: green;
}
.status-led.inactive {
background-color: red;
}

View File

@ -0,0 +1,66 @@
<header>
<h1 mat-dialog-title>Estado global de la aplicación</h1>
</header>
<mat-dialog-content>
<mat-tab-group>
<mat-tab label="OgBoot">
<app-loading [isLoading]="loading"></app-loading>
<div class="dashboard">
<!-- Disk Usage Section -->
<div class="disk-usage" joyrideStep="diskUsageStep" text="{{ 'diskUsageDescription' | translate }}">
<h3>{{ 'diskUsageTitle' | translate }}</h3>
<ngx-charts-pie-chart [view]="view" [scheme]="colorScheme" [results]="diskUsageChartData"
[gradient]="gradient" [doughnut]="isDoughnut" [labels]="showLabels">
</ngx-charts-pie-chart>
<div class="disk-usage-info">
<p>{{ 'totalLabel' | translate }}: {{ formatBytes(ogBootDiskUsage.total) }}</p>
<p>{{ 'usedLabel' | translate }}: {{ formatBytes(ogBootDiskUsage.used) }}</p>
<p>{{ 'availableLabel' | translate }}: {{ formatBytes(ogBootDiskUsage.available) }}</p>
<p>{{ 'freeLabel' | translate }}: {{ ogBootDiskUsage.percentage }}%</p>
</div>
</div>
<!-- Services Status Section -->
<div class="services-status" joyrideStep="servicesStatusStep"
text="{{ 'servicesStatusDescription' | translate }}">
<h3>{{ 'servicesTitle' | translate }}</h3>
<ul>
<li *ngFor="let service of getServices()">
<span class="status-led"
[ngClass]="{ 'active': service.status === 'active', 'inactive': service.status !== 'active' }"></span>
{{ service.name }}: {{ service.status | translate }}
</li>
</ul>
</div>
<!-- Installed OgLives Section -->
<div class="installed-oglives" joyrideStep="oglivesStep" text="{{ 'oglivesDescription' | translate }}">
<h3>{{ 'installedOglivesTitle' | translate }}</h3>
<table>
<thead>
<tr>
<th>{{ 'idLabel' | translate }}</th>
<th>{{ 'kernelLabel' | translate }}</th>
<th>{{ 'architectureLabel' | translate }}</th>
<th>{{ 'revisionLabel' | translate }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let oglive of installedOgLives">
<td>{{ oglive.id }}</td>
<td>{{ oglive.kernel }}</td>
<td>{{ oglive.architecture }}</td>
<td>{{ oglive.revision }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</mat-tab>
<mat-tab label="Dhcp"> Content 2 </mat-tab>
<mat-tab label="Pxe"> Content 3 </mat-tab>
</mat-tab-group>
</mat-dialog-content>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" [mat-dialog-close]="true">{{ 'closeButton' | translate }}</button>
</mat-dialog-actions>

View File

@ -0,0 +1,49 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ToastrModule } from 'ngx-toastr';
import { MatDialogModule } from '@angular/material/dialog';
import { MatTabsModule } from '@angular/material/tabs';
import { GlobalStatusComponent } from './global-status.component';
import { ConfigService } from '@services/config.service';
import { LoadingComponent } from '../../shared/loading/loading.component';
import { TranslateModule } from '@ngx-translate/core';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NO_ERRORS_SCHEMA } from '@angular/core';
describe('GlobalStatusComponent', () => {
let component: GlobalStatusComponent;
let fixture: ComponentFixture<GlobalStatusComponent>;
beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url'
};
await TestBed.configureTestingModule({
declarations: [GlobalStatusComponent, LoadingComponent],
imports: [
HttpClientTestingModule,
ToastrModule.forRoot(),
MatDialogModule,
MatTabsModule,
TranslateModule.forRoot(),
NgxChartsModule,
BrowserAnimationsModule
],
providers: [
{ provide: ConfigService, useValue: mockConfigService }
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
fixture = TestBed.createComponent(GlobalStatusComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,77 @@
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { ConfigService } from '@services/config.service';
import { ToastrService } from 'ngx-toastr';
@Component({
selector: 'app-global-status',
templateUrl: './global-status.component.html',
styleUrl: './global-status.component.css'
})
export class GlobalStatusComponent implements OnInit {
baseUrl: string;
ogBootApiUrl: string;
loading: boolean = false;
ogBootDiskUsage: any = {};
ogBootSubnets: any[] = [];
ogBootServicesStatus: any = {};
installedOgLives: any[] = [];
diskUsageChartData: any[] = [];
gradient: boolean = true;
showLabels: boolean = true;
isDoughnut: boolean = true;
colorScheme: any = {
domain: ['#df200d', '#26a700']
};
view: [number, number] = [1100, 500];
constructor(
private configService: ConfigService,
private http: HttpClient,
private toastService: ToastrService
) {
this.baseUrl = this.configService.apiUrl;
this.ogBootApiUrl = `${this.baseUrl}/og-boot/status`;
}
ngOnInit(): void {
}
loadOgBootStatus(): void {
this.loading = true;
this.http.get<any>(this.ogBootApiUrl).subscribe({
next: data => {
this.ogBootDiskUsage = data.message.disk_usage;
this.ogBootSubnets = data.message.subnets;
this.ogBootServicesStatus = data.message.service_status;
this.installedOgLives = data.message.installed_oglives;
this.loading = false;
},
error: error => {
this.toastService.error('Error al sincronizar ogBoot');
console.log(error);
this.loading = false;
}
});
}
getServices(): { name: string, status: string }[] {
return Object.keys(this.ogBootServicesStatus).map(key => ({
name: key,
status: this.ogBootServicesStatus[key]
}))
}
formatBytes(bytes: number): string {
if (bytes >= 1e9) {
return (bytes / 1e9).toFixed(2) + ' GB';
} else if (bytes >= 1e6) {
return (bytes / 1e6).toFixed(2) + ' MB';
} else if (bytes >= 1e3) {
return (bytes / 1e3).toFixed(2) + ' KB';
} else {
return bytes + ' B';
}
}
}

View File

@ -17,13 +17,14 @@ import { DeleteModalComponent } from '../../shared/delete_modal/delete-modal/del
import { ClassroomViewDialogComponent } from './shared/classroom-view/classroom-view-modal';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { PageEvent } from '@angular/material/paginator';
import { CreateMultipleClientComponent } from "./shared/clients/create-multiple-client/create-multiple-client.component";
import { SelectionModel } from "@angular/cdk/collections";
import { ManageClientComponent } from "./shared/clients/manage-client/manage-client.component";
import { debounceTime } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { ConfigService } from '@services/config.service';
import { GlobalStatusComponent } from '../global-status/global-status.component';
enum NodeType {
OrganizationalUnit = 'organizational-unit',

View File

@ -9,36 +9,39 @@
</button>
<div class="navbar-actions-row">
<button class="trace-button" routerLink="/commands-logs" mat-button
i18n="@@admin"><mat-icon>notifications</mat-icon></button>
<button class="trace-button" routerLink="/commands-logs" mat-button><mat-icon>notifications</mat-icon></button>
<div class="navbar-buttons-row">
<button class="ordinary-button" *ngIf="isSuperAdmin" [matMenuTriggerFor]="menu" i18n="@@admin"
<button class="ordinary-button" (click)="showGlobalStatus()">
{{'GlobalStatus' | translate}}
</button>
<button class="ordinary-button" *ngIf="isSuperAdmin" [matMenuTriggerFor]="menu"
matTooltip="Gestión de usuarios y roles de la aplicación" matTooltipShowDelay="1000">
{{ 'Administration' | translate }}
</button>
<button class="ordinary-button" *ngIf="!isSuperAdmin" (click)="editUser()" i18n="@@editUser"
<button class="ordinary-button" *ngIf="!isSuperAdmin" (click)="editUser()"
matTooltip="Editar tu información de usuario" matTooltipShowDelay="1000">
{{ 'changePassword' | translate }}
</button>
<button class="logout-button" routerLink="/auth/login" i18n="@@logout"
matTooltip="Cerrar sesión y salir de la aplicación" matTooltipShowDelay="1000">
<button class="logout-button" routerLink="/auth/login" matTooltip="Cerrar sesión y salir de la aplicación"
matTooltipShowDelay="1000">
{{ 'logout' | translate }}
</button>
</div>
<mat-menu #menu="matMenu">
<button mat-menu-item routerLink="/users" i18n="@@usersMenuItem" matTooltip="Ver y gestionar todos los usuarios"
<button mat-menu-item routerLink="/users" matTooltip="Ver y gestionar todos los usuarios"
matTooltipShowDelay="1000">
{{ 'labelUsers' | translate }}
</button>
<button mat-menu-item routerLink="/user-groups" i18n="@@rolesMenuItem" matTooltip="Gestionar roles de usuario"
<button mat-menu-item routerLink="/user-groups" matTooltip="Gestionar roles de usuario"
matTooltipShowDelay="1000">
{{ 'labelRoles' | translate }}
</button>
<button mat-menu-item routerLink="/env-vars" i18n="@@rolesMenuItem" matTooltip="Gestionar variables de entorno"
<button mat-menu-item routerLink="/env-vars" matTooltip="Gestionar variables de entorno"
matTooltipShowDelay="1000">
{{ 'labelEnvVars' | translate }}
</button>

View File

@ -1,7 +1,8 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import {jwtDecode} from 'jwt-decode';
import { jwtDecode } from 'jwt-decode';
import { ChangePasswordModalComponent } from '../../components/admin/users/users/change-password-modal/change-password-modal.component';
import {MatDialog} from "@angular/material/dialog";
import { MatDialog } from "@angular/material/dialog";
import { GlobalStatusComponent } from 'src/app/components/global-status/global-status.component';
@Component({
selector: 'app-header',
@ -20,7 +21,7 @@ export class HeaderComponent implements OnInit {
this.toggleSidebar.emit();
}
constructor(public dialog: MatDialog) {}
constructor(public dialog: MatDialog) { }
ngOnInit(): void {
const token = localStorage.getItem('loginToken');
@ -47,4 +48,7 @@ export class HeaderComponent implements OnInit {
});
}
showGlobalStatus() {
this.dialog.open(GlobalStatusComponent)
}
}

View File

@ -468,5 +468,6 @@
"filtersPanelStepText": "Use these filters to search or load configurations.",
"organizationalUnitsStepText": "List of Organizational Units. Click on one to view details.",
"defaultMenuLabel": "Main menu",
"noClients": "No clients"
"noClients": "No clients",
"GlobalStatus": "Global Status"
}

View File

@ -469,5 +469,6 @@
"filtersPanelStepText": "Utiliza estos filtros para buscar o cargar configuraciones.",
"organizationalUnitsStepText": "Lista de Unidades Organizacionales. Haz clic en una para ver detalles.",
"defaultMenuLabel": "Menú por defecto",
"noClients": "No hay clientes"
"noClients": "No hay clientes",
"GlobalStatus": "Estado Global"
}