refs 2337. Global status new UX
testing/ogGui-multibranch/pipeline/head There was a failure building this commit
Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit
Details
parent
5b8dba4835
commit
3e8f8cc3db
|
@ -1,44 +1,557 @@
|
|||
/* ===== HEADER DE BIENVENIDA ===== */
|
||||
.welcome-header {
|
||||
background: #3f51b5;
|
||||
color: white;
|
||||
padding: 2rem 2rem 1.5rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.welcome-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="white" opacity="0.1"/><circle cx="75" cy="75" r="1" fill="white" opacity="0.1"/><circle cx="50" cy="10" r="0.5" fill="white" opacity="0.1"/><circle cx="10" cy="60" r="0.5" fill="white" opacity="0.1"/><circle cx="90" cy="40" r="0.5" fill="white" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.welcome-icon {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.welcome-icon mat-icon {
|
||||
font-size: 32px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.welcome-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.welcome-subtitle {
|
||||
margin: 0 0 0.25rem 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.welcome-description {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.welcome-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.help-button,
|
||||
.refresh-button {
|
||||
background: rgba(255, 255, 255, 0.2) !important;
|
||||
color: white !important;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.help-button:hover,
|
||||
.refresh-button:hover {
|
||||
background: rgba(255, 255, 255, 0.3) !important;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* ===== CONTENIDO PRINCIPAL ===== */
|
||||
mat-dialog-content {
|
||||
height: calc(100% - 64px);
|
||||
overflow: auto;
|
||||
padding-top: 0.5em !important;
|
||||
height: calc(100% - 200px);
|
||||
overflow: auto;
|
||||
padding: 0 !important;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.action-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1em;
|
||||
padding: 1.5em;
|
||||
.main-content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* ===== SPINNER DE CARGA ===== */
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1000;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
width: 60px !important;
|
||||
height: 60px !important;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ===== RESUMEN DEL SISTEMA ===== */
|
||||
.system-overview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.overview-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.overview-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.overview-card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.overview-icon {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.overview-icon mat-icon {
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.overview-content h3 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.overview-content p {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* ===== BADGES DE ESTADO ===== */
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.status-badge.online {
|
||||
background: linear-gradient(135deg, #28a745, #20c997);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-badge.offline {
|
||||
background: linear-gradient(135deg, #dc3545, #c82333);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-badge.info {
|
||||
background: linear-gradient(135deg, #17a2b8, #138496);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* ===== TABS PRINCIPALES ===== */
|
||||
.main-tabs {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
::ng-deep .main-tabs .mat-tab-header {
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
::ng-deep .main-tabs .mat-tab-label {
|
||||
font-weight: 600;
|
||||
color: #6c757d;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
::ng-deep .main-tabs .mat-tab-label-active {
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
::ng-deep .main-tabs .mat-ink-bar {
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* ===== CONTENEDOR DE ERRORES ===== */
|
||||
.error-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 300px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.error-card {
|
||||
margin: 20px auto;
|
||||
max-width: 600px;
|
||||
text-align: center;
|
||||
background-color: rgb(243, 243, 243);
|
||||
color: rgb(48, 48, 48);
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 3rem 2rem;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #e9ecef;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 64px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
color: #dc3545;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.error-card h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.error-card p {
|
||||
margin-top: 0;
|
||||
margin: 0 0 2rem 0;
|
||||
color: #6c757d;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ===== REPOSITORIOS ===== */
|
||||
.repositories-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.repositories-selector-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.repository-selector {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.repository-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.repository-select-field {
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.selected-repository-content {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
.no-repository-selected {
|
||||
text-align: center;
|
||||
padding: 3rem 2rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.no-repository-selected .no-data-icon {
|
||||
font-size: 4rem;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.no-repository-selected h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.no-repository-selected p {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ===== FOOTER CON ACCIONES ===== */
|
||||
.action-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem 2rem;
|
||||
background: white;
|
||||
border-top: 1px solid #e9ecef;
|
||||
border-radius: 0 0 12px 12px;
|
||||
}
|
||||
|
||||
.action-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.last-update {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
color: #6c757d !important;
|
||||
border: 1px solid #dee2e6 !important;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.secondary-button:hover {
|
||||
background: #f8f9fa !important;
|
||||
border-color: #667eea !important;
|
||||
color: #667eea !important;
|
||||
}
|
||||
|
||||
/* ===== RESPONSIVE ===== */
|
||||
@media (max-width: 768px) {
|
||||
.welcome-header {
|
||||
padding: 1.5rem 1rem 1rem 1rem;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.welcome-subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.system-overview {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.action-container {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.welcome-header {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.welcome-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.welcome-icon mat-icon {
|
||||
font-size: 28px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.repository-select-field {
|
||||
min-width: 250px;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== ANIMACIONES ===== */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.system-overview,
|
||||
.main-tabs {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
.overview-card:nth-child(1) { animation-delay: 0.1s; }
|
||||
.overview-card:nth-child(2) { animation-delay: 0.2s; }
|
||||
.overview-card:nth-child(3) { animation-delay: 0.3s; }
|
||||
|
||||
/* ===== ESTILOS GLOBALES DEL DIALOG ===== */
|
||||
::ng-deep .mat-dialog-container {
|
||||
border-radius: 16px !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-dialog-content {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* ===== LOADING INDIVIDUAL POR TAB ===== */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 300px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.loading-container .loading-content {
|
||||
text-align: center;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.loading-container .loading-spinner {
|
||||
width: 60px !important;
|
||||
height: 60px !important;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.loading-container .loading-text {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
|
@ -1,68 +1,209 @@
|
|||
<header>
|
||||
<h1 mat-dialog-title>{{'GlobalStatus' | translate}}</h1>
|
||||
</header>
|
||||
<mat-dialog-content [ngClass]="{'loading': loading}">
|
||||
<div class="spinner-container" *ngIf="loading">
|
||||
<mat-spinner class="loading-spinner"></mat-spinner>
|
||||
<!-- Header con bienvenida -->
|
||||
<div class="welcome-header">
|
||||
<div class="welcome-content">
|
||||
<div class="welcome-icon">
|
||||
<mat-icon>dashboard</mat-icon>
|
||||
</div>
|
||||
<div class="welcome-text">
|
||||
<h1 class="welcome-title">{{ 'GlobalStatus' | translate }}</h1>
|
||||
<p class="welcome-subtitle">Bienvenido a la consola de administración de OpenGnsys</p>
|
||||
<p class="welcome-description">Estado general de todos los componentes de la plataforma</p>
|
||||
</div>
|
||||
</div>
|
||||
<mat-tab-group (selectedTabChange)="onTabChange($event)">
|
||||
<mat-tab label="OgBoot">
|
||||
<div *ngIf="!loading && !errorOgBoot" class="content-container">
|
||||
<app-status-tab [loading]="loading" [diskUsage]="ogBootDiskUsage" [servicesStatus]="ogBootServicesStatus"
|
||||
[installedOgLives]="installedOgLives" [diskUsageChartData]="ogBootDiskUsageChartData" [view]="view"
|
||||
[colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="isDhcp"
|
||||
[isRepository]="false">
|
||||
</app-status-tab>
|
||||
</div>
|
||||
<mat-card *ngIf="!loading && errorOgBoot" class="error-card">
|
||||
<mat-card-content>
|
||||
<p>{{ 'errorLoadingData' | translate }}</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</mat-tab>
|
||||
<div class="welcome-actions">
|
||||
<button mat-icon-button class="refresh-button" (click)="refreshAll()" matTooltip="Actualizar datos">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-tab label="Dhcp">
|
||||
<div *ngIf="!loading && !errorDhcp" class="content-container">
|
||||
<app-status-tab [loading]="loading" [diskUsage]="dhcpDiskUsage" [servicesStatus]="dhcpServicesStatus"
|
||||
[subnets]="subnets" [diskUsageChartData]="dhcpDiskUsageChartData" [view]="view" [colorScheme]="colorScheme"
|
||||
[isDoughnut]="isDoughnut" [showLabels]="showLabels" [isDhcp]="isDhcp" [isRepository]="false">
|
||||
</app-status-tab>
|
||||
</div>
|
||||
<mat-card *ngIf="!loading && errorDhcp" class="error-card">
|
||||
<mat-card-content>
|
||||
<p>{{ 'errorLoadingData' | translate }}</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</mat-tab>
|
||||
<!-- Contenido principal -->
|
||||
<mat-dialog-content [ngClass]="{'loading': loading}">
|
||||
<!-- Spinner de carga -->
|
||||
<div class="spinner-container" *ngIf="loading">
|
||||
<div class="loading-content">
|
||||
<mat-spinner class="loading-spinner"></mat-spinner>
|
||||
<p class="loading-text">Cargando estado del sistema...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-tab label="{{ 'repositoryLabel' | translate }}">
|
||||
<mat-tab-group>
|
||||
<mat-tab *ngFor="let repository of repositories" [label]="repository.name">
|
||||
<div *ngIf="!loading && !errorRepositories[repository.uuid] && repositoryStatuses[repository.uuid]">
|
||||
<app-status-tab [loading]="loading" [diskUsage]="repositoryStatuses[repository.uuid].disk"
|
||||
[servicesStatus]="repositoryStatuses[repository.uuid].services"
|
||||
[processesStatus]="repositoryStatuses[repository.uuid].processes"
|
||||
[ramUsage]="repositoryStatuses[repository.uuid].ram" [cpuUsage]="repositoryStatuses[repository.uuid].cpu"
|
||||
[diskUsageChartData]="[
|
||||
{ name: 'Usado', value: repositoryStatuses[repository.uuid].disk.used },
|
||||
{ name: 'Disponible', value: repositoryStatuses[repository.uuid].disk.available }
|
||||
]" [ramUsageChartData]="[
|
||||
{ name: 'Usado', value: repositoryStatuses[repository.uuid].ram.used },
|
||||
{ name: 'Disponible', value: repositoryStatuses[repository.uuid].ram.available }
|
||||
]" [view]="view" [colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels"
|
||||
[isDhcp]="false" [isRepository]="true">
|
||||
</app-status-tab>
|
||||
<!-- Contenido principal cuando no está cargando -->
|
||||
<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>storage</mat-icon>
|
||||
</div>
|
||||
<div class="overview-content">
|
||||
<h3>OgBoot Server</h3>
|
||||
<p *ngIf="!errorOgBoot">Estado: <span class="status-badge online">Operativo</span></p>
|
||||
<p *ngIf="errorOgBoot">Estado: <span class="status-badge offline">Error</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overview-card">
|
||||
<div class="overview-icon">
|
||||
<mat-icon>router</mat-icon>
|
||||
</div>
|
||||
<div class="overview-content">
|
||||
<h3>DHCP Server</h3>
|
||||
<p *ngIf="!errorDhcp">Estado: <span class="status-badge online">Operativo</span></p>
|
||||
<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>
|
||||
<mat-card *ngIf="!loading && errorRepositories[repository.uuid]" class="error-card">
|
||||
<mat-card-content>
|
||||
<p>{{ 'errorLoadingData' | translate }}</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</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">
|
||||
<mat-icon class="no-data-icon">cloud_off</mat-icon>
|
||||
<h3>No hay repositorios disponibles</h3>
|
||||
<p>No se encontraron repositorios configurados en el sistema.</p>
|
||||
</div>
|
||||
|
||||
<div *ngIf="repositories.length > 0" class="repositories-selector-container">
|
||||
<!-- Selector de repositorio -->
|
||||
<div class="repository-selector">
|
||||
<mat-form-field appearance="outline" class="repository-select-field">
|
||||
<mat-label>Seleccionar repositorio</mat-label>
|
||||
<mat-select [(ngModel)]="selectedRepositoryUuid" (selectionChange)="onRepositoryChange($event.value)">
|
||||
<mat-option *ngFor="let repository of repositories" [value]="repository.uuid">
|
||||
{{ repository.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-icon matSuffix>storage</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Información del repositorio seleccionado -->
|
||||
<div *ngIf="selectedRepositoryUuid && !errorRepositories[selectedRepositoryUuid] && repositoryStatuses[selectedRepositoryUuid]" class="selected-repository-content">
|
||||
<div class="repository-item">
|
||||
<div class="repository-header">
|
||||
<h3>{{ getSelectedRepositoryName() }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="repository-content">
|
||||
<app-status-tab [loading]="loading" [diskUsage]="repositoryStatuses[selectedRepositoryUuid].disk"
|
||||
[servicesStatus]="repositoryStatuses[selectedRepositoryUuid].services"
|
||||
[processesStatus]="repositoryStatuses[selectedRepositoryUuid].processes"
|
||||
[ramUsage]="repositoryStatuses[selectedRepositoryUuid].ram" [cpuUsage]="repositoryStatuses[selectedRepositoryUuid].cpu"
|
||||
[diskUsageChartData]="[
|
||||
{ name: 'Usado', value: repositoryStatuses[selectedRepositoryUuid].disk.used },
|
||||
{ name: 'Disponible', value: repositoryStatuses[selectedRepositoryUuid].disk.available }
|
||||
]" [ramUsageChartData]="[
|
||||
{ name: 'Usado', value: repositoryStatuses[selectedRepositoryUuid].ram.used },
|
||||
{ name: 'Disponible', value: repositoryStatuses[selectedRepositoryUuid].ram.available }
|
||||
]" [view]="view" [colorScheme]="colorScheme" [isDoughnut]="isDoughnut" [showLabels]="showLabels"
|
||||
[isDhcp]="false" [isRepository]="true">
|
||||
</app-status-tab>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mensaje cuando no hay repositorio seleccionado -->
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Error del repositorio seleccionado -->
|
||||
<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>
|
||||
<button mat-raised-button color="primary" (click)="retryRepositoryStatus(selectedRepositoryUuid)">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
Reintentar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<!-- Footer con acciones -->
|
||||
<mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" [mat-dialog-close]="true">{{ 'closeButton' | translate }}</button>
|
||||
<div class="action-info">
|
||||
<p class="last-update">Última actualización: {{ lastUpdateTime }}</p>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button mat-button class="secondary-button" (click)="refreshAll()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
Actualizar
|
||||
</button>
|
||||
<button mat-raised-button color="primary" [mat-dialog-close]="true">
|
||||
{{ 'closeButton' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</mat-dialog-actions>
|
|
@ -12,6 +12,9 @@ import {ToastrService} from "ngx-toastr";
|
|||
export class GlobalStatusComponent implements OnInit {
|
||||
baseUrl: string;
|
||||
loading: boolean = false;
|
||||
loadingOgBoot: boolean = false;
|
||||
loadingDhcp: boolean = false;
|
||||
loadingRepositories: boolean = false;
|
||||
errorOgBoot: boolean = false;
|
||||
errorDhcp: boolean = false;
|
||||
errorRepositories: { [key: string]: boolean } = {};
|
||||
|
@ -26,6 +29,8 @@ export class GlobalStatusComponent implements OnInit {
|
|||
repositoriesUrl: string;
|
||||
repositories: any[] = [];
|
||||
repositoryStatuses: { [key: string]: any } = {};
|
||||
lastUpdateTime: string = '';
|
||||
selectedRepositoryUuid: string = '';
|
||||
|
||||
ogBootApiUrl: string;
|
||||
ogBootDiskUsage: any = {};
|
||||
|
@ -48,13 +53,21 @@ export class GlobalStatusComponent implements OnInit {
|
|||
this.ogBootApiUrl = `${this.baseUrl}/og-boot/status`;
|
||||
this.dhcpApiUrl = `${this.baseUrl}/og-dhcp/status`;
|
||||
this.repositoriesUrl = `${this.baseUrl}/image-repositories`;
|
||||
|
||||
this.ogBootDiskUsageChartData = [];
|
||||
this.dhcpDiskUsageChartData = [];
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.updateLastUpdateTime();
|
||||
|
||||
this.loadOgBootStatus();
|
||||
this.syncSubnets()
|
||||
this.syncTemplates()
|
||||
this.syncOgLives()
|
||||
this.loadDhcpStatus();
|
||||
this.loadRepositories(false);
|
||||
|
||||
this.syncSubnets();
|
||||
this.syncTemplates();
|
||||
this.syncOgLives();
|
||||
}
|
||||
|
||||
syncSubnets() {
|
||||
|
@ -106,11 +119,26 @@ export class GlobalStatusComponent implements OnInit {
|
|||
|
||||
[key: string]: any;
|
||||
|
||||
loadStatus(apiUrl: string, diskUsage: any, servicesStatus: any, diskUsageChartData: any[], installedOgLives: any[], isDhcp: boolean, errorState: string): void {
|
||||
this.loading = true;
|
||||
loadStatus(apiUrl: string, diskUsage: any, servicesStatus: any, diskUsageChartData: any[], installedOgLives: any[], isDhcp: boolean, errorState: string, showLoading: boolean = true): void {
|
||||
if (isDhcp) {
|
||||
this.loadingDhcp = true;
|
||||
} else {
|
||||
this.loadingOgBoot = true;
|
||||
}
|
||||
|
||||
if (showLoading) {
|
||||
this.loading = true;
|
||||
}
|
||||
this[errorState] = false;
|
||||
const timeoutId = setTimeout(() => {
|
||||
this.loading = false;
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
if (isDhcp) {
|
||||
this.loadingDhcp = false;
|
||||
} else {
|
||||
this.loadingOgBoot = false;
|
||||
}
|
||||
this[errorState] = true;
|
||||
}, 3500);
|
||||
this.http.get<any>(apiUrl).subscribe({
|
||||
|
@ -140,23 +168,41 @@ export class GlobalStatusComponent implements OnInit {
|
|||
{ name: 'Disponible', value: parseFloat(diskUsage.available) }
|
||||
);
|
||||
|
||||
this.loading = false;
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
if (isDhcp) {
|
||||
this.loadingDhcp = false;
|
||||
} else {
|
||||
this.loadingOgBoot = false;
|
||||
}
|
||||
clearTimeout(timeoutId);
|
||||
},
|
||||
error: error => {
|
||||
this.toastService.error(error.error['hydra:description'] || 'Error al cargar el estado de ogBoot');
|
||||
this.loading = false;
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
if (isDhcp) {
|
||||
this.loadingDhcp = false;
|
||||
} else {
|
||||
this.loadingOgBoot = false;
|
||||
}
|
||||
this[errorState] = true;
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadRepositories(): void {
|
||||
this.loading = true;
|
||||
loadRepositories(showLoading: boolean = true): void {
|
||||
if (showLoading) {
|
||||
this.loading = true;
|
||||
}
|
||||
this.errorRepositories = {};
|
||||
const timeoutId = setTimeout(() => {
|
||||
this.loading = false;
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
this.repositories.forEach(repository => {
|
||||
if (!(repository.uuid in this.errorRepositories)) {
|
||||
this.errorRepositories[repository.uuid] = true;
|
||||
|
@ -176,14 +222,15 @@ export class GlobalStatusComponent implements OnInit {
|
|||
this.errorRepositories[repository.uuid] = errorOccurred;
|
||||
|
||||
if (remainingRepositories === 0) {
|
||||
this.loading = false;
|
||||
if (showLoading) {
|
||||
this.loading = false;
|
||||
}
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching repositories', error);
|
||||
this.loading = false;
|
||||
this.repositories.forEach(repository => {
|
||||
this.errorRepositories[repository.uuid] = true;
|
||||
|
@ -226,23 +273,86 @@ export class GlobalStatusComponent implements OnInit {
|
|||
|
||||
loadOgBootStatus(): void {
|
||||
this.isDhcp = false;
|
||||
this.loadStatus(this.ogBootApiUrl, this.ogBootDiskUsage, this.ogBootServicesStatus, this.ogBootDiskUsageChartData, this.installedOgLives, this.isDhcp, 'errorOgBoot');
|
||||
this.loadStatus(this.ogBootApiUrl, this.ogBootDiskUsage, this.ogBootServicesStatus, this.ogBootDiskUsageChartData, this.installedOgLives, this.isDhcp, 'errorOgBoot', false);
|
||||
}
|
||||
|
||||
loadDhcpStatus(): void {
|
||||
this.isDhcp = true;
|
||||
this.loadStatus(this.dhcpApiUrl, this.dhcpDiskUsage, this.dhcpServicesStatus, this.dhcpDiskUsageChartData, this.installedOgLives, this.isDhcp, 'errorDhcp');
|
||||
this.loadStatus(this.dhcpApiUrl, this.dhcpDiskUsage, this.dhcpServicesStatus, this.dhcpDiskUsageChartData, this.installedOgLives, this.isDhcp, 'errorDhcp', false);
|
||||
}
|
||||
|
||||
onTabChange(event: MatTabChangeEvent): void {
|
||||
if (event.tab.textLabel === 'OgBoot') {
|
||||
this.loadOgBootStatus();
|
||||
} else if (event.tab.textLabel === 'Dhcp') {
|
||||
this.loadDhcpStatus();
|
||||
} else if (event.tab.textLabel === 'Repositorios') {
|
||||
if (this.repositories.length === 0) {
|
||||
this.loadRepositories();
|
||||
}
|
||||
switch (event.index) {
|
||||
case 0:
|
||||
this.loadOgBootStatus();
|
||||
break;
|
||||
case 1:
|
||||
this.loadDhcpStatus();
|
||||
break;
|
||||
case 2:
|
||||
if (this.repositories.length === 0) {
|
||||
this.loadRepositories(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onRepositoryChange(repositoryUuid: string): void {
|
||||
this.selectedRepositoryUuid = repositoryUuid;
|
||||
if (repositoryUuid && !this.repositoryStatuses[repositoryUuid]) {
|
||||
this.loadRepositoryStatus(repositoryUuid, (errorOccurred: boolean) => {
|
||||
if (errorOccurred) {
|
||||
this.errorRepositories[repositoryUuid] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedRepositoryName(): string {
|
||||
const selectedRepo = this.repositories.find(repo => repo.uuid === this.selectedRepositoryUuid);
|
||||
return selectedRepo ? selectedRepo.name : '';
|
||||
}
|
||||
|
||||
refreshAll(): void {
|
||||
this.loading = true;
|
||||
this.updateLastUpdateTime();
|
||||
this.loadStatus(this.ogBootApiUrl, this.ogBootDiskUsage, this.ogBootServicesStatus, this.ogBootDiskUsageChartData, this.installedOgLives, false, 'errorOgBoot', true);
|
||||
this.loadStatus(this.dhcpApiUrl, this.dhcpDiskUsage, this.dhcpServicesStatus, this.dhcpDiskUsageChartData, this.installedOgLives, true, 'errorDhcp', true);
|
||||
this.loadRepositories(true);
|
||||
this.syncSubnets();
|
||||
this.syncTemplates();
|
||||
this.syncOgLives();
|
||||
this.toastService.success('Datos actualizados correctamente');
|
||||
}
|
||||
|
||||
updateLastUpdateTime(): void {
|
||||
const now = new Date();
|
||||
this.lastUpdateTime = now.toLocaleTimeString('es-ES', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentTime(): string {
|
||||
const now = new Date();
|
||||
return now.toLocaleTimeString('es-ES', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
retryRepositoryStatus(repositoryUuid: string): void {
|
||||
this.loadRepositoryStatus(repositoryUuid, (errorOccurred: boolean) => {
|
||||
this.errorRepositories[repositoryUuid] = errorOccurred;
|
||||
if (!errorOccurred) {
|
||||
this.toastService.success('Estado del repositorio actualizado correctamente');
|
||||
} else {
|
||||
this.toastService.error('Error al cargar el estado del repositorio');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,84 +1,533 @@
|
|||
/* ===== LAYOUT PRINCIPAL ===== */
|
||||
.dashboard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.disk-usage-container,
|
||||
.ram-usage-container {
|
||||
/* ===== SECCIÓN DE RECURSOS ===== */
|
||||
.resources-section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* Layout específico para repositorios - gráficas en una sola fila */
|
||||
.resources-section.repository-layout {
|
||||
grid-template-columns: 1fr !important;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.resources-section.repository-layout .resource-card {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.resource-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.resource-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.resource-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.resource-card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.resource-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.disk-usage,
|
||||
.ram-usage {
|
||||
.resource-icon {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.resource-icon mat-icon {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.resource-title {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.resource-content {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.service-list,
|
||||
.process-list {
|
||||
margin-top: 0em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.services-status,
|
||||
.processes-status {
|
||||
.resource-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.services-status li {
|
||||
margin: 5px 0;
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 0.9rem;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.usage-percentage {
|
||||
color: #667eea;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ===== CPU USAGE DISPLAY ===== */
|
||||
.cpu-usage-display {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cpu-circle {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 50%;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
text-align: center;
|
||||
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.cpu-percentage {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.cpu-label {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* ===== SECCIÓN DE SERVICIOS ===== */
|
||||
.services-section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.service-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.service-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.processes-status li {
|
||||
margin: 5px 0;
|
||||
.service-icon {
|
||||
background: linear-gradient(135deg, #28a745, #20c997);
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-led {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
.service-icon mat-icon {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.service-title {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.service-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.service-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.service-item:hover {
|
||||
background: #e9ecef;
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.service-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.status-led.active {
|
||||
background-color: green;
|
||||
.status-indicator.active {
|
||||
background: linear-gradient(135deg, #28a745, #20c997);
|
||||
box-shadow: 0 0 8px rgba(40, 167, 69, 0.4);
|
||||
}
|
||||
|
||||
.status-led.inactive {
|
||||
background-color: red;
|
||||
.status-indicator.inactive {
|
||||
background: linear-gradient(135deg, #dc3545, #c82333);
|
||||
box-shadow: 0 0 8px rgba(220, 53, 69, 0.4);
|
||||
}
|
||||
|
||||
.disk-title,
|
||||
.ram-title {
|
||||
margin-bottom: 0px;
|
||||
.service-name {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.service-title,
|
||||
.process-title {
|
||||
margin-top: 0px;
|
||||
.service-state {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
table {
|
||||
.service-state.active {
|
||||
background: linear-gradient(135deg, #28a745, #20c997);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.service-state.inactive {
|
||||
background: linear-gradient(135deg, #dc3545, #c82333);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* ===== SECCIÓN DE DATOS ===== */
|
||||
.data-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.data-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.data-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.data-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.data-icon {
|
||||
background: linear-gradient(135deg, #17a2b8, #138496);
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.data-icon mat-icon {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.data-title {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: white;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
.data-table th {
|
||||
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||||
color: #495057;
|
||||
padding: 1rem;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f4f4f4;
|
||||
.data-table td {
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
color: #2c3e50;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.data-table tr:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.data-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* ===== RESPONSIVE ===== */
|
||||
@media (max-width: 768px) {
|
||||
.dashboard {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.resources-section {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.services-section {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.resource-content {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.resource-card,
|
||||
.service-card,
|
||||
.data-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.cpu-circle {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.cpu-percentage {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.service-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.service-item:hover {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.resource-header,
|
||||
.service-header,
|
||||
.data-header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.resource-icon,
|
||||
.service-icon,
|
||||
.data-icon {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.resource-icon mat-icon,
|
||||
.service-icon mat-icon,
|
||||
.data-icon mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.resource-title,
|
||||
.service-title,
|
||||
.data-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.data-table th,
|
||||
.data-table td {
|
||||
padding: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== ANIMACIONES ===== */
|
||||
@keyframes slideInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.resource-card,
|
||||
.service-card,
|
||||
.data-card {
|
||||
animation: slideInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
.resource-card:nth-child(1) { animation-delay: 0.1s; }
|
||||
.resource-card:nth-child(2) { animation-delay: 0.2s; }
|
||||
.resource-card:nth-child(3) { animation-delay: 0.3s; }
|
||||
|
||||
.service-card:nth-child(1) { animation-delay: 0.4s; }
|
||||
.service-card:nth-child(2) { animation-delay: 0.5s; }
|
||||
|
||||
.data-card { animation-delay: 0.6s; }
|
||||
|
||||
/* ===== ESTILOS PARA LOS GRÁFICOS ===== */
|
||||
::ng-deep .chart-container ngx-charts-pie-chart {
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
::ng-deep .chart-container ngx-charts-pie-chart .chart-legend {
|
||||
position: relative !important;
|
||||
top: auto !important;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
bottom: auto !important;
|
||||
display: block !important;
|
||||
margin-top: 1rem !important;
|
||||
}
|
||||
|
||||
::ng-deep .chart-container ngx-charts-pie-chart .chart-legend .legend-labels {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
::ng-deep .chart-container ngx-charts-pie-chart .chart-legend .legend-label {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
margin: 0.25rem 0 !important;
|
||||
}
|
|
@ -1,113 +1,206 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div *ngIf="!loading" class="dashboard">
|
||||
<!-- Disk Usage Section -->
|
||||
<div class="disk-usage-container">
|
||||
<h3 class="disk-title">{{ 'diskUsageTitle' | translate }}</h3>
|
||||
<div class="disk-usage" joyrideStep="diskUsageStep" text="{{ 'diskUsageDescription' | translate }}">
|
||||
<ngx-charts-pie-chart [view]="view" [scheme]="colorScheme" [results]="diskUsageChartData" [doughnut]="isDoughnut"
|
||||
[labels]="showLabels">
|
||||
</ngx-charts-pie-chart>
|
||||
<div class="disk-usage-info">
|
||||
<p>{{ 'totalLabel' | translate }}: <strong>{{ isRepository ? diskUsage.total : formatBytes(diskUsage.total) }}</strong></p>
|
||||
<p>{{ 'usedLabel' | translate }}: <strong>{{ isRepository ? diskUsage.used : formatBytes(diskUsage.used) }}</strong></p>
|
||||
<p>{{ 'availableLabel' | translate }}: <strong>{{ isRepository ? diskUsage.available : formatBytes(diskUsage.available) }}</strong></p>
|
||||
<p>{{ 'usedPercentageLabel' | translate }}: <strong>{{ isRepository ? diskUsage.used_percentage : diskUsage.percentage }}</strong></p>
|
||||
<!-- Sección de uso de recursos -->
|
||||
<div class="resources-section" [ngClass]="{'repository-layout': isRepository}">
|
||||
<!-- Disk Usage Section -->
|
||||
<div class="resource-card disk-usage-container">
|
||||
<div class="resource-header">
|
||||
<div class="resource-icon">
|
||||
<mat-icon>storage</mat-icon>
|
||||
</div>
|
||||
<h3 class="resource-title">{{ 'diskUsageTitle' | translate }}</h3>
|
||||
</div>
|
||||
<div class="resource-content">
|
||||
<div class="chart-container" joyrideStep="diskUsageStep" text="{{ 'diskUsageDescription' | translate }}">
|
||||
<ngx-charts-pie-chart [view]="view" [scheme]="colorScheme" [results]="diskUsageChartData" [doughnut]="isDoughnut"
|
||||
[labels]="showLabels">
|
||||
</ngx-charts-pie-chart>
|
||||
</div>
|
||||
<div class="resource-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'totalLabel' | translate }}:</span>
|
||||
<span class="info-value">{{ isRepository ? diskUsage.total : formatBytes(diskUsage.total) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'usedLabel' | translate }}:</span>
|
||||
<span class="info-value">{{ isRepository ? diskUsage.used : formatBytes(diskUsage.used) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'availableLabel' | translate }}:</span>
|
||||
<span class="info-value">{{ isRepository ? diskUsage.available : formatBytes(diskUsage.available) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'usedPercentageLabel' | translate }}:</span>
|
||||
<span class="info-value usage-percentage">{{ isRepository ? diskUsage.used_percentage : diskUsage.percentage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RAM Usage Section -->
|
||||
<div class="resource-card ram-usage-container" *ngIf="isRepository">
|
||||
<div class="resource-header">
|
||||
<div class="resource-icon">
|
||||
<mat-icon>memory</mat-icon>
|
||||
</div>
|
||||
<h3 class="resource-title">{{ 'RamUsage' | translate }}</h3>
|
||||
</div>
|
||||
<div class="resource-content">
|
||||
<div class="chart-container">
|
||||
<ngx-charts-pie-chart [view]="view" [scheme]="colorScheme" [results]="ramUsageChartData" [doughnut]="isDoughnut"
|
||||
[labels]="showLabels">
|
||||
</ngx-charts-pie-chart>
|
||||
</div>
|
||||
<div class="resource-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'totalLabel' | translate }}:</span>
|
||||
<span class="info-value">{{ ramUsage.total }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'usedLabel' | translate }}:</span>
|
||||
<span class="info-value">{{ ramUsage.used }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'availableLabel' | translate }}:</span>
|
||||
<span class="info-value">{{ ramUsage.available }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{{ 'usedPercentageLabel' | translate }}:</span>
|
||||
<span class="info-value usage-percentage">{{ ramUsage.used_percentage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CPU Usage Section -->
|
||||
<div class="resource-card cpu-usage-container" *ngIf="isRepository">
|
||||
<div class="resource-header">
|
||||
<div class="resource-icon">
|
||||
<mat-icon>speed</mat-icon>
|
||||
</div>
|
||||
<h3 class="resource-title">{{ 'CpuUsage' | translate }}</h3>
|
||||
</div>
|
||||
<div class="resource-content">
|
||||
<div class="cpu-usage-display">
|
||||
<div class="cpu-circle">
|
||||
<div class="cpu-percentage">{{ cpuUsage.used_percentage }}</div>
|
||||
<div class="cpu-label">Uso actual</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RAM Usage Section -->
|
||||
<div class="ram-usage-container" *ngIf="isRepository">
|
||||
<h3 class="ram-title">{{ 'RamUsage' | translate }}</h3>
|
||||
<div class="ram-usage">
|
||||
<ngx-charts-pie-chart [view]="view" [scheme]="colorScheme" [results]="ramUsageChartData" [doughnut]="isDoughnut"
|
||||
[labels]="showLabels">
|
||||
</ngx-charts-pie-chart>
|
||||
<div class="ram-usage-info">
|
||||
<p>{{ 'totalLabel' | translate }}: <strong>{{ ramUsage.total }}</strong></p>
|
||||
<p>{{ 'usedLabel' | translate }}: <strong>{{ ramUsage.used }}</strong></p>
|
||||
<p>{{ 'availableLabel' | translate }}: <strong>{{ ramUsage.available }}</strong></p>
|
||||
<p>{{ 'usedPercentageLabel' | translate }}: <strong>{{ ramUsage.used_percentage }}</strong></p>
|
||||
<!-- Sección de servicios y procesos -->
|
||||
<div class="services-section">
|
||||
<!-- Services Status Section -->
|
||||
<div class="service-card services-status" joyrideStep="servicesStatusStep" text="{{ 'servicesStatusDescription' | translate }}">
|
||||
<div class="service-header">
|
||||
<div class="service-icon">
|
||||
<mat-icon>settings</mat-icon>
|
||||
</div>
|
||||
<h3 class="service-title">{{ 'servicesTitle' | translate }}</h3>
|
||||
</div>
|
||||
<div class="service-list">
|
||||
<div class="service-item" *ngFor="let service of getServices()">
|
||||
<div class="service-status">
|
||||
<span class="status-indicator"
|
||||
[ngClass]="{ 'active': service.status === 'active', 'inactive': service.status !== 'active' }"></span>
|
||||
<span class="service-name">{{ service.name }}</span>
|
||||
</div>
|
||||
<span class="service-state" [ngClass]="{ 'active': service.status === 'active', 'inactive': service.status !== 'active' }">
|
||||
{{ service.status | translate }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Processes Status Section -->
|
||||
<div class="service-card processes-status" *ngIf="isRepository">
|
||||
<div class="service-header">
|
||||
<div class="service-icon">
|
||||
<mat-icon>list_alt</mat-icon>
|
||||
</div>
|
||||
<h3 class="service-title">{{ 'processes' | translate }}</h3>
|
||||
</div>
|
||||
<div class="service-list">
|
||||
<div class="service-item" *ngFor="let process of getProcesses()">
|
||||
<div class="service-status">
|
||||
<span class="status-indicator"
|
||||
[ngClass]="{ 'active': process.status === 'running', 'inactive': process.status !== 'running' }"></span>
|
||||
<span class="service-name">{{ process.name }}</span>
|
||||
</div>
|
||||
<span class="service-state" [ngClass]="{ 'active': process.status === 'running', 'inactive': process.status !== 'running' }">
|
||||
{{ process.status }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CPU Usage Section -->
|
||||
<div class="cpu-usage-container" *ngIf="isRepository">
|
||||
<h3 class="cpu-title">{{ 'CpuUsage' | translate }}</h3>
|
||||
<div class="cpu-usage">
|
||||
<p>{{ 'usedLabel' | translate }}: <strong>{{ cpuUsage.used_percentage }}</strong></p>
|
||||
<!-- Sección de datos específicos -->
|
||||
<div class="data-section">
|
||||
<!-- Installed OgLives Section -->
|
||||
<div class="data-card" *ngIf="!isRepository && !isDhcp">
|
||||
<div class="data-header">
|
||||
<div class="data-icon">
|
||||
<mat-icon>computer</mat-icon>
|
||||
</div>
|
||||
<h3 class="data-title">{{ 'InstalledOglivesTitle' | translate }}</h3>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="data-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 item of installedOgLives">
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.kernel }}</td>
|
||||
<td>{{ item.architecture }}</td>
|
||||
<td>{{ item.revision }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Services Status Section -->
|
||||
<div class="services-status" joyrideStep="servicesStatusStep" text="{{ 'servicesStatusDescription' | translate }}">
|
||||
<h3 class="service-title">{{ 'servicesTitle' | translate }}</h3>
|
||||
<ul class="service-list">
|
||||
<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>
|
||||
|
||||
<!-- Processes Status Section -->
|
||||
<div class="processes-status" *ngIf="isRepository">
|
||||
<h3 class="process-title">{{ 'processes' | translate }}</h3>
|
||||
<ul class="process-list">
|
||||
<li *ngFor="let process of getProcesses()">
|
||||
<span class="status-led"
|
||||
[ngClass]="{ 'active': process.status === 'running', 'inactive': process.status !== 'running' }"></span>
|
||||
{{ process.name }}: {{ process.status }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Installed OgLives / Subnets Section -->
|
||||
<div *ngIf="!isRepository && !isDhcp">
|
||||
<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 item of installedOgLives">
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.kernel }}</td>
|
||||
<td>{{ item.architecture }}</td>
|
||||
<td>{{ item.revision }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isDhcp">
|
||||
<h3>{{ 'subnets' | translate }}</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'idLabel' | translate }}</th>
|
||||
<th>{{ 'bootFileNameLabel' | translate }}</th>
|
||||
<th>{{ 'nextServerLabel' | translate }}</th>
|
||||
<th>{{ 'ipLabel' | translate }}</th>
|
||||
<th>{{ 'clientsLabel' | translate }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of subnets">
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item['boot-file-name'] }}</td>
|
||||
<td>{{ item['next-server'] }}</td>
|
||||
<td>{{ item.subnet }}</td>
|
||||
<td>{{ item.reservations.length }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Subnets Section -->
|
||||
<div class="data-card" *ngIf="isDhcp">
|
||||
<div class="data-header">
|
||||
<div class="data-icon">
|
||||
<mat-icon>router</mat-icon>
|
||||
</div>
|
||||
<h3 class="data-title">{{ 'subnets' | translate }}</h3>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'idLabel' | translate }}</th>
|
||||
<th>{{ 'bootFileNameLabel' | translate }}</th>
|
||||
<th>{{ 'nextServerLabel' | translate }}</th>
|
||||
<th>{{ 'ipLabel' | translate }}</th>
|
||||
<th>{{ 'clientsLabel' | translate }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of subnets">
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item['boot-file-name'] }}</td>
|
||||
<td>{{ item['next-server'] }}</td>
|
||||
<td>{{ item.subnet }}</td>
|
||||
<td>{{ item.reservations.length }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in New Issue