-
-
+
cloud
@@ -41,7 +38,9 @@
-
+
storage
@@ -52,7 +51,9 @@
-
+
router
@@ -62,11 +63,22 @@

Estado: Error

+ +
+
+ dns +
+
+

OgCore Server

+

Logs del servidor

+
+
- - - +
+
cloud_off @@ -75,7 +87,6 @@
-
Seleccionar repositorio @@ -88,11 +99,14 @@
-
-
-

{{ getSelectedRepositoryName() }}

+
+

{{ getSelectedRepositoryName() }}

+
@@ -111,16 +125,30 @@
+ +
+
+

Datos del repositorio: {{ getSelectedRepositoryName() }}

+

Información específica del repositorio seleccionado

+
+
+ +
+
-
storage

Selecciona un repositorio

Elige un repositorio de la lista para ver su estado detallado.

-
error_outline @@ -134,15 +162,39 @@
- +
- -
- - +
+
+
+

OgBoot Server

+ +
+ + + + +
+
+

Logs de OgBoot

+

Logs en tiempo real del servidor OgBoot

+
+
+ +
+
@@ -161,14 +213,38 @@
- +
- -
- - +
+
+
+

DHCP Server

+ +
+ + + + +
+
+

Logs de DHCP

+

Logs en tiempo real del servidor DHCP

+
+
+ +
+
@@ -187,12 +263,36 @@
- - +
+ +
+
+
+

OgCore Server

+
+ +
+
+

Logs de OgCore

+

Logs en tiempo real del servidor OgCore

+
+
+ +
+
+
+
+
+ -

Última actualización: {{ lastUpdateTime }}

diff --git a/ogWebconsole/src/app/components/global-status/global-status.component.ts b/ogWebconsole/src/app/components/global-status/global-status.component.ts index 05f74a4..2d09a25 100644 --- a/ogWebconsole/src/app/components/global-status/global-status.component.ts +++ b/ogWebconsole/src/app/components/global-status/global-status.component.ts @@ -1,8 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Component, OnInit } from '@angular/core'; import { ConfigService } from '@services/config.service'; -import { MatTabChangeEvent } from '@angular/material/tabs'; -import {ToastrService} from "ngx-toastr"; +import { ToastrService } from "ngx-toastr"; @Component({ selector: 'app-global-status', @@ -31,6 +30,7 @@ export class GlobalStatusComponent implements OnInit { repositoryStatuses: { [key: string]: any } = {}; lastUpdateTime: string = ''; selectedRepositoryUuid: string = ''; + selectedSection: 'repositories' | 'ogboot' | 'dhcp' | 'ogcore' = 'repositories'; ogBootApiUrl: string; ogBootDiskUsage: any = {}; @@ -44,7 +44,8 @@ export class GlobalStatusComponent implements OnInit { isDhcp: boolean = false; isRepository: boolean = false; - // Loading específicos para cada sección + ogCoreApiUrl: string; + loadingOgBootOgLives: boolean = false; loadingOgBootServices: boolean = false; loadingOgBootDisk: boolean = false; @@ -60,6 +61,7 @@ export class GlobalStatusComponent implements OnInit { this.baseUrl = this.configService.apiUrl; this.ogBootApiUrl = `${this.baseUrl}/og-boot/status`; this.dhcpApiUrl = `${this.baseUrl}/og-dhcp/status`; + this.ogCoreApiUrl = `${this.baseUrl}`; this.repositoriesUrl = `${this.baseUrl}/image-repositories`; this.ogBootDiskUsageChartData = []; @@ -290,23 +292,9 @@ export class GlobalStatusComponent implements OnInit { this.loadStatus(this.dhcpApiUrl, this.dhcpDiskUsage, this.dhcpServicesStatus, this.dhcpDiskUsageChartData, this.installedOgLives, this.isDhcp, 'errorDhcp', false); } - onTabChange(event: MatTabChangeEvent): void { - switch (event.index) { - case 0: - if (this.repositories.length === 0) { - this.loadRepositories(false); - } - break; - case 1: - this.loadOgBootStatus(); - break; - case 2: - this.loadDhcpStatus(); - break; - default: - break; - } - } + + + onRepositoryChange(repositoryUuid: string): void { this.selectedRepositoryUuid = repositoryUuid; @@ -324,6 +312,15 @@ export class GlobalStatusComponent implements OnInit { return selectedRepo ? selectedRepo.name : ''; } + getRepositoryIframeUrl(): string { + const repositoryName = this.getSelectedRepositoryName(); + if (repositoryName) { + const encodedName = encodeURIComponent(repositoryName); + return `https://localhost:3030/d-solo/ogrepo-logs/ogrepo-logs?orgId=1&timezone=browser&refresh=5s&var-query0=&editIndex=0&var-hostname=${encodedName}&theme=dark&panelId=1&__feature.dashboardSceneSolo`; + } + return ''; + } + refreshAll(): void { this.loading = true; this.updateLastUpdateTime(); @@ -364,4 +361,71 @@ export class GlobalStatusComponent implements OnInit { } }); } + + + + selectSection(section: 'repositories' | 'ogboot' | 'dhcp' | 'ogcore'): void { + this.selectedSection = section; + + switch (section) { + case 'repositories': + break; + case 'ogboot': + if (!this.ogBootDiskUsage) { + this.loadOgBootStatus(); + } + break; + case 'dhcp': + if (!this.dhcpDiskUsage) { + this.loadDhcpStatus(); + } + break; + case 'ogcore': + // No se necesita cargar nada, solo mostrar los logs + break; + + } + } + + scrollToLogs(): void { + let logsSectionId: string; + switch (this.selectedSection) { + case 'repositories': + logsSectionId = 'repository-logs-section'; + break; + case 'ogboot': + logsSectionId = 'ogboot-logs-section'; + break; + case 'dhcp': + logsSectionId = 'dhcp-logs-section'; + break; + case 'ogcore': + logsSectionId = 'ogcore-logs-section'; + break; + default: + return; + } + + const checkAndScroll = () => { + const logsSection = document.getElementById(logsSectionId); + if (logsSection) { + logsSection.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + return true; + } + return false; + }; + + const interval = setInterval(() => { + if (checkAndScroll()) { + clearInterval(interval); + } + }, 100); + + setTimeout(() => { + clearInterval(interval); + }, 3000); + } } diff --git a/ogWebconsole/src/app/components/global-status/status-tab/status-tab.component.html b/ogWebconsole/src/app/components/global-status/status-tab/status-tab.component.html index eb34459..c70823b 100644 --- a/ogWebconsole/src/app/components/global-status/status-tab/status-tab.component.html +++ b/ogWebconsole/src/app/components/global-status/status-tab/status-tab.component.html @@ -1,9 +1,7 @@
-
-
@@ -32,13 +30,12 @@
{{ 'usedPercentageLabel' | translate }}: - {{ isRepository ? diskUsage.used_percentage : diskUsage.percentage }}% + {{ isRepository ? diskUsage.used_percentage : diskUsage.percentage }}
-
@@ -67,13 +64,12 @@
{{ 'usedPercentageLabel' | translate }}: - {{ ramUsage.used_percentage }}% + {{ ramUsage.used_percentage }}
-
@@ -84,7 +80,7 @@
-
{{ cpuUsage.used_percentage }}%
+
{{ cpuUsage.used_percentage }}
Uso actual
@@ -92,9 +88,7 @@
-
-
@@ -116,7 +110,6 @@
-
@@ -139,9 +132,7 @@
-
-
@@ -171,7 +162,6 @@
-
-- 2.40.1 From 229ff86c5b06e1033060d73b237f9f88df35e95b Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 5 Aug 2025 10:26:36 +0200 Subject: [PATCH 05/11] refs #2597. Git repository show --- .../create-branch-modal.component.css | 67 +++++++++++++++++++ .../create-branch-modal.component.html | 31 +++++++++ .../create-branch-modal.component.ts | 61 +++++++++++++++++ .../create-tag-modal.component.spec.ts | 24 +++++-- .../create-tag-modal.component.ts | 1 - .../show-git-images.component.html | 4 ++ .../show-git-images.component.ts | 23 +++++++ 7 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 ogWebconsole/src/app/components/repositories/show-git-images/create-branch-modal/create-branch-modal.component.css create mode 100644 ogWebconsole/src/app/components/repositories/show-git-images/create-branch-modal/create-branch-modal.component.html create mode 100644 ogWebconsole/src/app/components/repositories/show-git-images/create-branch-modal/create-branch-modal.component.ts diff --git a/ogWebconsole/src/app/components/repositories/show-git-images/create-branch-modal/create-branch-modal.component.css b/ogWebconsole/src/app/components/repositories/show-git-images/create-branch-modal/create-branch-modal.component.css new file mode 100644 index 0000000..df6972f --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/show-git-images/create-branch-modal/create-branch-modal.component.css @@ -0,0 +1,67 @@ +.dialog-content { + min-width: 400px; + max-width: 600px; +} + +.commit-info { + background-color: #f5f5f5; + padding: 15px; + border-radius: 5px; + margin-bottom: 20px; +} + +.commit-info p { + margin: 5px 0; + font-size: 14px; +} + +.branch-form { + display: flex; + flex-direction: column; + gap: 15px; +} + +.form-field { + width: 100%; +} + +.action-container { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 20px; + padding: 0 24px 24px 24px; +} + +.ordinary-button { + background-color: #f5f5f5; + color: #333; + border: 1px solid #ddd; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; +} + +.ordinary-button:hover { + background-color: #e0e0e0; +} + +.submit-button { + background-color: #3f51b5; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; +} + +.submit-button:hover:not(:disabled) { + background-color: #303f9f; +} + +.submit-button:disabled { + background-color: #ccc; + cursor: not-allowed; +} \ No newline at end of file diff --git a/ogWebconsole/src/app/components/repositories/show-git-images/create-branch-modal/create-branch-modal.component.html b/ogWebconsole/src/app/components/repositories/show-git-images/create-branch-modal/create-branch-modal.component.html new file mode 100644 index 0000000..cb0f7cf --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/show-git-images/create-branch-modal/create-branch-modal.component.html @@ -0,0 +1,31 @@ + + +

Crear Rama desde Commit

+ + +
+

Commit ID: {{ data.commit?.hexsha }}

+

Mensaje: {{ data.commit?.message }}

+
+ + + + Nombre de la rama + + + El nombre de la rama es obligatorio + + + El nombre de la rama solo puede contener letras, números, puntos, guiones y guiones bajos + + Ejemplo: feature-nueva-funcionalidad, hotfix-bug-123, release-v2.0 + + +
+ +
+ + +
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/repositories/show-git-images/create-branch-modal/create-branch-modal.component.ts b/ogWebconsole/src/app/components/repositories/show-git-images/create-branch-modal/create-branch-modal.component.ts new file mode 100644 index 0000000..ce8f72f --- /dev/null +++ b/ogWebconsole/src/app/components/repositories/show-git-images/create-branch-modal/create-branch-modal.component.ts @@ -0,0 +1,61 @@ +import { Component, Inject } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { HttpClient } from '@angular/common/http'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { ToastrService } from 'ngx-toastr'; +import { ConfigService } from '@services/config.service'; + +@Component({ + selector: 'app-create-branch-modal', + templateUrl: './create-branch-modal.component.html', + styleUrl: './create-branch-modal.component.css' +}) +export class CreateBranchModalComponent { + branchForm: FormGroup; + loading: boolean = false; + baseUrl: string; + + constructor( + private fb: FormBuilder, + private http: HttpClient, + public dialogRef: MatDialogRef, + private toastService: ToastrService, + private configService: ConfigService, + @Inject(MAT_DIALOG_DATA) public data: { commit: any, repositoryName: string, repositoryUuid: string } + ) { + this.baseUrl = this.configService.apiUrl; + this.branchForm = this.fb.group({ + name: ['', [Validators.required, Validators.pattern(/^[a-zA-Z0-9._-]+$/)]], + }); + } + + createBranch(): void { + if (this.branchForm.valid) { + this.loading = true; + const payload = { + commit: this.data.commit?.hexsha || 'master', + name: this.branchForm.value.name, + repository: this.data.repositoryName + }; + + const url = `${this.baseUrl}/image-repositories/server/git/${this.data.repositoryUuid}/create-branch`; + + this.http.post(url, payload).subscribe({ + next: (response) => { + this.toastService.success('Rama creada correctamente'); + this.dialogRef.close(response); + }, + error: (error) => { + this.toastService.error(error.error?.message || 'Error al crear la rama'); + this.loading = false; + } + }); + } else { + this.toastService.error('Por favor, complete todos los campos requeridos'); + } + } + + close(): void { + this.dialogRef.close(); + } +} \ No newline at end of file diff --git a/ogWebconsole/src/app/components/repositories/show-git-images/create-tag-modal/create-tag-modal.component.spec.ts b/ogWebconsole/src/app/components/repositories/show-git-images/create-tag-modal/create-tag-modal.component.spec.ts index 9c17550..488da1e 100644 --- a/ogWebconsole/src/app/components/repositories/show-git-images/create-tag-modal/create-tag-modal.component.spec.ts +++ b/ogWebconsole/src/app/components/repositories/show-git-images/create-tag-modal/create-tag-modal.component.spec.ts @@ -4,6 +4,7 @@ import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/materia import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { HttpClient } from '@angular/common/http'; import { ToastrService } from 'ngx-toastr'; +import { ToastrModule } from 'ngx-toastr'; import { ConfigService } from '@services/config.service'; import { provideHttpClient } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; @@ -11,29 +12,44 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatButtonModule } from '@angular/material/button'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TranslateModule } from '@ngx-translate/core'; +import { LoadingComponent } from '../../../../shared/loading/loading.component'; describe('CreateTagModalComponent', () => { let component: CreateTagModalComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockToastrService = { + success: jasmine.createSpy('success'), + error: jasmine.createSpy('error'), + warning: jasmine.createSpy('warning'), + info: jasmine.createSpy('info') + }; + + const mockConfigService = { + apiUrl: 'http://mock-api-url' + }; + await TestBed.configureTestingModule({ - declarations: [CreateTagModalComponent], + declarations: [CreateTagModalComponent, LoadingComponent], imports: [ MatDialogModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatButtonModule, - NoopAnimationsModule + NoopAnimationsModule, + TranslateModule.forRoot(), + ToastrModule.forRoot() ], providers: [ FormBuilder, HttpClient, - ToastrService, - ConfigService, provideHttpClient(), provideHttpClientTesting(), + { provide: ToastrService, useValue: mockToastrService }, + { provide: ConfigService, useValue: mockConfigService }, { provide: MatDialogRef, useValue: {} diff --git a/ogWebconsole/src/app/components/repositories/show-git-images/create-tag-modal/create-tag-modal.component.ts b/ogWebconsole/src/app/components/repositories/show-git-images/create-tag-modal/create-tag-modal.component.ts index 5eea2a1..3ed3f55 100644 --- a/ogWebconsole/src/app/components/repositories/show-git-images/create-tag-modal/create-tag-modal.component.ts +++ b/ogWebconsole/src/app/components/repositories/show-git-images/create-tag-modal/create-tag-modal.component.ts @@ -48,7 +48,6 @@ export class CreateTagModalComponent { this.dialogRef.close(response); }, error: (error) => { - console.error('Error creating tag:', error); this.toastService.error(error.error?.message || 'Error al crear el tag'); this.loading = false; } diff --git a/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.html b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.html index 1269004..2f53ce2 100644 --- a/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.html +++ b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.html @@ -99,6 +99,10 @@ + +
diff --git a/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.ts b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.ts index 8475d38..407c318 100644 --- a/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.ts +++ b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.ts @@ -9,6 +9,7 @@ import {ConfigService} from "@services/config.service"; import {Router} from "@angular/router"; import {ServerInfoDialogComponent} from "../../ogdhcp/server-info-dialog/server-info-dialog.component"; import {CreateTagModalComponent} from "./create-tag-modal/create-tag-modal.component"; +import {CreateBranchModalComponent} from "./create-branch-modal/create-branch-modal.component"; @Component({ selector: 'app-show-git-commits', @@ -210,6 +211,9 @@ export class ShowGitCommitsComponent implements OnInit{ case 'create-tag': this.openCreateTagDialog(commit); break; + case 'create-branch': + this.openCreateBranchDialog(commit); + break; default: console.error('Acción no soportada:', action); break; @@ -269,6 +273,25 @@ export class ShowGitCommitsComponent implements OnInit{ }); } + openCreateBranchDialog(commit: any) { + const dialogRef = this.dialog.open(CreateBranchModalComponent, { + width: '500px', + data: { + commit: commit, + repositoryName: this.selectedRepository, + repositoryUuid: this.data.repositoryUuid + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + // Recargar los datos para mostrar la nueva rama + this.loadBranches(); + this.loadData(); + } + }); + } + goToPage(commit: any) { window.open(`http://localhost:3100/oggit/${this.selectedRepository}/commit/${commit.hexsha}`, '_blank'); } -- 2.40.1 From 9969ab303aff97e3d76247a24631ee3bd11443b7 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 5 Aug 2025 10:28:06 +0200 Subject: [PATCH 06/11] refs #2505. Update GIT image --- .../create-image/create-image.component.css | 337 ++++++++++++++++++ .../create-image/create-image.component.html | 233 +++++++----- .../create-image/create-image.component.ts | 294 ++++++++++++--- 3 files changed, 722 insertions(+), 142 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.css index d7c8b69..a1ed15d 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.css +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.css @@ -675,3 +675,340 @@ mat-form-field { } } +@media (max-width: 768px) { + .scroll-to-top-button { + right: 16px; + bottom: 16px; + } +} + +/* Estilos para el selector de ramas */ +.branch-selector-header { + display: flex; + gap: 16px; + align-items: flex-end; + width: 100%; +} + +.create-branch-button { + background: #4caf50; + color: white; + border: none; + padding: 12px 20px; + border-radius: 8px; + font-weight: 500; + transition: all 0.3s ease; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; + white-space: nowrap; + height: 56px; +} + +.create-branch-button:hover:not(:disabled) { + background: #45a049; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3); +} + +.create-branch-button:disabled { + background: #ccc; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +.create-branch-button mat-icon { + font-size: 18px; + width: 18px; + height: 18px; +} + +/* Estilos para la sección de commits */ +.commits-section { + margin-top: 24px; + padding: 24px; + background: white; + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border: 1px solid #e0e0e0; +} + +.search-container { + margin-bottom: 20px; +} + +.search-string { + width: 100%; +} + +.commits-table-container { + margin-top: 20px; + overflow-x: auto; +} + +.commit-id { + font-family: 'Courier New', monospace; + background: #f5f5f5; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + color: #333; +} + +.commit-message { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.commit-stats { + font-size: 12px; + color: #666; +} + +.commit-tags { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.commit-tags mat-chip { + font-size: 10px; + height: 20px; +} + +.no-tags { + color: #999; + font-size: 12px; + font-style: italic; +} + +.action-buttons { + display: flex; + gap: 8px; + justify-content: center; +} + +.action-buttons button { + transition: all 0.2s ease; +} + +.action-buttons button:hover { + transform: scale(1.1); +} + +.paginator-container { + margin-top: 20px; +} + +/* Responsive para ramas y commits */ +@media (max-width: 768px) { + .branch-selector-header { + flex-direction: column; + align-items: stretch; + } + + .create-branch-button { + height: auto; + padding: 12px 16px; + } + + .commits-section { + padding: 16px; + } + + .commit-message { + max-width: 200px; + } + + .action-buttons { + flex-direction: column; + gap: 4px; + } +} + +.git-info-container { + margin-bottom: 20px; +} + +.git-info-card { + background: #f8f9fa; + border-radius: 8px; + padding: 20px; + color: #495057; + border: 1px solid #e9ecef; + animation: fadeInUp 0.5s ease-out; +} + +.git-info-header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 16px; +} + +.git-info-icon { + font-size: 20px; + width: 20px; + height: 20px; + color: #6c757d; +} + +.git-info-title { + font-size: 16px; + font-weight: 600; + flex-grow: 1; + color: #495057; +} + +.git-loading-spinner { + margin-left: auto; +} + +.git-info-content { + background: #ffffff; + border-radius: 6px; + padding: 16px; + margin-top: 12px; + border: 1px solid #dee2e6; +} + +.git-info-grid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 16px; +} + +.git-info-item { + display: flex; + flex-direction: column; +} + +.git-form-field { + width: 100%; +} + +.git-item-icon { + font-size: 16px; + width: 16px; + height: 16px; + color: #495057; + margin-right: 4px; +} + + + + + +.git-data-display { + background: #f8f9fa; + border-radius: 4px; + padding: 12px; + margin: 0; + font-family: 'Courier New', monospace; + font-size: 12px; + color: #495057; + overflow-x: auto; + white-space: pre-wrap; + word-break: break-word; + max-height: 300px; + overflow-y: auto; + border: 1px solid #e9ecef; +} + +.git-info-loading { + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + font-size: 14px; + color: #6c757d; +} + +.git-info-empty { + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + font-size: 14px; + color: #6c757d; + font-style: italic; +} + +/* Estilos para la advertencia de repositorio no encontrado */ +.repository-warning { + display: flex; + align-items: flex-start; + gap: 12px; + margin-top: 16px; + padding: 16px; + background: #fff3cd; + border: 1px solid #ffeaa7; + border-radius: 8px; + color: #856404; +} + +.warning-icon { + font-size: 20px; + width: 20px; + height: 20px; + color: #f39c12; + flex-shrink: 0; + margin-top: 2px; +} + +.warning-content { + flex-grow: 1; +} + +.warning-title { + font-size: 14px; + font-weight: 600; + margin-bottom: 4px; + color: #e67e22; +} + +.warning-message { + font-size: 13px; + line-height: 1.4; + color: #856404; +} + +/* Responsive para el cuadro informativo de Git */ +@media (max-width: 768px) { + .git-info-card { + padding: 16px; + } + + .git-info-header { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } + + .git-loading-spinner { + margin-left: 0; + align-self: flex-end; + } + + .git-data-display { + font-size: 11px; + max-height: 200px; + } + + .git-info-grid { + grid-template-columns: 1fr; + gap: 12px; + } + + .git-info-item { + padding: 10px; + } + + .git-info-value { + font-size: 13px; + } +} \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html index 5092b02..70eedfe 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.html @@ -1,4 +1,5 @@ - + + @@ -20,12 +21,11 @@
- +
-
settings @@ -43,15 +43,81 @@
-
code Configuración Git
-
-
+
+
+
+ info + La imagen en caché a actualizar es: + +
+
+
+
+ + + folder + Repositorio + + + +
+
+ + + account_tree + Rama origen + + + +
+
+ + + call_split + Rama destino + + + + +
+
+ +
+ warning +
+
Repositorio no encontrado
+
+ El repositorio "{{ gitData.repo }}" no existe en el listado de repositorios disponibles. + Es posible que necesites crearlo primero. +
+
+
+
+
+ Cargando información de Git... +
+
+ No se encontró información de Git para esta partición +
+
+
+ +
-
- - Seleccionar repositorio Git - - Seleccionar repositorio git / SO - {{ repo.name }} - - - - info - Selecciona el repositorio git para obtener las imágenes disponibles. - - No hay repositorios disponibles. Crea uno nuevo para continuar. - - - -
-
+
+ + Seleccionar repositorio Git + + Seleccionar repositorio git / SO + {{ repo.name }} + + + + info + Selecciona el repositorio git para obtener las imágenes disponibles. + + No hay repositorios disponibles. Crea uno nuevo para continuar. + + + - -
-
- - - Crear imagen - - - Actualizar imagen - - -
-
- info - Crea una nueva imagen con el nombre especificado - Actualiza una imagen existente seleccionada -
-
-
- - -
-
- image - Configuración de imagen +
- -
- - - Crear imagen - - - Actualizar imagen - - -
-
- info - Crea una nueva imagen con el nombre especificado - Actualiza una imagen existente seleccionada -
- -
- - Nombre canónico - - Introduce el nombre para la nueva imagen que se creará. - -
- -
- - Seleccione imagen - - Seleccionar imagen para actualizar - {{ image?.name }} - - - Selecciona la imagen existente que quieres actualizar. - -
-
- +
+
+ image + Configuración de imagen monolítica +
+ +
+ + + Crear imagen + + + Actualizar imagen + + +
+
+ info + Crea una nueva imagen con el nombre especificado + Actualiza una imagen existente seleccionada +
+ +
+ + Nombre canónico + + Introduce el nombre para la nueva imagen que se creará. + +
+ +
+ + Seleccione imagen + + Seleccionar imagen para actualizar + {{ image?.name }} + + + Selecciona la imagen existente que quieres actualizar. + +
+
+
storage diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts index 2f4e8d9..88a8f57 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-image.component.ts @@ -8,6 +8,7 @@ import { ConfigService } from '@services/config.service'; import {MatDialog} from "@angular/material/dialog"; import {QueueConfirmationModalComponent} from "../../../../../shared/queue-confirmation-modal/queue-confirmation-modal.component"; import {CreateRepositoryModalComponent} from "./create-repository-modal/create-repository-modal.component"; +import {CreateBranchModalComponent} from "../../../../repositories/show-git-images/create-branch-modal/create-branch-modal.component"; @Component({ selector: 'app-create-image', @@ -38,12 +39,27 @@ export class CreateClientImageComponent implements OnInit{ loadingGitRepositories: boolean = false; loadingGitImageRepositories: boolean = false; creatingRepository: boolean = false; - gitAction: string = 'create'; monolithicAction: string = 'create'; existingImages: any[] = []; selectedExistingImage: any = null; loadingExistingImages: boolean = false; dataSource = new MatTableDataSource(); + + branches: string[] = []; + selectedBranch: string = ''; + loadingBranches: boolean = false; + + gitData: any = null; + loadingGitData: boolean = false; + destinationBranch: string = ''; + isDestinationBranchEditable: boolean = false; + repositoryNotFound: boolean = false; + newlyCreatedRepository: any = null; + + get hasValidGitData(): boolean { + return this.gitData && this.gitData.repo && this.gitData.branch; + } + columns = [ { columnDef: 'diskNumber', @@ -107,9 +123,19 @@ export class CreateClientImageComponent implements OnInit{ this.clientName = response.name; this.selectedRepository = response.repository; - this.dataSource.data = response.partitions.filter((partition: any) => { + const validPartitions = response.partitions.filter((partition: any) => { return partition.partitionNumber !== 0; }); + + this.dataSource.data = validPartitions; + + const firstValidPartition = validPartitions.find((partition: any) => { + return partition.operativeSystem; + }); + + if (firstValidPartition) { + this.selectedPartition = firstValidPartition; + } } }, (error) => { @@ -141,6 +167,23 @@ export class CreateClientImageComponent implements OnInit{ (response: any) => { this.gitRepositories = response['hydra:member']; this.loadingGitRepositories = false; + + if (this.newlyCreatedRepository) { + + const newRepo = this.gitRepositories.find(repo => + repo.name === this.newlyCreatedRepository.name + ); + + if (newRepo) { + this.selectedGitRepository = newRepo; + this.onGitRepositorySelected(newRepo); + this.toastService.success(`Repositorio "${newRepo.name}" preseleccionado`); + } + + this.newlyCreatedRepository = null; + } + + this.checkRepositoryExists(); }, (error) => { console.error('Error al cargar los repositorios git:', error); @@ -151,7 +194,7 @@ export class CreateClientImageComponent implements OnInit{ loadGitImageRepositories(gitRepository: any) { this.loadingGitImageRepositories = true; - const url = `${this.baseUrl}/git-image-repositories?gitRepository.id=${gitRepository.id}&page=1&itemsPerPage=100`; + const url = `${this.baseUrl}/git-image-repositories?gitRepository.uuid=${gitRepository.uuid}&page=1&itemsPerPage=100`; this.http.get(url).subscribe( (response: any) => { this.gitImageRepositories = response['hydra:member']; @@ -168,47 +211,97 @@ export class CreateClientImageComponent implements OnInit{ this.selectedGitRepository = gitRepository; this.selectedExistingImage = null; this.existingImages = []; + this.selectedBranch = ''; + this.branches = []; + if (gitRepository) { this.loadGitImageRepositories(gitRepository); + this.loadBranches(); } else { this.gitImageRepositories = []; } } - onGitActionSelected(event: any) { - console.log('onGitActionSelected llamado con:', event); - this.gitAction = event.value; - this.selectedExistingImage = null; - this.gitImageName = ''; - - // Si se selecciona 'update' y ya hay un repositorio Git seleccionado, cargar los repositorios de imágenes - if (event.value === 'update' && this.selectedGitRepository) { - this.loadGitImageRepositories(this.selectedGitRepository); + loadBranches(): void { + if (!this.selectedGitRepository) { + return; } - - console.log('Antes del setTimeout'); - // Hacer scroll hacia la sección de partición después de un delay más largo - setTimeout(() => { - console.log('Dentro del setTimeout, llamando a scrollToPartitionSection'); - this.scrollToPartitionSection(); - }, 300); + this.loadingBranches = true; + const url = `${this.baseUrl}/image-repositories/server/git/${this.selectedRepository.uuid}/branches`; + this.http.post(url, { repositoryName: this.selectedGitRepository.name }).subscribe( + data => { + this.branches = data.branches || []; + this.loadingBranches = false; + if (this.branches.length > 0) { + this.selectedBranch = this.branches[0]; + } + }, + error => { + console.error('Error fetching branches', error); + this.toastService.error('Error al cargar las ramas del repositorio'); + this.loadingBranches = false; + } + ); + } + + onBranchChange(): void { + // La rama ha sido seleccionada, no necesitamos hacer nada más + } + + openCreateBranchModal(): void { + if (this.hasValidGitData) { + const dialogRef = this.dialog.open(CreateBranchModalComponent, { + width: '500px', + data: { + commit: this.gitData.branch, + repositoryName: this.gitData.repo, + repositoryUuid: this.selectedRepository.uuid + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.toastService.success('Rama creada correctamente'); + this.destinationBranch = result.branchName || result; + this.loadBranches(); + } + }); + return; + } + + if (!this.selectedGitRepository) { + this.toastService.error('Debe seleccionar un repositorio primero'); + return; + } + + const dialogRef = this.dialog.open(CreateBranchModalComponent, { + width: '500px', + data: { + commit: this.selectedBranch || 'master', + repositoryName: this.selectedGitRepository.name, + repositoryUuid: this.selectedRepository.uuid + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.toastService.success('Rama creada correctamente'); + this.destinationBranch = result.branchName || result; + this.loadBranches(); + } + }); } onMonolithicActionSelected(event: any) { - console.log('onMonolithicActionSelected llamado con:', event); this.monolithicAction = event.value; this.selectedImage = null; this.name = ''; - // Si se selecciona 'update', cargar las imágenes existentes if (event.value === 'update') { this.loadImages(); } - console.log('Antes del setTimeout (monolithic)'); - // Hacer scroll hacia la sección de partición después de un delay más largo setTimeout(() => { - console.log('Dentro del setTimeout (monolithic), llamando a scrollToPartitionSection'); this.scrollToPartitionSection(); }, 300); } @@ -217,8 +310,7 @@ export class CreateClientImageComponent implements OnInit{ if (!this.selectedExistingImage) return; this.loadingExistingImages = true; - // Aquí deberías hacer el GET al endpoint externo - // Por ahora uso un endpoint de ejemplo, ajusta según tu API + const url = `${this.baseUrl}/images?gitImageRepository.id=${this.selectedExistingImage.id}&page=1&itemsPerPage=100`; this.http.get(url).subscribe( @@ -238,14 +330,15 @@ export class CreateClientImageComponent implements OnInit{ this.selectedGitRepository = null; this.selectedExistingImage = null; this.gitImageName = ''; - this.gitAction = 'create'; + this.monolithicAction = 'create'; this.existingImages = []; this.gitRepositories = []; this.gitImageRepositories = []; + this.selectedBranch = ''; + this.branches = []; this.selectedImage = null; this.name = ''; - this.monolithicAction = 'create'; } resetCanonicalName() { @@ -258,12 +351,6 @@ export class CreateClientImageComponent implements OnInit{ return; } - if (this.imageType === 'git') { - if (!this.selectedGitRepository) { - this.toastService.error('Debes seleccionar un repositorio Git'); - return; - } - } if (this.imageType === 'monolithic') { if (this.monolithicAction === 'create' && !this.name) { @@ -295,25 +382,32 @@ export class CreateClientImageComponent implements OnInit{ let payload: any; if (this.imageType === 'git') { - if (this.gitAction === 'update') { - endpoint = `${this.baseUrl}/git-repositories/update-image`; - payload = { - client: `/clients/${this.clientId}`, - partition: this.selectedPartition['@id'], - gitRepository: this.selectedGitRepository.name, - queue: result - }; - } else { + const gitRepoName = this.hasValidGitData ? this.gitData.repo : this.selectedGitRepository?.name; + const originBranch = this.hasValidGitData ? this.gitData.branch : this.selectedBranch; + + if (this.branches.length === 0) { endpoint = `${this.baseUrl}/images`; payload = { client: `/clients/${this.clientId}`, partition: this.selectedPartition['@id'], type: this.imageType, - gitRepository: this.selectedGitRepository.name, - name: this.selectedGitRepository.name, + gitRepository: gitRepoName, + name: gitRepoName, + originalBranch: originBranch, + destinationBranch: this.destinationBranch, action: 'create', queue: result }; + } else { + endpoint = `${this.baseUrl}/git-repositories/update-image`; + payload = { + client: `/clients/${this.clientId}`, + partition: this.selectedPartition['@id'], + gitRepository: gitRepoName, + originalBranch: originBranch, + destinationBranch: this.destinationBranch, + queue: result + }; } } else { endpoint = `${this.baseUrl}/images`; @@ -337,9 +431,9 @@ export class CreateClientImageComponent implements OnInit{ .subscribe({ next: (response) => { let actionText = 'creación'; - if (this.imageType === 'git' && this.gitAction === 'update') { + if (this.imageType === 'monolithic' && this.monolithicAction === 'update') { actionText = 'actualización'; - } else if (this.imageType === 'monolithic' && this.monolithicAction === 'update') { + } else if (this.imageType === 'git' && this.branches.length > 0) { actionText = 'actualización'; } this.toastService.success(`Petición de ${actionText} de imagen enviada`); @@ -370,14 +464,28 @@ export class CreateClientImageComponent implements OnInit{ dialogRef.afterClosed().subscribe(result => { this.creatingRepository = false; if (result) { + + + this.newlyCreatedRepository = result; + this.loadGitRepositories(); + setTimeout(() => { - const newRepository = this.gitRepositories.find(repo => repo['@id'] === result['@id']); - if (newRepository) { - this.selectedGitRepository = newRepository; - this.onGitRepositorySelected(newRepository); + if (this.newlyCreatedRepository && !this.selectedGitRepository) { + const newRepo = this.gitRepositories.find(repo => + repo.name === this.newlyCreatedRepository.name + ); + + if (newRepo) { + this.selectedGitRepository = newRepo; + this.onGitRepositorySelected(newRepo); + this.toastService.success(`Repositorio "${newRepo.name}" preseleccionado`); + } + + this.newlyCreatedRepository = null; } - }, 200); + }, 1000); + } else { } }); } @@ -394,8 +502,13 @@ export class CreateClientImageComponent implements OnInit{ this.selectedImage = null; this.name = ''; this.monolithicAction = 'create'; + + if (this.selectedPartition) { + this.loadGitData(this.selectedPartition); + } } else { this.resetGitSelections(); + this.gitData = null; } } @@ -425,5 +538,82 @@ export class CreateClientImageComponent implements OnInit{ set selectedPartition(value: any) { this._selectedPartition = value; + + if (value && this.imageType === 'git') { + this.loadGitData(value); + } else { + this.gitData = null; + } + } + + loadGitData(partition: any): void { + this.loadingGitData = true; + this.gitData = null; + this.repositoryNotFound = false; + + const payload = { + partition: partition['@id'], + client: `/clients/${this.clientId}` + }; + + this.http.post(`${this.baseUrl}/git-repositories/get-git-data`, payload).subscribe({ + next: (response) => { + this.gitData = response; + if (this.gitData && this.gitData.branch) { + this.destinationBranch = this.gitData.branch; + } + this.loadingGitData = false; + + this.checkRepositoryExists(); + + if (this.hasValidGitData) { + this.loadBranchesForGitData(); + } + }, + error: (error) => { + console.error('Error al cargar datos de Git:', error); + this.toastService.error('Error al cargar información de Git'); + this.loadingGitData = false; + } + }); + } + + checkRepositoryExists(): void { + if (this.gitData && this.gitData.repo && this.gitRepositories.length > 0) { + const repoExists = this.gitRepositories.some((repo: any) => + repo.name === this.gitData.repo + ); + this.repositoryNotFound = !repoExists; + } else { + this.repositoryNotFound = false; + } + } + + loadBranchesForGitData(): void { + if (!this.gitData || !this.gitData.repo) { + return; + } + + this.loadingBranches = true; + const url = `${this.baseUrl}/image-repositories/server/git/${this.selectedRepository.uuid}/branches`; + this.http.post(url, { repositoryName: this.gitData.repo }).subscribe( + data => { + this.branches = data.branches || []; + this.loadingBranches = false; + }, + error => { + console.error('Error fetching branches for Git data', error); + this.loadingBranches = false; + } + ); + } + + toggleDestinationBranchEdit(): void { + this.isDestinationBranchEditable = !this.isDestinationBranchEditable; + + if (!this.isDestinationBranchEditable) { + // Opcional: Aquí se pueden agregar validaciones adicionales + console.log('Rama destino guardada:', this.destinationBranch); + } } } -- 2.40.1 From 847504f2860b0136eda2938359b2360fd8e7d531 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 5 Aug 2025 10:29:13 +0200 Subject: [PATCH 07/11] refs #2501. Panel logs integration --- .../components/groups/groups.component.html | 8 +++++++ .../app/components/groups/groups.component.ts | 22 +++++++++++++++++++ .../src/app/layout/header/header.component.ts | 4 ++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/ogWebconsole/src/app/components/groups/groups.component.html b/ogWebconsole/src/app/components/groups/groups.component.html index 3e98714..741645d 100644 --- a/ogWebconsole/src/app/components/groups/groups.component.html +++ b/ogWebconsole/src/app/components/groups/groups.component.html @@ -422,6 +422,10 @@ article Logs en tiempo real + +
- +
@@ -26,7 +26,7 @@
-
@@ -51,7 +51,7 @@
- Selecciona el disco que deseas particionar + Selecciona el disco que deseas particionar + + check_circle + Disco {{ selectedDisk.diskNumber }} seleccionado - {{ (selectedDisk.totalDiskSize / 1024).toFixed(2) }} GB + -
- info -
- Disco seleccionado: {{ selectedDisk.diskNumber }} - Tamaño total: {{ (selectedDisk.totalDiskSize / 1024).toFixed(2) }} GB -
-
-
warning
@@ -207,6 +203,16 @@
+ +
+
+ hourglass_empty + check_circle + error + {{ partitionValidationMessage }} +
+
+ diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts index eed1294..795b3d1 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/partition-assistant/partition-assistant.component.ts @@ -63,6 +63,12 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes partitionCode: string = ''; generatedInstructions: string = ''; + // Propiedades para validación de particiones + partitionValidationStatus: 'idle' | 'loading' | 'success' | 'error' = 'idle'; + partitionValidationMessage: string = ''; + private validationDebounceTime = 500; // ms + private validationSubject = new Subject(); + // Columnas para mat-table displayedColumns: string[] = ['partitionNumber', 'partitionCode', 'filesystem', 'size', 'percentage', 'format', 'actions']; @@ -95,10 +101,22 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null; this.clientData.forEach((client: { selected: boolean; status: string}) => { client.selected = true; }); - this.selectedClients = this.clientData.filter((client: { selected: boolean; status: string}) => client.selected); + this.selectedClients = this.clientData.filter( + (client: { selected: boolean; status: string }) => client.selected + ); + + if (this.selectedClients.length === 0 && this.clientData.length > 0) { + this.selectedClients = [this.clientData[0]]; + this.clientData[0].selected = true; + } this.selectedModelClient = this.clientData.find((client: { selected: boolean; status: string}) => client.selected) || null; + if (!this.selectedModelClient && this.clientData.length > 0) { + this.selectedModelClient = this.clientData[0]; + this.clientData[0].selected = true; + } + if (this.selectedModelClient) { this.loadPartitions(this.selectedModelClient); } @@ -115,6 +133,13 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes ).subscribe(() => { this.resizeChart(); }); + + this.validationSubject.pipe( + takeUntil(this.destroy$), + debounceTime(this.validationDebounceTime) + ).subscribe(() => { + this.validatePartitionSizes(); + }); } ngAfterViewInit(): void { @@ -293,7 +318,14 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes } updateSelectedClients() { - this.selectedClients = this.clientData.filter((client: { selected: any; }) => client.selected); + this.selectedClients = this.clientData.filter( + (client: { selected: boolean; status: string }) => client.selected + ); + + if (this.selectedClients.length === 0 && this.clientData.length > 0) { + this.selectedClients = [this.clientData[0]]; + this.clientData[0].selected = true; + } } getPartitionsTooltip(client: any): string { @@ -333,7 +365,8 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); this.updateDiskChart(disk); - // Redimensionar el gráfico después de añadir partición + this.validationSubject.next(); + setTimeout(() => { this.resizeChart(); }, 100); @@ -361,6 +394,8 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes partition.percentage = (size / disk.totalDiskSize) * 100; this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); this.updateDiskChart(disk); + + this.validationSubject.next(); } } } @@ -375,6 +410,10 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes save() { + if (this.selectedClients.length === 0 && this.clientData.length > 0) { + this.updateSelectedClients(); + } + if (!this.selectedDisk) { this.toastService.error('No se ha seleccionado un disco.'); return; @@ -454,7 +493,8 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes this.updateDiskChart(disk); this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); - // Redimensionar el gráfico después de eliminar partición + this.validationSubject.next(); + setTimeout(() => { this.resizeChart(); }, 100); @@ -470,9 +510,55 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes } this.updateDiskChart(disk); this.updatePartitionPercentages(disk.partitions, disk.totalDiskSize); + + this.validationSubject.next(); } } + validatePartitionSizes(): void { + if (!this.selectedModelClient || !this.selectedDisk) { + return; + } + + this.partitionValidationStatus = 'loading'; + this.partitionValidationMessage = ''; + + const partitions = this.selectedDisk.partitions + .filter((partition: Partition) => !partition.removed) + .map((partition: Partition) => ({ + diskNumber: this.selectedDisk.diskNumber, + partitionNumber: partition.partitionNumber, + size: partition.size, + partitionCode: partition.partitionCode, + filesystem: partition.filesystem + })); + + const payload = { + partitions: partitions + }; + + const url = `${this.baseUrl}${this.selectedModelClient.uuid}/check-partition-sizes`; + + this.http.post(url, payload).subscribe( + (response: any) => { + if (response.res === 1) { + this.partitionValidationStatus = 'success'; + this.partitionValidationMessage = 'Las particiones cumplen con los requisitos del disco.'; + } else if (response.res === 2) { + this.partitionValidationStatus = 'error'; + this.partitionValidationMessage = response.der || 'Las particiones no cumplen con los requisitos del disco.'; + } else { + this.partitionValidationStatus = 'error'; + this.partitionValidationMessage = 'Respuesta inesperada del servidor.'; + } + }, + (error) => { + this.partitionValidationStatus = 'error'; + this.partitionValidationMessage = error.error?.message || 'Error al validar las particiones.'; + } + ); + } + calculateUsedSpace(partitions: Partition[]): number { return partitions .filter(partition => !partition.removed) @@ -498,24 +584,36 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes } updateDiskChart(disk: any) { - console.log('disk', disk); disk.chartData = this.generateChartData(disk.partitions); disk.used = this.calculateUsedSpace(disk.partitions); disk.percentage = (disk.used / disk.totalDiskSize) * 100; - // Redimensionar el gráfico después de actualizar los datos setTimeout(() => { this.resizeChart(); }, 50); } openScheduleModal(): void { + let scope = this.runScriptContext?.type || 'clients'; + let selectedClients = null; + + if (this.selectedClients.length === 0 && this.clientData.length > 0) { + this.updateSelectedClients(); + } + + if (this.selectedClients && this.selectedClients.length > 0) { + scope = 'clients'; + selectedClients = this.selectedClients; + } + const dialogRef = this.dialog.open(CreateTaskComponent, { width: '800px', data: { - scope: this.runScriptContext.type, - organizationalUnit: this.runScriptContext['@id'], - source: 'assistant' + scope: scope, + selectedClients: selectedClients, + organizationalUnit: this.runScriptContext?.['@id'], + source: 'assistant', + runScriptContext: this.runScriptContext } }); @@ -577,7 +675,8 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes if (this.selectedDiskNumber) { this.scrollToPartitionTable(); - // Redimensionar el gráfico después de cambiar de disco + this.validationSubject.next(); + setTimeout(() => { this.resizeChart(); }, 150); @@ -585,7 +684,6 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes } scrollToPartitionTable() { - // Pequeño delay para asegurar que el contenido se haya renderizado setTimeout(() => { const diskInfo = document.getElementById('disk-info'); @@ -630,9 +728,7 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes ((this.selectedDisk.totalDiskSize - this.selectedDisk.used) / this.selectedDisk.totalDiskSize) * 100 : 0; } - /** - * Asigna un color único a una nueva partición - */ + private getNextPartitionColor(): string { if (!this.selectedDisk) return this.partitionColors[0]; @@ -640,20 +736,16 @@ export class PartitionAssistantComponent implements OnInit, AfterViewInit, OnDes .filter((p: Partition) => !p.removed) .map((p: Partition) => p.color); - // Buscar el primer color no usado for (const color of this.partitionColors) { if (!usedColors.includes(color)) { return color; } } - // Si todos están usados, generar uno aleatorio return this.partitionColors[Math.floor(Math.random() * this.partitionColors.length)]; } - /** - * Asigna un color basado en el número de partición - */ + private getColorForPartition(partitionNumber: number): string { return this.partitionColors[(partitionNumber - 1) % this.partitionColors.length]; } -- 2.40.1 From 17a3bfb2c54160175870e47ac3f5827b8210fd61 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 5 Aug 2025 10:35:57 +0200 Subject: [PATCH 09/11] refs #2501. Panel logs integration --- .../client-logs-modal.component.css | 139 ++++++++++++++++++ .../client-logs-modal.component.html | 37 +++++ .../client-logs-modal.component.ts | 29 ++++ 3 files changed, 205 insertions(+) create mode 100644 ogWebconsole/src/app/components/groups/shared/client-logs-modal/client-logs-modal.component.css create mode 100644 ogWebconsole/src/app/components/groups/shared/client-logs-modal/client-logs-modal.component.html create mode 100644 ogWebconsole/src/app/components/groups/shared/client-logs-modal/client-logs-modal.component.ts diff --git a/ogWebconsole/src/app/components/groups/shared/client-logs-modal/client-logs-modal.component.css b/ogWebconsole/src/app/components/groups/shared/client-logs-modal/client-logs-modal.component.css new file mode 100644 index 0000000..1e3fa4b --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/client-logs-modal/client-logs-modal.component.css @@ -0,0 +1,139 @@ +.client-logs-modal { + display: flex; + flex-direction: column; + height: 100%; + max-height: 90vh; +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 24px; + border-bottom: 1px solid #e0e0e0; + background-color: #fafafa; +} + +.header-actions { + display: flex; + gap: 8px; +} + +.grafana-button { + color: #666; + transition: color 0.2s ease; +} + +.grafana-button:hover { + color: #2196f3; +} + +.modal-header h2 { + margin: 0; + display: flex; + align-items: center; + gap: 8px; + font-size: 1.25rem; + color: #333; +} + +.close-button { + color: #666; +} + +.close-button:hover { + color: #333; +} + +.modal-content { + flex: 1; + padding: 24px; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.client-info { + margin-bottom: 16px; + padding: 12px; + background-color: #f5f5f5; + border-radius: 4px; + border-left: 4px solid #2196f3; +} + +.client-info p { + margin: 4px 0; + font-size: 0.9rem; +} + +.client-info strong { + color: #333; +} + +.iframe-container { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + background-color: #f9f9f9; + border-radius: 4px; + overflow: hidden; +} + +.logs-iframe { + border: none; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + background-color: white; +} + +.modal-actions { + padding: 16px 24px; + border-top: 1px solid #e0e0e0; + display: flex; + justify-content: flex-end; + background-color: #fafafa; +} + +.modal-actions button { + min-width: 80px; +} + +/* Responsive design */ +@media (max-width: 1200px) { + .logs-iframe { + width: 100% !important; + height: 600px !important; + } +} + +@media (max-width: 768px) { + .logs-iframe { + width: 100% !important; + height: 400px !important; + } + + .modal-content { + padding: 16px; + } + + .modal-header { + padding: 12px 16px; + } + + .modal-actions { + padding: 12px 16px; + } +} + +.action-button { + border-radius: 8px; + font-weight: 500; + padding: 12px 24px; + transition: all 0.3s ease; +} + +.action-button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); +} \ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/client-logs-modal/client-logs-modal.component.html b/ogWebconsole/src/app/components/groups/shared/client-logs-modal/client-logs-modal.component.html new file mode 100644 index 0000000..c2d12a7 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/client-logs-modal/client-logs-modal.component.html @@ -0,0 +1,37 @@ +
+ + + + + +
\ No newline at end of file diff --git a/ogWebconsole/src/app/components/groups/shared/client-logs-modal/client-logs-modal.component.ts b/ogWebconsole/src/app/components/groups/shared/client-logs-modal/client-logs-modal.component.ts new file mode 100644 index 0000000..9e7ebd1 --- /dev/null +++ b/ogWebconsole/src/app/components/groups/shared/client-logs-modal/client-logs-modal.component.ts @@ -0,0 +1,29 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'app-client-logs-modal', + templateUrl: './client-logs-modal.component.html', + styleUrls: ['./client-logs-modal.component.css'] +}) +export class ClientLogsModalComponent { + iframeUrl: string; + grafanaUrl: string; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { client: any } + ) { + const mac = this.data.client.mac || ''; + this.iframeUrl = `https://localhost:3030/d-solo/opengnsys-clients/filebeat-clients?orgId=1&timezone=browser&var-hostname=$__all&var-mac=${mac}&refresh=5s&panelId=1&__feature.dashboardSceneSolo`; + this.grafanaUrl = `https://localhost:3030/d/opengnsys-clients/filebeat-clients?orgId=1&from=now-5m&to=now&timezone=browser&var-hostname=$__all&var-mac=${mac}&refresh=5s&viewPanel=panel-1`; + } + + closeModal(): void { + this.dialogRef.close(); + } + + openGrafanaInNewTab(): void { + window.open(this.grafanaUrl, '_blank'); + } +} \ No newline at end of file -- 2.40.1 From cee95adcb4d9be780b5c20089d0dfde52005c08d Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 5 Aug 2025 10:37:56 +0200 Subject: [PATCH 10/11] refs #2596. Create task options --- ogWebconsole/src/app/app.module.ts | 6 +++- .../deploy-image/deploy-image.component.ts | 19 +++++++--- .../run-script-assistant.component.html | 4 +-- .../run-script-assistant.component.ts | 35 ++++++++++++++----- .../queue-confirmation-modal.component.ts | 2 +- 5 files changed, 50 insertions(+), 16 deletions(-) diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index 607ce23..1690c08 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -160,6 +160,8 @@ import { QueueConfirmationModalComponent } from './shared/queue-confirmation-mod import { ModalOverlayComponent } from './shared/modal-overlay/modal-overlay.component'; import { ScrollToTopComponent } from './shared/scroll-to-top/scroll-to-top.component'; import { CreateTagModalComponent } from './components/repositories/show-git-images/create-tag-modal/create-tag-modal.component'; +import { CreateBranchModalComponent } from './components/repositories/show-git-images/create-branch-modal/create-branch-modal.component'; +import { ClientLogsModalComponent } from './components/groups/shared/client-logs-modal/client-logs-modal.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); @@ -276,7 +278,9 @@ registerLocaleData(localeEs, 'es-ES'); ClientPendingTasksComponent, ModalOverlayComponent, ScrollToTopComponent, - CreateTagModalComponent + CreateTagModalComponent, + CreateBranchModalComponent, + ClientLogsModalComponent ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts index 54265c6..14a0c04 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.ts @@ -421,17 +421,28 @@ export class DeployImageComponent implements OnInit{ openScheduleModal(): void { + let scope = this.runScriptContext.type; + let selectedClients = null; + + + if ((!this.runScriptContext || this.runScriptContext.type === 'client' || this.selectedClients.length === 1) && this.selectedClients && this.selectedClients.length > 0) { + scope = 'clients'; + selectedClients = this.selectedClients; + } + const dialogRef = this.dialog.open(CreateTaskComponent, { width: '800px', data: { - scope: this.runScriptContext.type, - organizationalUnit: this.runScriptContext['@id'], - source: 'assistant' + scope: scope, + selectedClients: selectedClients, + organizationalUnit: this.runScriptContext?.['@id'], + source: 'assistant', + runScriptContext: this.runScriptContext } }); dialogRef.afterClosed().subscribe((result: { [x: string]: any; }) => { - if (result) { + if (result !== undefined) { const payload = { method: this.selectedMethod, diskNumber: this.selectedPartition.diskNumber, diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html index a17794d..7b154c9 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.html @@ -10,11 +10,11 @@
- +
-
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts index b1135d9..61297c8 100644 --- a/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts +++ b/ogWebconsole/src/app/components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component.ts @@ -29,7 +29,7 @@ export class RunScriptAssistantComponent implements OnInit{ parameters: any = {}; selectedScript: any = null; selectedClients: any[] = []; - allSelected: boolean = true; + allSelected: boolean = false; commandType: string = 'existing'; newScript: string = ''; selection = new SelectionModel(true, []); @@ -56,7 +56,11 @@ export class RunScriptAssistantComponent implements OnInit{ this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null; this.clientData.forEach((client: { selected: boolean; status: string}) => { client.selected = true; }); - this.selectedClients = this.clientData.filter((client: { selected: boolean; status: string}) => client.selected); + this.selectedClients = this.clientData.filter( + (client: { selected: boolean; status: string }) => client.selected + ); + + this.allSelected = this.clientData.length > 0 && this.clientData.every((client: { selected: boolean }) => client.selected); this.loadScripts() } @@ -117,12 +121,15 @@ export class RunScriptAssistantComponent implements OnInit{ } updateSelectedClients() { - this.selectedClients = this.clientData.filter((client: { selected: boolean; status: string}) => client.selected); + this.selectedClients = this.clientData.filter( + (client: { selected: boolean; status: string }) => client.selected + ); } toggleSelectAll() { this.allSelected = !this.allSelected; this.clientData.forEach((client: { selected: boolean; status: string }) => { client.selected = this.allSelected; }); + this.updateSelectedClients(); } getPartitionsTooltip(client: any): string { @@ -198,21 +205,33 @@ export class RunScriptAssistantComponent implements OnInit{ } openScheduleModal(): void { + let scope = this.runScriptContext.type; + let selectedClients = null; + + + if ((!this.runScriptContext || this.runScriptContext.type === 'client' || this.selectedClients.length === 1) && this.selectedClients && this.selectedClients.length > 0) { + scope = 'clients'; + selectedClients = this.selectedClients; + } + const dialogRef = this.dialog.open(CreateTaskComponent, { width: '800px', data: { - scope: this.runScriptContext.type, - organizationalUnit: this.runScriptContext['@id'], - source: 'assistant' + scope: scope, + selectedClients: selectedClients, + organizationalUnit: this.runScriptContext?.['@id'], + source: 'assistant', + runScriptContext: this.runScriptContext } }); dialogRef.afterClosed().subscribe(result => { + console.log(result); if (result) { this.http.post(`${this.baseUrl}/command-task-scripts`, { - commandTask: result['@id'], + commandTask: result.taskId['@id'], content: this.commandType === 'existing' ? this.scriptContent : this.newScript, - order: 1, + order: result.executionOrder, type: 'run-script', }).subscribe({ next: () => { diff --git a/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.ts b/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.ts index 2e6571b..722873c 100644 --- a/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.ts +++ b/ogWebconsole/src/app/shared/queue-confirmation-modal/queue-confirmation-modal.component.ts @@ -15,7 +15,7 @@ export class QueueConfirmationModalComponent { ) {} onNoClick(): void { - this.dialogRef.close(false); + this.dialogRef.close(); } onYesClick(): void { -- 2.40.1 From 7e8b121df27866f3b7dd88d8a2608fbda09537de Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Tue, 5 Aug 2025 10:41:57 +0200 Subject: [PATCH 11/11] updated changelog --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4656787..8d66f7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,22 @@ # Changelog +## [0.18.0] - 2025-08-04 +### Added +- Se ha añadido la posibilidad de visualizar logs en tiempo real de Grafana. Tanto en los componentes como en los clientes. +- Se ha añadido la funcionaldad e integracion con OgGit. +- En el particionador, se ha añadido una integracion para comprobar los tamaños de las particiones. + +### Improved +- Sistema de cola de acciones. + +--- +## [0.17.0] - 2025-07-15 +### Added +- Se ha añadido la funcionalidad para tagear commits en el apartado de imágenes git. + +### Improved +- Se ha corregido el particionador, para cuando un equipo es EFI, ahora aparece la primera particion completada. + + ## [0.16.0] - 2025-06-27 ### Added - Sistema de logs en tiempo real. -- 2.40.1