refs #1705 Add Global Status component with styling and integration in header
testing/ogGui-multibranch/pipeline/head This commit looks good
Details
testing/ogGui-multibranch/pipeline/head This commit looks good
Details
parent
d1af610e93
commit
984e4fe4db
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue