Compare commits

...

3 Commits

Author SHA1 Message Date
Manuel Aranda Rosales 2cc10615a1 Merge pull request 'develop' (#31) from develop into main
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
oggui-debian-package/pipeline/head This commit looks good Details
Reviewed-on: #31
2025-06-27 12:46:30 +02:00
Manuel Aranda Rosales f16581e4ed Merge branch 'main' into develop
testing/ogGui-multibranch/pipeline/pr-main Build queued... Details
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-06-27 12:43:55 +02:00
Manuel Aranda Rosales f2e4f5d081 Added logs real time ogLive 2025-06-27 12:43:27 +02:00
9 changed files with 176 additions and 87 deletions

View File

@ -31,6 +31,16 @@
<div *ngIf="!loading" class="main-content">
<!-- Resumen rápido del sistema -->
<div class="system-overview">
<div class="overview-card">
<div class="overview-icon">
<mat-icon>cloud</mat-icon>
</div>
<div class="overview-content">
<h3>Repositorios</h3>
<p>Total: <span class="status-badge info">{{ repositories.length }}</span></p>
</div>
</div>
<div class="overview-card">
<div class="overview-icon">
<mat-icon>storage</mat-icon>
@ -52,73 +62,10 @@
<p *ngIf="errorDhcp">Estado: <span class="status-badge offline">Error</span></p>
</div>
</div>
<div class="overview-card">
<div class="overview-icon">
<mat-icon>cloud</mat-icon>
</div>
<div class="overview-content">
<h3>Repositorios</h3>
<p>Total: <span class="status-badge info">{{ repositories.length }}</span></p>
</div>
</div>
</div>
<!-- Tabs principales -->
<mat-tab-group (selectedTabChange)="onTabChange($event)" class="main-tabs">
<mat-tab label="OgBoot Server">
<div *ngIf="!errorOgBoot && !loadingOgBoot" class="tab-content">
<app-status-tab [loading]="loadingOgBoot" [diskUsage]="ogBootDiskUsage" [servicesStatus]="ogBootServicesStatus"
[installedOgLives]="installedOgLives" [diskUsageChartData]="ogBootDiskUsageChartData" [view]="view"
[colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="isDhcp"
[isRepository]="false">
</app-status-tab>
</div>
<div *ngIf="loadingOgBoot" class="loading-container">
<div class="loading-content">
<mat-spinner class="loading-spinner"></mat-spinner>
<p class="loading-text">Cargando estado de OgBoot...</p>
</div>
</div>
<div *ngIf="errorOgBoot" class="error-container">
<div class="error-card">
<mat-icon class="error-icon">error_outline</mat-icon>
<h3>Error de conexión</h3>
<p>No se pudo conectar con el servidor OgBoot</p>
<button mat-raised-button color="primary" (click)="loadOgBootStatus()">
<mat-icon>refresh</mat-icon>
Reintentar
</button>
</div>
</div>
</mat-tab>
<mat-tab label="DHCP Server">
<div *ngIf="!errorDhcp && !loadingDhcp" class="tab-content">
<app-status-tab [loading]="loadingDhcp" [diskUsage]="dhcpDiskUsage" [servicesStatus]="dhcpServicesStatus"
[subnets]="subnets" [diskUsageChartData]="dhcpDiskUsageChartData" [view]="view" [colorScheme]="colorScheme"
[isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="isDhcp" [isRepository]="false">
</app-status-tab>
</div>
<div *ngIf="loadingDhcp" class="loading-container">
<div class="loading-content">
<mat-spinner class="loading-spinner"></mat-spinner>
<p class="loading-text">Cargando estado de DHCP...</p>
</div>
</div>
<div *ngIf="errorDhcp" class="error-container">
<div class="error-card">
<mat-icon class="error-icon">error_outline</mat-icon>
<h3>Error de conexión</h3>
<p>No se pudo conectar con el servidor DHCP</p>
<button mat-raised-button color="primary" (click)="loadDhcpStatus()">
<mat-icon>refresh</mat-icon>
Reintentar
</button>
</div>
</div>
</mat-tab>
<mat-tab label="{{ 'repositoryLabel' | translate }}">
<div class="repositories-container">
<div *ngIf="repositories.length === 0" class="no-repositories">
@ -170,15 +117,15 @@
<div *ngIf="!selectedRepositoryUuid" class="no-repository-selected">
<mat-icon class="no-data-icon">storage</mat-icon>
<h3>Selecciona un repositorio</h3>
<p>Elige un repositorio del selector para ver su información detallada.</p>
<p>Elige un repositorio de la lista para ver su estado detallado.</p>
</div>
<!-- Error del repositorio seleccionado -->
<!-- Error al cargar repositorio -->
<div *ngIf="selectedRepositoryUuid && errorRepositories[selectedRepositoryUuid]" class="error-container">
<div class="error-card">
<mat-icon class="error-icon">error_outline</mat-icon>
<h3>Error de conexión</h3>
<p>No se pudo conectar con el repositorio {{ getSelectedRepositoryName() }}</p>
<p>No se pudo conectar con el repositorio seleccionado</p>
<button mat-raised-button color="primary" (click)="retryRepositoryStatus(selectedRepositoryUuid)">
<mat-icon>refresh</mat-icon>
Reintentar
@ -188,6 +135,59 @@
</div>
</div>
</mat-tab>
<mat-tab label="OgBoot Server">
<div *ngIf="!errorOgBoot && !loadingOgBoot" class="tab-content">
<app-status-tab [loading]="loadingOgBoot" [diskUsage]="ogBootDiskUsage" [servicesStatus]="ogBootServicesStatus"
[installedOgLives]="installedOgLives" [diskUsageChartData]="ogBootDiskUsageChartData" [view]="view"
[colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="isDhcp"
[isRepository]="false">
</app-status-tab>
</div>
<div *ngIf="loadingOgBoot" class="loading-container">
<div class="loading-content">
<mat-spinner class="loading-spinner"></mat-spinner>
<p class="loading-text">Cargando estado de OgBoot...</p>
</div>
</div>
<div *ngIf="errorOgBoot" class="error-container">
<div class="error-card">
<mat-icon class="error-icon">error_outline</mat-icon>
<h3>Error de conexión</h3>
<p>No se pudo conectar con el servidor OgBoot</p>
<button mat-raised-button color="primary" (click)="loadOgBootStatus()">
<mat-icon>refresh</mat-icon>
Reintentar
</button>
</div>
</div>
</mat-tab>
<mat-tab label="DHCP Server">
<div *ngIf="!errorDhcp && !loadingDhcp" class="tab-content">
<app-status-tab [loading]="loadingDhcp" [diskUsage]="dhcpDiskUsage" [servicesStatus]="dhcpServicesStatus"
[subnets]="subnets" [diskUsageChartData]="dhcpDiskUsageChartData" [view]="view" [colorScheme]="colorScheme"
[isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="true" [isRepository]="false">
</app-status-tab>
</div>
<div *ngIf="loadingDhcp" class="loading-container">
<div class="loading-content">
<mat-spinner class="loading-spinner"></mat-spinner>
<p class="loading-text">Cargando estado de DHCP...</p>
</div>
</div>
<div *ngIf="errorDhcp" class="error-container">
<div class="error-card">
<mat-icon class="error-icon">error_outline</mat-icon>
<h3>Error de conexión</h3>
<p>No se pudo conectar con el servidor DHCP</p>
<button mat-raised-button color="primary" (click)="loadDhcpStatus()">
<mat-icon>refresh</mat-icon>
Reintentar
</button>
</div>
</div>
</mat-tab>
</mat-tab-group>
</div>
</mat-dialog-content>

View File

@ -293,16 +293,16 @@ export class GlobalStatusComponent implements OnInit {
onTabChange(event: MatTabChangeEvent): void {
switch (event.index) {
case 0:
this.loadOgBootStatus();
break;
case 1:
this.loadDhcpStatus();
break;
case 2:
if (this.repositories.length === 0) {
this.loadRepositories(false);
}
break;
case 1:
this.loadOgBootStatus();
break;
case 2:
this.loadDhcpStatus();
break;
default:
break;
}

View File

@ -344,7 +344,7 @@ export class DeployImageComponent implements OnInit{
diskNumber: this.selectedPartition.diskNumber,
partitionNumber: this.selectedPartition.partitionNumber,
p2pMode: this.p2pMode,
p2pTime: this.p2pMode === 'seeder' ? this.p2pTime : null,
p2pTime: this.p2pMode === 'seeder' ? this.p2pTime : 0,
mcastIp: this.mcastIp,
mcastPort: this.mcastPort,
mcastMode: this.mcastMode,

View File

@ -418,6 +418,10 @@
<mat-icon>pending_actions</mat-icon>
<span>{{ 'colaAcciones' | translate }}</span>
</button>
<button mat-menu-item *ngIf="client.status === 'og-live'" (click)="openClientLogsInNewTab($event, client)">
<mat-icon>article</mat-icon>
<span>Logs en tiempo real</span>
</button>
<button mat-menu-item (click)="onDeleteClick($event, client)" *ngIf="auth.userCategory !== 'ou-minimal'">
<mat-icon>delete</mat-icon>
<span>{{ 'delete' | translate }}</span>
@ -605,6 +609,10 @@
<mat-icon>pending_actions</mat-icon>
<span>{{ 'colaAcciones' | translate }}</span>
</button>
<button mat-menu-item *ngIf="client.status === 'og-live'" (click)="openClientLogsInNewTab($event, client)">
<mat-icon>article</mat-icon>
<span>Logs en tiempo real</span>
</button>
<button mat-menu-item (click)="onDeleteClick($event, client)" *ngIf="auth.userCategory !== 'ou-minimal'">
<mat-icon>delete</mat-icon>
<span>{{ 'delete' | translate }}</span>

View File

@ -961,20 +961,38 @@ export class GroupsComponent implements OnInit, OnDestroy {
}
openClientPendingTasks(event: MouseEvent, client: Client): void {
this.loading = true;
event.stopPropagation();
const dialogRef = this.dialog.open(ClientPendingTasksComponent, {
width: '1200px',
data: { client },
disableClose: true,
hasBackdrop: true,
backdropClass: 'non-clickable-backdrop',
})
dialogRef.afterClosed().subscribe((result) => {
this.loading = false;
width: '90vw',
height: '80vh',
data: {
client: client,
parentNode: this.selectedNode
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.refreshClientData();
}
});
}
openClientLogsInNewTab(event: MouseEvent, client: Client): void {
event.stopPropagation();
if (client.ip) {
const logsUrl = `${this.baseUrl}/pcclients/${client.ip}/cgi-bin/httpd-log.sh`;
const windowName = `logs_${client.ip.replace(/\./g, '_')}`;
const newWindow = window.open(logsUrl, windowName);
if (newWindow) {
newWindow.document.write(`
<title>Logs - ${client.ip}</title>
<iframe src="${logsUrl}" width="100%" height="100%" style="border:none;"></iframe>
`);
}
} else {
this.toastr.error('No se puede acceder a los logs: IP del cliente no disponible', 'Error');
}
}
openOUPendingTasks(event: MouseEvent, node: any): void {

View File

@ -61,6 +61,60 @@
gap: 15px;
margin: 20px 0;
padding: 0 10px;
position: relative;
}
.stats-hint {
position: absolute;
top: -8px;
right: 10px;
z-index: 10;
}
.stats-hint mat-icon {
font-size: 20px;
width: 20px;
height: 20px;
color: #667eea;
opacity: 0.7;
cursor: help;
transition: opacity 0.3s ease;
}
.stats-hint mat-icon:hover {
opacity: 1;
}
.stats-info-message {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 8px;
margin-bottom: 15px;
font-size: 0.9rem;
font-weight: 500;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
animation: fadeInUp 0.5s ease-out;
}
.stats-info-message mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.stat-card {

View File

@ -385,7 +385,14 @@ export class TaskLogsComponent implements OnInit, OnDestroy {
openLogsInNewTab(trace: any): void {
if (trace.client?.ip) {
const logsUrl = `${this.baseUrl}/pcclients/${trace.client.ip}/cgi-bin/httpd-log.sh`;
window.open(logsUrl, '_blank');
const windowName = `logs_${trace.client.ip.replace(/\./g, '_')}`;
const newWindow = window.open(logsUrl, windowName);
if (newWindow) {
newWindow.document.write(`
<title>Logs - ${trace.client.ip}</title>
<iframe src="${logsUrl}" width="100%" height="100%" style="border:none;"></iframe>
`);
}
} else {
this.toastService.error('No se puede acceder a los logs: IP del cliente no disponible', 'Error');
}

View File

@ -580,5 +580,6 @@
"offline": "Offline",
"online": "Online",
"busy": "Busy",
"cancelTask": "Cancel task"
"cancelTask": "Cancel task",
"clickStatsToFilter": "Click on the statistics cards to filter the traces"
}

View File

@ -585,6 +585,7 @@
"offline": "Offline",
"online": "Online",
"busy": "Ocupado",
"cancelTask": "Cancelar tarea"
"cancelTask": "Cancelar tarea",
"clickStatsToFilter": "Haz clic en las tarjetas de estadísticas para filtrar las trazas"
}