Creada vista de logs de disco de cada componente de OG en la vista estado global, llama al endpoint de ogcore /opengnsys/system/disk-logs

disk-logs
Juan Manuel Bardallo 2026-05-14 13:19:52 +02:00
parent b0d1f743a8
commit 6a3c75ccb7
11 changed files with 384 additions and 37 deletions

View File

@ -201,6 +201,7 @@ import { OgcoreStatusComponent } from './components/global-status/ogcore-status/
import { GlobalOgbootStatusComponent } from './components/global-status/ogboot-status/ogboot-status.component';
import { OgrepositoryStatusComponent } from './components/global-status/ogrepository-status/ogrepository-status.component';
import { OgdhcpStatusComponent } from './components/global-status/ogdhcp-status/ogdhcp-status.component';
import { StatusLogsComponent } from './components/global-status/status-logs/status-logs.component';
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './locale/', '.json');
}
@ -344,6 +345,7 @@ registerLocaleData(localeEs, 'es-ES');
GlobalOgbootStatusComponent,
OgrepositoryStatusComponent,
OgdhcpStatusComponent,
StatusLogsComponent,
ImportLegacyComponent,
PageNotFoundComponent
],

View File

@ -42,15 +42,13 @@
</div>
</div>
<div class="logs-section" id="ogboot-logs-section">
<div class="logs-header">
<h3>{{ 'ogbootLogs' | translate }}</h3>
<p>{{ 'realTimeOgbootLogs' | translate }}</p>
</div>
<div class="logs-container">
<iframe [src]="getBootServerIframeUrl()" width="100%" height="800" frameborder="0" class="ogboot-logs-iframe">
</iframe>
</div>
<div id="ogboot-logs-section">
<app-status-logs
[title]="'ogbootLogs'"
[identifier]="'ogboot'"
[host]="getSelectedBootServerName()"
[oglogUrl]="getBootServerIframeUrl()">
</app-status-logs>
</div>
</div>
}

View File

@ -1,14 +1,12 @@
<div class="section-content">
<div class="tab-content">
<div class="logs-section" id="ogcore-logs-section">
<div class="logs-header">
<h3>{{ 'ogcoreLogs' | translate }}</h3>
<p>{{ 'realTimeOgcoreLogs' | translate }}</p>
</div>
<div class="logs-container">
<iframe [src]="getOgCoreIframeUrl()" width="100%" height="800" frameborder="0" class="ogcore-logs-iframe">
</iframe>
</div>
<div id="ogcore-logs-section">
<app-status-logs
[title]="'ogcoreLogs'"
[identifier]="'ogcore'"
[host]="''"
[oglogUrl]="getOgCoreIframeUrl()">
</app-status-logs>
</div>
</div>
</div>

View File

@ -42,15 +42,13 @@
</div>
</div>
<div class="logs-section" id="dhcp-logs-section">
<div class="logs-header">
<h3>{{ 'dhcpLogs' | translate }}</h3>
<p>{{ 'realTimeDhcpLogs' | translate }}</p>
</div>
<div class="logs-container">
<iframe [src]="getDhcpIframeUrl()" width="100%" height="800" frameborder="0" class="dhcp-logs-iframe">
</iframe>
</div>
<div id="dhcp-logs-section">
<app-status-logs
[title]="'dhcpLogs'"
[identifier]="'ogdhcp'"
[host]="getSelectedDhcpName()"
[oglogUrl]="getDhcpIframeUrl()">
</app-status-logs>
</div>
</div>
}

View File

@ -49,16 +49,13 @@
</div>
</div>
<div class="repository-logs-section" id="repository-logs-section">
<div class="logs-header">
<h3>Datos del repositorio: {{ getSelectedRepositoryName() }}</h3>
<p>{{ 'specificRepoInfo' | translate }}</p>
</div>
<div class="logs-container">
<iframe [src]="getRepositoryIframeUrl()" width="100%" height="600" frameborder="0"
class="repository-logs-iframe">
</iframe>
</div>
<div id="repository-logs-section">
<app-status-logs
[title]="'ogrepositoryLogs'"
[identifier]="'ogrepo-api'"
[host]="getSelectedRepositoryName()"
[oglogUrl]="getRepositoryIframeUrl()">
</app-status-logs>
</div>
</div>
}

View File

@ -0,0 +1,165 @@
.logs-section {
background-color: var(--surface-card, #ffffff);
border-radius: 12px;
padding: 16px;
margin-top: 16px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.logs-header {
margin-bottom: 12px;
}
.logs-header h3 {
font-size: 1.1rem;
font-weight: 600;
margin: 0;
color: var(--text-primary, #111827);
}
.logs-header p {
font-size: 0.8rem;
color: var(--text-secondary, #6b7280);
margin: 2px 0 0;
}
.logs-tabs {
margin-top: 8px;
}
.logs-container {
position: relative;
background-color: #0d1117;
border-radius: 8px;
margin-top: 8px;
display: flex;
flex-direction: column;
}
.tab-actions-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 12px;
background: rgba(255, 255, 255, 0.05);
border-top-left-radius: 8px;
border-top-right-radius: 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.header-actions {
display: flex;
gap: 4px;
}
.header-actions button {
color: rgba(255, 255, 255, 0.7) !important;
}
.header-actions button:hover:not([disabled]) {
color: white !important;
background: rgba(255, 255, 255, 0.1);
}
.header-actions button[disabled] {
color: rgba(255, 255, 255, 0.2) !important;
}
.header-actions button[color="warn"] {
color: #ff4081 !important;
}
.live-badge {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.7rem;
font-weight: 700;
color: #ff4081;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.live-dot {
width: 6px;
height: 6px;
background-color: #ff4081;
border-radius: 50%;
animation: pulse 2s infinite;
}
.log-viewer {
margin: 0;
padding: 12px;
color: #e6edf3;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 0.8rem;
line-height: 1.4;
overflow-y: auto;
max-height: 50vh;
white-space: pre-wrap;
word-break: break-all;
}
@keyframes pulse {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(255, 64, 129, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 8px rgba(255, 64, 129, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(255, 64, 129, 0);
}
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(13, 17, 23, 0.7);
z-index: 10;
}
.error-message {
padding: 32px;
text-align: center;
color: #f85149;
}
.error-message mat-icon {
font-size: 48px;
width: 48px;
height: 48px;
margin-bottom: 16px;
}
.logs-iframe {
border-radius: 8px;
}
.no-oglog {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 64px;
color: #8b949e;
}
.no-oglog mat-icon {
font-size: 48px;
width: 48px;
height: 48px;
margin-bottom: 16px;
}

View File

@ -0,0 +1,59 @@
<div class="logs-section">
<div class="logs-header">
<h3>{{ title | translate }}</h3>
<p>{{ 'realTimeLogs' | translate }}</p>
</div>
<mat-tab-group class="logs-tabs" animationDuration="0ms" (selectedTabChange)="onTabChange($event)">
<mat-tab [label]="'diskLogs' | translate">
<div class="logs-container disk-logs">
<div class="tab-actions-header">
<div class="header-actions">
<button mat-icon-button
(click)="toggleLive()"
[matTooltip]="(isLive ? 'diskLogsStopLive' : 'diskLogsStartLive') | translate"
[color]="isLive ? 'warn' : ''">
<mat-icon>{{ isLive ? 'pause_circle' : 'play_circle' }}</mat-icon>
</button>
<button mat-icon-button (click)="refreshLogs()" [matTooltip]="'buttonUpdateData' | translate" [disabled]="isLive">
<mat-icon>refresh</mat-icon>
</button>
</div>
<div *ngIf="isLive" class="live-badge">
<span class="live-dot"></span>
LIVE
</div>
</div>
@if (loadingDiskLogs && !diskLogsContent) {
<div class="loading-overlay">
<mat-spinner diameter="40"></mat-spinner>
</div>
}
@if (errorDiskLogs) {
<div class="error-message">
<mat-icon>error_outline</mat-icon>
<p>{{ errorDiskLogs }}</p>
<button mat-button (click)="loadDiskLogs()">{{ 'buttonRetry' | translate }}</button>
</div>
}
<pre #logContent class="log-viewer">{{ diskLogsContent }}</pre>
</div>
</mat-tab>
<mat-tab [label]="'oglogLogs' | translate">
<div class="logs-container oglog-logs">
@if (oglogUrl) {
<iframe [src]="oglogUrl" width="100%" height="600" frameborder="0" class="logs-iframe"></iframe>
} @else {
<div class="no-oglog">
<mat-icon>info_outline</mat-icon>
<p>oglog no está disponible o no se ha configurado la URL.</p>
</div>
}
</div>
</mat-tab>
</mat-tab-group>
</div>

View File

@ -0,0 +1,119 @@
import { Component, Input, OnInit, OnDestroy, inject, ViewChild, ElementRef, AfterViewChecked } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { SafeResourceUrl } from '@angular/platform-browser';
import { ConfigService } from '@services/config.service';
import { TranslateService } from '@ngx-translate/core';
import { MatTabChangeEvent } from '@angular/material/tabs';
@Component({
selector: 'app-status-logs',
templateUrl: './status-logs.component.html',
styleUrl: './status-logs.component.css'
})
export class StatusLogsComponent implements OnInit, OnDestroy, AfterViewChecked {
@ViewChild('logContent') private logContainer!: ElementRef;
@Input() identifier: string = '';
@Input() host: string = '';
@Input() oglogUrl: SafeResourceUrl | null = null;
@Input() title: string = '';
public translate = inject(TranslateService);
diskLogsContent: string = '';
loadingDiskLogs: boolean = false;
errorDiskLogs: string | null = null;
isLive: boolean = true;
private pollInterval: any;
private readonly POLL_INTERVAL_MS = 5000;
constructor(
private http: HttpClient,
private configService: ConfigService
) {}
ngOnInit(): void {
this.loadDiskLogs();
if (this.isLive) {
this.startPolling();
}
}
ngAfterViewChecked() {
this.scrollToBottom();
}
toggleLive(): void {
this.isLive = !this.isLive;
if (this.isLive) {
this.loadDiskLogs();
this.startPolling();
} else {
this.stopPolling();
}
}
onTabChange(event: MatTabChangeEvent): void {
if (event.index === 0) { // Disk Logs tab
if (this.isLive) {
this.loadDiskLogs();
this.startPolling();
}
} else { // oglog Logs tab or others
this.stopPolling();
}
}
ngOnDestroy(): void {
this.stopPolling();
}
loadDiskLogs(): void {
if (!this.identifier) return;
if (!this.isLive) {
this.loadingDiskLogs = true;
}
const url = `${this.configService.apiUrl}/opengnsys/system/disk-logs?SYSLOG_IDENTIFIER=${this.identifier}&host=${this.host}&lines=200`;
this.http.get<{ content: string }>(url).subscribe({
next: (response) => {
this.diskLogsContent = response.content || '';
this.loadingDiskLogs = false;
this.errorDiskLogs = null;
},
error: (err) => {
this.errorDiskLogs = 'Error al cargar logs de disco';
this.loadingDiskLogs = false;
}
});
}
private startPolling(): void {
this.stopPolling();
this.pollInterval = setInterval(() => {
if (this.isLive) {
this.loadDiskLogs();
}
}, this.POLL_INTERVAL_MS);
}
private stopPolling(): void {
if (this.pollInterval) {
clearInterval(this.pollInterval);
this.pollInterval = null;
}
}
refreshLogs(): void {
this.loadDiskLogs();
}
private scrollToBottom(): void {
if (this.isLive && this.logContainer) {
try {
this.logContainer.nativeElement.scrollTop = this.logContainer.nativeElement.scrollHeight;
} catch (err) {}
}
}
}

View File

@ -232,6 +232,10 @@
"clientListPartition": "Llistat de clients on es realitzarà el particionat",
"clientLogs": "Registres de client",
"diskLogs": "Registres de disc",
"oglogLogs": "Registres d'oglog",
"realTimeLogs": "Visualització de registres en temps real",
"diskLogsStartLive": "Activar mode en temps real",
"diskLogsStopLive": "Aturar mode en temps real",
"clientMacOS": "Client MacOS",
"clientOff": "Client apagat",
"clientOgLive": "Client OGLive",
@ -1032,6 +1036,7 @@
"ogbootServers": "Servidors OgBoot",
"ogBootServerStatus": "Estat del servidor OgBoot",
"ogcoreLogs": "Logs d'OgCore",
"ogrepositoryLogs": "Logs d'OgRepository",
"ogCoreServer": "Servidor OgCore",
"ogLive": "ogLive",
"ogLiveDeletedSuccessfully": "OG Live eliminat amb èxit",

View File

@ -232,6 +232,8 @@
"clientListPartition": "List of clients where partitioning will be performed",
"clientLogs": "Client Logs",
"diskLogs": "Disk Logs",
"oglogLogs": "oglog Logs",
"realTimeLogs": "Real-time log visualization",
"diskLogsStartLive": "Enable real-time mode",
"diskLogsStopLive": "Stop real-time mode",
"clientMacOS": "MacOS client",
@ -1034,6 +1036,7 @@
"ogbootServers": "OgBoot Servers",
"ogBootServerStatus": "OgBoot Server Status",
"ogcoreLogs": "OgCore Logs",
"ogrepositoryLogs": "OgRepository Logs",
"ogCoreServer": "OgCore Server",
"ogLive": "ogLive",
"ogLiveDeletedSuccessfully": "OG Live deleted successfully",

View File

@ -232,6 +232,8 @@
"clientListPartition": "Listado de clientes donde se realizará el particionado",
"clientLogs": "Logs de cliente",
"diskLogs": "Logs de disco",
"oglogLogs": "Logs de oglog",
"realTimeLogs": "Visualización de logs en tiempo real",
"diskLogsStartLive": "Activar modo en tiempo real",
"diskLogsStopLive": "Detener modo en tiempo real",
"clientMacOS": "Cliente MacOS",
@ -1034,6 +1036,7 @@
"ogbootServers": "Servidores OgBoot",
"ogBootServerStatus": "Estado del servidor OgBoot",
"ogcoreLogs": "Logs de OgCore",
"ogrepositoryLogs": "Logs de OgRepository",
"ogCoreServer": "Servidor OgCore",
"ogLive": "ogLive",
"ogLiveDeletedSuccessfully": "OG Live eliminado con éxito",