diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts
index a172a3c..67add43 100644
--- a/ogWebconsole/src/app/app.module.ts
+++ b/ogWebconsole/src/app/app.module.ts
@@ -91,6 +91,7 @@ import { MatSliderModule } from '@angular/material/slider';
import { ImagesComponent } from './components/images/images.component';
import { CreateImageComponent } from './components/images/create-image/create-image.component';
import { CreateClientImageComponent } from './components/groups/components/client-main-view/create-image/create-image.component';
+import { CreateRepositoryModalComponent } from './components/groups/components/client-main-view/create-image/create-repository-modal/create-repository-modal.component';
import { PartitionAssistantComponent } from './components/groups/components/client-main-view/partition-assistant/partition-assistant.component';
import { SoftwareComponent } from './components/software/software.component';
import { CreateSoftwareComponent } from './components/software/create-software/create-software.component';
@@ -139,7 +140,7 @@ import {
SaveScriptComponent
} from "./components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component";
import { EditImageComponent } from './components/repositories/edit-image/edit-image.component';
-import { ShowGitImagesComponent } from './components/repositories/show-git-images/show-git-images.component';
+import { ShowGitCommitsComponent } from './components/repositories/show-git-images/show-git-images.component';
import { RenameImageComponent } from './components/repositories/rename-image/rename-image.component';
import { ClientDetailsComponent } from './components/groups/shared/client-details/client-details.component';
import { PartitionTypeOrganizatorComponent } from './components/groups/shared/partition-type-organizator/partition-type-organizator.component';
@@ -154,6 +155,10 @@ import { BootSoPartitionComponent } from './components/commands/main-commands/ex
import { RemoveCacheImageComponent } from './components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component';
import { ChangeParentComponent } from './components/groups/shared/change-parent/change-parent.component';
import { SoftwareProfilePartitionComponent } from './components/commands/main-commands/execute-command/software-profile-partition/software-profile-partition.component';
+import { ClientPendingTasksComponent } from './components/task-logs/client-pending-tasks/client-pending-tasks.component';
+import { QueueConfirmationModalComponent } from './shared/queue-confirmation-modal/queue-confirmation-modal.component';
+import { ModalOverlayComponent } from './shared/modal-overlay/modal-overlay.component';
+import { ScrollToTopComponent } from './shared/scroll-to-top/scroll-to-top.component';
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './locale/', '.json');
@@ -182,6 +187,7 @@ registerLocaleData(localeEs, 'es-ES');
GroupsComponent,
ManageClientComponent,
DeleteModalComponent,
+ QueueConfirmationModalComponent,
ClassroomViewComponent,
ClientViewComponent,
ShowOrganizationalUnitComponent,
@@ -204,6 +210,8 @@ registerLocaleData(localeEs, 'es-ES');
CalendarComponent,
CreateCalendarComponent,
CreateClientImageComponent,
+ CreateRepositoryModalComponent,
+ PartitionAssistantComponent,
CreateCalendarRuleComponent,
CommandsGroupsComponent,
CommandsTaskComponent,
@@ -216,7 +224,6 @@ registerLocaleData(localeEs, 'es-ES');
StatusComponent,
ImagesComponent,
CreateImageComponent,
- PartitionAssistantComponent,
SoftwareComponent,
CreateSoftwareComponent,
SoftwareProfileComponent,
@@ -230,7 +237,6 @@ registerLocaleData(localeEs, 'es-ES');
ExecuteCommandOuComponent,
DeployImageComponent,
MainRepositoryViewComponent,
- ExecuteCommandOuComponent,
EnvVarsComponent,
MenusComponent,
CreateMenuComponent,
@@ -251,7 +257,7 @@ registerLocaleData(localeEs, 'es-ES');
RunScriptAssistantComponent,
SaveScriptComponent,
EditImageComponent,
- ShowGitImagesComponent,
+ ShowGitCommitsComponent,
RenameImageComponent,
ClientDetailsComponent,
PartitionTypeOrganizatorComponent,
@@ -265,7 +271,10 @@ registerLocaleData(localeEs, 'es-ES');
BootSoPartitionComponent,
RemoveCacheImageComponent,
ChangeParentComponent,
- SoftwareProfilePartitionComponent
+ SoftwareProfilePartitionComponent,
+ ClientPendingTasksComponent,
+ ModalOverlayComponent,
+ ScrollToTopComponent
],
bootstrap: [AppComponent],
imports: [BrowserModule,
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 53d269b..d7c8b69 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
@@ -1,105 +1,677 @@
-.title {
- font-size: 24px;
-}
-
-.calendar-button-row {
- display: flex;
- justify-content: flex-start;
- margin-top: 16px;
-}
-
-.divider {
- margin: 20px 0;
-}
-
-.lists-container {
- padding: 16px;
-}
-
-.card.unidad-card {
- height: 100%;
- box-sizing: border-box;
-}
-
-table {
- width: 100%;
- margin-top: 50px;
- background-color: #eaeff6;
-}
-
-.search-container {
- display: flex;
- justify-content: space-between;
- align-items: center;
- width: 100%;
- padding: 0 5px;
- box-sizing: border-box;
-}
-
+/* Contenedor principal modernizado */
.select-container {
- gap: 16px;
+ gap: 24px;
width: 100%;
box-sizing: border-box;
- padding: 20px;
+ padding: 32px;
+ background: white !important;
+ border-radius: 4px;
+ margin: 20px 0;
+ align-items: center;
+}
+
+/* Secciones del formulario */
+.form-section {
+ background: white !important;
+ border-radius: 8px;
+ padding: 24px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ margin-bottom: 20px;
+ border: 1px solid #bbdefb;
+}
+
+.form-section-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ margin-bottom: 20px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.form-section-title mat-icon {
+ color: #2196f3;
}
.selector {
display: flex;
- gap: 16px;
+ gap: 20px;
width: 100%;
- margin-top: 30px;
+ margin-top: 16px;
box-sizing: border-box;
+ align-items: start;
}
.half-width {
flex: 1;
- max-width: 50%;
+ max-width: calc(50% - 10px);
}
-
-.search-string {
- flex: 2;
- padding: 5px;
-}
-
-.search-boolean {
+.full-width {
flex: 1;
- padding: 5px;
+ width: 100%;
}
+/* Header modernizado */
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
- padding: 10px 10px;
- border-bottom: 1px solid #ddd;
-}
-
-.mat-elevation-z8 {
- box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
-}
-
-.paginator-container {
- display: flex;
- justify-content: end;
- margin-bottom: 30px;
+ padding: 24px 32px;
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ margin-bottom: 20px;
}
.header-container-title {
flex-grow: 1;
text-align: left;
- padding-left: 1em;
+}
+
+.header-container-title h2 {
+ margin: 0 0 8px 0;
+ color: #333;
+ font-weight: 600;
+}
+
+/* Estilos modernos para el badge de destino */
+.destination-info {
+ margin-top: 12px;
+}
+
+.destination-badge {
+ display: inline-flex;
+ align-items: center;
+ background: #e3f2fd;
+ color: #1565c0;
+ padding: 12px 16px;
+ border-radius: 12px;
+ border: 1px solid #bbdefb;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+ transition: all 0.2s ease;
+}
+
+.destination-icon {
+ font-size: 20px;
+ width: 20px;
+ height: 20px;
+ margin-right: 12px;
+ color: #1976d2;
+}
+
+.destination-content {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.destination-label {
+ font-size: 11px;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: #1976d2;
+ line-height: 1;
+}
+
+.destination-value {
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1.2;
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: #0d47a1;
}
.button-row {
display: flex;
- padding-right: 1em;
+ gap: 12px;
+ padding-right: 0;
}
+/* Tabla de particiones modernizada */
.partition-table-container {
- background-color: #eaeff6;
- padding: 20px;
+ background: white;
+ padding: 24px;
border-radius: 12px;
- margin-top: 20px;
+ margin-top: 24px;
+}
+
+.partition-table-container h3 {
+ margin: 0 0 20px 0;
+ color: #333;
+ font-weight: 600;
+ font-size: 18px;
+}
+
+.repository-label {
+ font-weight: 500;
+ margin-right: 8px;
+}
+
+mat-chip {
+ margin-top: 8px !important;
+ border-radius: 20px !important;
+}
+
+mat-icon {
+ margin-right: 4px;
+}
+
+/* Botón de crear repositorio modernizado */
+.create-repository-button {
+ 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;
+}
+
+.create-repository-button:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
+}
+
+.create-repository-button mat-icon {
+ font-size: 18px;
+ width: 18px;
+ height: 18px;
+}
+
+/* Botones modernizados */
+.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);
+}
+
+/* Campos de formulario modernizados */
+mat-form-field {
+ margin-bottom: 8px;
+}
+
+::ng-deep .mat-form-field-appearance-fill .mat-form-field-flex {
+ border-radius: 8px;
+ background-color: white !important;
+ border: 1px solid #e9ecef;
+ transition: all 0.3s ease;
+}
+
+::ng-deep .mat-form-field-appearance-fill.mat-focused .mat-form-field-flex {
+ background-color: white;
+ border-color: #2196f3;
+ box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
+}
+
+/* Overlay de carga para creación de repositorio */
+.creating-repository-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background: rgba(0, 0, 0, 0.7);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ z-index: 9999;
+ color: white;
+ backdrop-filter: blur(4px);
+}
+
+.creating-repository-overlay p {
+ margin-top: 16px;
+ font-size: 16px;
+ font-weight: 500;
+}
+
+/* Estilo para hacer el backdrop no clickeable */
+::ng-deep .non-clickable-backdrop {
+ pointer-events: none !important;
+}
+
+/* Responsive design */
+@media (max-width: 768px) {
+ .select-container {
+ padding: 16px;
+ }
+
+ .header-container {
+ padding: 16px;
+ flex-direction: column;
+ gap: 16px;
+ align-items: stretch;
+ }
+
+ .selector {
+ flex-direction: column;
+ gap: 16px;
+ }
+
+ .half-width {
+ max-width: 100%;
+ }
+
+ .create-repository-button {
+ min-width: 100%;
+ margin-left: 0;
+ }
+
+ .button-row {
+ justify-content: center;
+ }
+
+ .destination-badge {
+ padding: 10px 14px;
+ border-radius: 10px;
+ }
+
+ .destination-icon {
+ font-size: 18px;
+ width: 18px;
+ height: 18px;
+ margin-right: 10px;
+ }
+
+ .destination-value {
+ max-width: 150px;
+ font-size: 13px;
+ }
+
+ .destination-label {
+ font-size: 10px;
+ }
+}
+
+/* Estilos para elementos específicos */
+.unit-name {
+ font-weight: 500;
+ color: #2c3e50;
+}
+
+/* Estilos para las opciones de acción Git */
+.git-action-selector {
+ margin: 24px 0;
+ padding: 20px;
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+ border-radius: 12px;
+ border: 1px solid #dee2e6;
+}
+
+.action-chips-container {
+ margin-bottom: 16px;
+}
+
+::ng-deep .action-chip {
+ margin: 8px !important;
+ padding: 12px 20px !important;
+ border-radius: px !important;
+ font-weight: 500 !important;
+ font-size: 14px !important;
+ transition: all 0.3s ease !important;
+ border: 2px solid transparent !important;
+ background: white !important;
+ color: #6c757d !important;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
+ cursor: pointer !important;
+ display: flex !important;
+ align-items: center !important;
+ gap: 8px !important;
+ min-height: 48px !important;
+}
+
+::ng-deep .action-chip:hover {
+ transform: translateY(-2px) !important;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15) !important;
+}
+
+::ng-deep .action-chip.mat-mdc-chip-selected {
+ border-color: #667eea !important;
+ box-shadow: 0 4px 16px rgba(102, 126, 234, 0.2) !important;
+}
+
+::ng-deep .create-chip.mat-mdc-chip-selected {
+ background: linear-gradient(135deg, #28a745 0%, #20c997 100%) !important;
+ color: white !important;
+}
+
+::ng-deep .update-chip.mat-mdc-chip-selected {
+ background: linear-gradient(135deg, #007bff 0%, #0056b3 100%) !important;
+ color: white !important;
+}
+
+::ng-deep .action-chip mat-icon {
+ font-size: 18px !important;
+ width: 18px !important;
+ height: 18px !important;
+}
+
+.action-hint {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px 16px;
+ background: rgba(102, 126, 234, 0.1);
+ border-radius: 8px;
+ border-left: 4px solid #667eea;
+ color: #495057;
+ font-size: 14px;
+}
+
+.action-hint mat-icon {
+ color: #667eea;
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+}
+
+.git-action-section {
+ background: white !important;
+ border-radius: 8px;
+ padding: 16px;
+ margin-top: 16px;
+ border-left: 4px solid #667eea;
+}
+
+/* Eliminar sombra de la tabla */
+.mat-elevation-z8 {
+ box-shadow: none !important;
+}
+
+/* Animaciones para transiciones de formulario */
+.form-transition {
+ transition: all 0.3s ease-in-out;
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.form-transition.ng-enter {
+ opacity: 0;
+ transform: translateY(10px);
+}
+
+.form-transition.ng-enter-active {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.form-transition.ng-leave {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.form-transition.ng-leave-active {
+ opacity: 0;
+ transform: translateY(-10px);
+}
+
+/* Estilos para los formularios específicos */
+.git-form-section {
+ background: white;
+ border-radius: 4px;
+ padding: 20px;
+ margin-top: 16px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+ border: 1px solid #e9ecef;
+ transition: all 0.3s ease;
+}
+
+.git-form-section:hover {
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+}
+
+/* Estilos para la sección de repositorio Git */
+.git-repository-section {
+ background: white !important;
+ border-radius: 8px;
+ padding: 20px;
+ margin-bottom: 20px;
+ border: 1px solid #e9ecef;
+}
+
+.repository-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 16px;
+ padding-bottom: 12px;
+}
+
+.repository-header h4 {
+ margin: 0;
+ color: #2c3e50;
+ font-weight: 600;
+ font-size: 16px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.repository-selector {
+ display: flex;
+ gap: 20px;
+ align-items: flex-start;
+}
+
+.repository-field {
+ flex: 0 0 300px;
+ min-width: 250px;
+}
+
+.repository-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.info-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ color: #6c757d;
+ font-size: 13px;
+ line-height: 1.4;
+}
+
+.info-item mat-icon {
+ color: #667eea;
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+ margin-top: 2px;
+ flex-shrink: 0;
+}
+
+.create-repository-button {
+ background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
+ color: white;
+ border: none;
+ padding: 10px 16px;
+ border-radius: 6px;
+ font-weight: 500;
+ font-size: 14px;
+ transition: all 0.3s ease;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ box-shadow: 0 2px 8px rgba(40, 167, 69, 0.2);
+}
+
+.create-repository-button:hover:not(:disabled) {
+ background: linear-gradient(135deg, #218838 0%, #1ea085 100%);
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
+}
+
+.create-repository-button:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ transform: none;
+}
+
+.create-repository-button mat-icon {
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+}
+
+.no-repositories-hint {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ color: #dc3545;
+ font-weight: 500;
+ font-size: 13px;
+ padding: 4px 4px 4px 12px;
+ background: rgba(220, 53, 69, 0.1);
+ border-radius: 6px;
+ border-left: 3px solid #dc3545;
+ line-height: 1.4;
+}
+
+.no-repositories-hint mat-icon {
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+ margin-top: 2px;
+ flex-shrink: 0;
+}
+
+/* Estilos para el hint del formulario */
+::ng-deep .mat-form-field-hint {
+ display: flex !important;
+ align-items: center !important;
+ gap: 6px !important;
+ color: #6c757d !important;
+ font-size: 12px !important;
+}
+
+::ng-deep .mat-form-field-hint mat-icon {
+ font-size: 14px !important;
+ width: 14px !important;
+ height: 14px !important;
+ color: #667eea !important;
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .header-container {
+ flex-direction: column;
+ gap: 20px;
+ text-align: center;
+ }
+
+ .button-row {
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
+ .selector {
+ flex-direction: column;
+ }
+
+ .half-width {
+ min-width: auto;
+ }
+
+ .clients-grid {
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ }
+
+ .input-group {
+ grid-template-columns: 1fr;
+ }
+
+ .select-container {
+ padding: 0 16px;
+ }
+
+ .form-section {
+ padding: 20px;
+ }
+
+ /* Responsive para repositorio Git */
+ .repository-header {
+ flex-direction: column;
+ gap: 12px;
+ align-items: flex-start;
+ }
+
+ .repository-selector {
+ flex-direction: column;
+ gap: 16px;
+ }
+
+ .repository-field {
+ flex: none;
+ width: 100%;
+ min-width: auto;
+ }
+
+ .repository-info {
+ flex: none;
+ }
+
+ .create-repository-button {
+ width: 100%;
+ justify-content: center;
+ }
+}
+
+/* Botón flotante para scroll hacia arriba */
+.scroll-to-top-button {
+ position: fixed;
+ bottom: 30px;
+ left: 30px;
+ z-index: 1000;
+ transition: all 0.3s ease;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.scroll-to-top-button:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
+}
+
+/* Animación de entrada/salida */
+.scroll-to-top-button {
+ animation: fadeInUp 0.3s ease;
+}
+
+@keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* Responsive para el botón */
+@media (max-width: 768px) {
+ .scroll-to-top-button {
+ bottom: 20px;
+ left: 20px;
+ }
}
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 ea2ed83..5092b02 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,87 +1,205 @@
-
+
+
+
-
-
-
- Tipo de imagen
-
- Monolítica
-
-
-
+
+
-
-
- Nombre canónico
-
-
+
+
+
+
+
+
+
+
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 8c6fc46..bf413eb 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
@@ -1,10 +1,13 @@
-import {Component, EventEmitter, OnInit, Output} from '@angular/core';
+import {Component, EventEmitter, OnInit, Output, ViewChild, ElementRef} from '@angular/core';
import { HttpClient } from "@angular/common/http";
import { ToastrService } from "ngx-toastr";
import { ActivatedRoute, Router } from "@angular/router";
import { MatTableDataSource } from "@angular/material/table";
import { SelectionModel } from "@angular/cdk/collections";
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";
@Component({
selector: 'app-create-image',
@@ -14,18 +17,32 @@ import { ConfigService } from '@services/config.service';
export class CreateClientImageComponent implements OnInit{
baseUrl: string;
@Output() dataChange = new EventEmitter
();
+ @ViewChild('partitionSection', { static: false }) partitionSection!: ElementRef;
errorMessage = '';
clientId: string | null = null;
partitions: any[] = [];
images: any[] = [];
clientName: string = '';
- selectedPartition: any = null;
+ private _selectedPartition: any = null;
name: string = '';
client: any = null;
loading: boolean = false;
selectedImage: any = null;
- imageType : string = 'monolithic';
+ private _imageType: string = 'monolithic';
+ selectedRepository: any = null;
+ gitRepositories: any[] = [];
+ selectedGitRepository: any = null;
+ gitImageRepositories: any[] = [];
+ gitImageName: string = '';
+ 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();
columns = [
{
@@ -69,11 +86,13 @@ export class CreateClientImageComponent implements OnInit{
private configService: ConfigService,
private route: ActivatedRoute,
private router: Router,
+ private dialog: MatDialog
) {
this.baseUrl = this.configService.apiUrl;
}
ngOnInit() {
+ console.log('CreateImageComponent ngOnInit ejecutado');
this.clientId = this.route.snapshot.paramMap.get('id');
this.loadPartitions();
this.loadImages();
@@ -86,6 +105,7 @@ export class CreateClientImageComponent implements OnInit{
if (response.partitions) {
this.client = response;
this.clientName = response.name;
+ this.selectedRepository = response.repository;
this.dataSource.data = response.partitions.filter((partition: any) => {
return partition.partitionNumber !== 0;
@@ -100,7 +120,6 @@ export class CreateClientImageComponent implements OnInit{
onImageTypeSelected(event: any) {
this.imageType = event;
- this.loadImages();
}
loadImages() {
@@ -115,45 +134,285 @@ export class CreateClientImageComponent implements OnInit{
);
}
+ loadGitRepositories() {
+ this.loadingGitRepositories = true;
+ const url = `${this.baseUrl}/git-repositories?repository=${this.selectedRepository.id}&page=1&itemsPerPage=100`;
+ return this.http.get(url).subscribe(
+ (response: any) => {
+ this.gitRepositories = response['hydra:member'];
+ this.loadingGitRepositories = false;
+ },
+ (error) => {
+ console.error('Error al cargar los repositorios git:', error);
+ this.loadingGitRepositories = false;
+ }
+ );
+ }
+
+ loadGitImageRepositories(gitRepository: any) {
+ this.loadingGitImageRepositories = true;
+ const url = `${this.baseUrl}/git-image-repositories?gitRepository.id=${gitRepository.id}&page=1&itemsPerPage=100`;
+ this.http.get(url).subscribe(
+ (response: any) => {
+ this.gitImageRepositories = response['hydra:member'];
+ this.loadingGitImageRepositories = false;
+ },
+ (error) => {
+ console.error('Error al cargar las imágenes de repositorio git:', error);
+ this.loadingGitImageRepositories = false;
+ }
+ );
+ }
+
+ onGitRepositorySelected(gitRepository: any) {
+ this.selectedGitRepository = gitRepository;
+ this.selectedExistingImage = null;
+ this.existingImages = [];
+ if (gitRepository) {
+ this.loadGitImageRepositories(gitRepository);
+ } 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);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ loadExistingImages() {
+ 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(
+ (response: any) => {
+ this.existingImages = response['hydra:member'] || [];
+ this.loadingExistingImages = false;
+ },
+ (error) => {
+ console.error('Error al cargar las imágenes existentes:', error);
+ this.loadingExistingImages = false;
+ this.toastService.error('Error al cargar las imágenes existentes');
+ }
+ );
+ }
+
+ resetGitSelections() {
+ this.selectedGitRepository = null;
+ this.selectedExistingImage = null;
+ this.gitImageName = '';
+ this.gitAction = 'create';
+ this.existingImages = [];
+ this.gitRepositories = [];
+ this.gitImageRepositories = [];
+
+ this.selectedImage = null;
+ this.name = '';
+ this.monolithicAction = 'create';
+ }
+
resetCanonicalName() {
this.name = this.selectedImage ? this.selectedImage.name : '';
}
save(): void {
- this.loading = true;
-
if (!this.selectedPartition) {
this.toastService.error('Debes seleccionar una partición');
- this.loading = false;
return;
}
+ if (this.imageType === 'git') {
+ if (!this.selectedGitRepository) {
+ this.toastService.error('Debes seleccionar un repositorio Git');
+ return;
+ }
+ if (this.gitAction === 'update' && !this.selectedExistingImage) {
+ this.toastService.error('Debes seleccionar un repositorio de imágenes Git');
+ return;
+ }
+ }
+
+ if (this.imageType === 'monolithic') {
+ if (this.monolithicAction === 'create' && !this.name) {
+ this.toastService.error('Debes introducir un nombre canónico para la imagen');
+ return;
+ }
+ if (this.monolithicAction === 'update' && !this.selectedImage) {
+ this.toastService.error('Debes seleccionar una imagen para actualizar');
+ return;
+ }
+ }
+
if (this.selectedImage) {
this.toastService.warning('Aviso: Está seleccionando una imagen previamente creada. Se procede a crear un backup de la misma. ');
}
- const payload = {
- client: `/clients/${this.clientId}`,
- name: this.name,
- partition: this.selectedPartition['@id'],
- source: 'assistant',
- type: this.imageType,
- selectedImage: this.selectedImage?.['@id']
- };
+ const dialogRef = this.dialog.open(QueueConfirmationModalComponent, {
+ width: '400px',
+ disableClose: true,
+ hasBackdrop: true,
+ backdropClass: 'non-clickable-backdrop'
+ });
+ dialogRef.afterClosed().subscribe(result => {
+ if (result !== undefined) {
+ this.loading = true;
+
+ let payload: any = {
+ client: `/clients/${this.clientId}`,
+ partition: this.selectedPartition['@id'],
+ source: 'assistant',
+ type: this.imageType,
+ queue: result
+ };
- this.http.post(`${this.baseUrl}/images`, payload)
- .subscribe({
- next: (response) => {
- this.toastService.success('Petición de creación de imagen enviada');
- this.loading = false;
- this.router.navigate(['/commands-logs']);
- },
- error: (error) => {
- this.toastService.error(error.error['hydra:description']);
- this.loading = false;
+ if (this.imageType === 'git') {
+ payload.gitRepository = this.selectedGitRepository.name
+ payload.name = this.selectedGitRepository.name;
+
+ if (this.gitAction === 'create') {
+ payload.action = 'create';
+ } else {
+ payload.action = 'update';
+ }
+ } else {
+ if (this.monolithicAction === 'create') {
+ payload.name = this.name;
+ payload.action = 'create';
+ } else {
+ payload.selectedImage = this.selectedImage['@id'];
+ payload.action = 'update';
+ }
}
+
+ this.http.post(`${this.baseUrl}/images`, payload)
+ .subscribe({
+ next: (response) => {
+ let actionText = 'creación';
+ if (this.imageType === 'git' && this.gitAction === 'update') {
+ actionText = 'actualización';
+ } else if (this.imageType === 'monolithic' && this.monolithicAction === 'update') {
+ actionText = 'actualización';
+ }
+ this.toastService.success(`Petición de ${actionText} de imagen enviada`);
+ this.loading = false;
+ this.router.navigate(['/commands-logs']);
+ },
+ error: (error) => {
+ this.toastService.error(error.error['hydra:description']);
+ this.loading = false;
+ }
+ });
}
- );
+ });
+ }
+
+ openCreateRepositoryModal(): void {
+ this.creatingRepository = true;
+ const dialogRef = this.dialog.open(CreateRepositoryModalComponent, {
+ width: '600px',
+ disableClose: true,
+ hasBackdrop: true,
+ backdropClass: 'non-clickable-backdrop',
+ data: {
+ clientRepository: this.selectedRepository
+ }
+ });
+
+ dialogRef.afterClosed().subscribe(result => {
+ this.creatingRepository = false;
+ if (result) {
+ this.loadGitRepositories();
+ setTimeout(() => {
+ const newRepository = this.gitRepositories.find(repo => repo['@id'] === result['@id']);
+ if (newRepository) {
+ this.selectedGitRepository = newRepository;
+ this.onGitRepositorySelected(newRepository);
+ }
+ }, 200);
+ }
+ });
+ }
+
+ get imageType(): string {
+ return this._imageType;
+ }
+
+ set imageType(value: string) {
+ this._imageType = value;
+ this.loadImages();
+ if (value === 'git') {
+ this.loadGitRepositories();
+ this.selectedImage = null;
+ this.name = '';
+ this.monolithicAction = 'create';
+ } else {
+ this.resetGitSelections();
+ }
+ }
+
+ onGitImageRepositorySelected(gitImageRepository: any) {
+ this.selectedExistingImage = gitImageRepository;
+ this.existingImages = [];
+
+ if (gitImageRepository) {
+ this.loadExistingImages();
+ }
+ }
+
+ scrollToPartitionSection() {
+ const partitionSection = document.getElementById('partition-selection');
+
+ if (partitionSection) {
+ partitionSection.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start'
+ });
+ }
+ }
+
+ get selectedPartition(): any {
+ return this._selectedPartition;
+ }
+
+ set selectedPartition(value: any) {
+ this._selectedPartition = value;
}
}
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-repository-modal/create-repository-modal.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-repository-modal/create-repository-modal.component.css
new file mode 100644
index 0000000..f2bc788
--- /dev/null
+++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-repository-modal/create-repository-modal.component.css
@@ -0,0 +1,40 @@
+.dialog-content {
+ display: flex;
+ flex-direction: column;
+ padding: 40px;
+}
+
+.repository-form {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.form-field {
+ width: 100%;
+ margin-top: 16px;
+}
+
+.action-container {
+ display: flex;
+ justify-content: flex-end;
+ gap: 1em;
+ padding: 1.5em;
+}
+
+@media (max-width: 600px) {
+ .form-field {
+ width: 100%;
+ }
+
+ .dialog-actions {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ button {
+ width: 100%;
+ margin-left: 0;
+ margin-bottom: 8px;
+ }
+}
\ No newline at end of file
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-repository-modal/create-repository-modal.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-repository-modal/create-repository-modal.component.html
new file mode 100644
index 0000000..8daa4da
--- /dev/null
+++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-repository-modal/create-repository-modal.component.html
@@ -0,0 +1,17 @@
+
+
+Crear nuevo repositorio de imágenes git
+
+
+
+
+
+
+ Cancelar
+ Guardar
+
\ No newline at end of file
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-repository-modal/create-repository-modal.component.ts b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-repository-modal/create-repository-modal.component.ts
new file mode 100644
index 0000000..9fae2af
--- /dev/null
+++ b/ogWebconsole/src/app/components/groups/components/client-main-view/create-image/create-repository-modal/create-repository-modal.component.ts
@@ -0,0 +1,64 @@
+import { Component, OnInit, Inject } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from "@angular/forms";
+import { HttpClient } from "@angular/common/http";
+import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
+import { ToastrService } from "ngx-toastr";
+import { ConfigService } from '@services/config.service';
+
+@Component({
+ selector: 'app-create-repository-modal',
+ templateUrl: './create-repository-modal.component.html',
+ styleUrl: './create-repository-modal.component.css'
+})
+export class CreateRepositoryModalComponent implements OnInit {
+ baseUrl: string;
+ repositoryForm: FormGroup;
+ loading: boolean = false;
+ clientRepository: any = null;
+
+ constructor(
+ private fb: FormBuilder,
+ private http: HttpClient,
+ public dialogRef: MatDialogRef,
+ private toastService: ToastrService,
+ private configService: ConfigService,
+ @Inject(MAT_DIALOG_DATA) public data: any
+ ) {
+ this.baseUrl = this.configService.apiUrl;
+ this.clientRepository = this.data?.clientRepository || null;
+ this.repositoryForm = this.fb.group({
+ name: [null, Validators.required],
+ });
+ }
+
+ ngOnInit() {
+ // El componente se inicializa
+ }
+
+ save(): void {
+ if (this.repositoryForm.valid) {
+ this.loading = true;
+ const payload = {
+ name: this.repositoryForm.value.name,
+ repository: this.clientRepository ? this.clientRepository.id : null
+ };
+
+ this.http.post(`${this.baseUrl}/git-repositories`, payload).subscribe({
+ next: (response) => {
+ this.toastService.success('Repositorio creado correctamente');
+ this.dialogRef.close(response);
+ },
+ error: (error) => {
+ this.toastService.error(error.error?.['hydra:description'] || 'Error al crear el repositorio');
+ 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/groups/components/client-main-view/deploy-image/deploy-image.component.css b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.css
index 69ad087..8cdb3e0 100644
--- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.css
+++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.css
@@ -1,101 +1,140 @@
-
-.divider {
- margin: 20px 0;
-}
-
-table {
- width: 100%;
- margin-top: 50px;
- background-color: #eaeff6;
-}
-
-.search-container {
+.header-container {
display: flex;
justify-content: space-between;
align-items: center;
- width: 100%;
- padding: 0 5px;
- box-sizing: border-box;
+ padding: 24px 32px;
+ background: white;
+ border-radius: 12px;
+ margin-bottom: 20px;
}
-.option-container {
- margin: 20px 0;
- width: 100%;
+.header-container-title {
+ flex-grow: 1;
+ text-align: left;
}
-.deploy-container {
+.header-container-title h2 {
+ margin: 0 0 8px 0;
+ color: #333;
+ font-weight: 600;
+}
+
+.header-container-title h4 {
+ margin: 0;
+ font-size: 16px;
+ opacity: 0.9;
+ font-weight: 400;
+}
+
+.button-row {
display: flex;
- justify-content: space-between;
+ padding-right: 1em;
+ gap: 12px;
align-items: center;
- width: 100%;
- padding: 5px;
- gap: 10px;
}
-.options-container {
- padding: 10px;
+.action-button {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ color: white;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-weight: 500;
+ transition: all 0.3s ease;
+ cursor: pointer;
}
+.action-button:hover:not(:disabled) {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+}
+
+.action-button:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* Contenedor principal */
.select-container {
+ background: white !important;
margin-top: 20px;
align-items: center;
padding: 20px;
box-sizing: border-box;
}
-.input-group {
+/* Secciones del formulario */
+.form-section {
+ background: white !important;
+ border-radius: 16px;
+ padding: 32px;
+ margin-bottom: 24px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+ border: 1px solid #bbdefb;
+}
+
+.form-section-title {
display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 24px;
+ font-size: 20px;
+ font-weight: 600;
+ color: #2c3e50;
+ padding-bottom: 16px;
+ border-bottom: 2px solid #f8f9fa;
+}
+
+.form-section-title mat-icon {
+ color: #667eea;
+ font-size: 24px;
+ width: 24px;
+ height: 24px;
+}
+
+/* Selectores */
+.selector {
+ display: flex;
+ gap: 20px;
+ margin-bottom: 20px;
flex-wrap: wrap;
- gap: 16px;
- margin-top: 20px;
}
-mat-option .unit-name {
- display: block;
-}
-
-.input-field {
- flex: 1 1 calc(33.33% - 16px);
+.half-width {
+ flex: 1;
min-width: 250px;
}
.full-width {
width: 100%;
- margin-bottom: 16px;
}
-.search-string {
- flex: 2;
- padding: 5px;
+/* Campos de formulario */
+mat-form-field {
+ width: 100%;
+ margin-bottom: 8px;
}
-.search-boolean {
- flex: 1;
- padding: 5px;
+::ng-deep .mat-form-field-appearance-fill .mat-form-field-flex {
+ background-color: #f8f9fa;
+ border-radius: 8px;
+ border: 1px solid #e9ecef;
+ transition: all 0.3s ease;
}
-.header-container {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 10px 10px;
- border-bottom: 1px solid #ddd;
-}
-
-.mat-elevation-z8 {
- box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
-}
-
-.paginator-container {
- display: flex;
- justify-content: end;
- margin-bottom: 30px;
+::ng-deep .mat-form-field-appearance-fill.mat-focused .mat-form-field-flex {
+ background-color: white;
+ border-color: #2196f3;
+ box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
}
+/* Grid de clientes */
.clients-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
- gap: 8px;
+ gap: 12px;
+ margin-top: 20px;
}
.client-item {
@@ -103,117 +142,381 @@ mat-option .unit-name {
}
.client-card {
- background: #ffffff;
- border-radius: 6px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
overflow: hidden;
position: relative;
- padding: 8px;
+ padding: 12px;
text-align: center;
cursor: pointer;
- transition: background-color 0.3s, transform 0.2s;
-
- &:hover {
- background-color: #f0f0f0;
- transform: scale(1.02);
- }
+ transition: all 0.3s ease;
+ border: 2px solid transparent;
}
-::ng-deep .custom-tooltip {
- white-space: pre-line !important;
- max-width: 200px;
- background: rgba(0, 0, 0, 0.8);
- color: white;
- padding: 8px;
- border-radius: 4px;
+.client-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+ border-color: #667eea;
}
.selected-client {
- background-color: #a0c2e5 !important; /* Azul */
- color: white !important;
+ background: linear-gradient(135deg, #8fa1f0 0%, #9b7bc8 100%);
+ color: white;
+ border-color: #667eea;
+}
+
+.selected-client .client-name,
+.selected-client .client-ip {
+ color: white;
+}
+
+.client-image {
+ width: 32px;
+ height: 32px;
+ margin-bottom: 8px;
}
.client-details {
- margin-top: 4px;
+ margin-bottom: 12px;
}
.client-name {
- font-size: 0.9em;
+ font-size: 12px;
font-weight: 600;
- color: #333;
- margin-bottom: 5px;
+ color: #2c3e50;
+ margin-bottom: 2px;
+ display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- max-width: 150px;
- display: inline-block;
}
.client-ip {
+ font-size: 10px;
+ color: #6c757d;
display: block;
- font-size: 0.9em;
- color: #666;
-}
-
-.header-container-title {
- flex-grow: 1;
- text-align: left;
- padding-left: 1em;
-}
-
-.button-row {
- display: flex;
- padding-right: 1em;
+ margin-bottom: 1px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
+/* Tabla de particiones */
.partition-table-container {
- background-color: #eaeff6;
- padding: 20px;
+ background: white !important;
border-radius: 12px;
+ padding: 24px;
margin-top: 20px;
}
+table {
+ width: 100%;
+ background: white;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+::ng-deep .mat-table {
+ background: white;
+}
+
+::ng-deep .mat-header-cell {
+ background: white !important;
+ color: #2c3e50;
+ font-weight: 600;
+ padding: 16px;
+}
+
+::ng-deep .mat-cell {
+ padding: 16px;
+ color: #495057;
+}
+
+/* Opciones avanzadas */
+.input-group {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 20px;
+ margin-top: 20px;
+}
+
+.input-field {
+ width: 100%;
+}
+
+/* Mensajes de error */
+.error-message {
+ background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%);
+ color: white;
+ padding: 16px 20px;
+ border-radius: 8px;
+ margin-top: 16px;
+ font-weight: 500;
+ box-shadow: 0 4px 16px rgba(255, 107, 107, 0.3);
+}
+
+/* Instrucciones */
+.instructions-box {
+ margin-bottom: 20px;
+}
+
+.instructions-card {
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+}
+
+::ng-deep .instructions-card .mat-card-title {
+ color: #2c3e50;
+ font-weight: 600;
+ padding: 20px 20px 0 20px;
+}
+
+::ng-deep .instructions-card .mat-card-content {
+ padding: 20px;
+}
+
+.instructions-card pre {
+ background: white !important;
+ padding: 16px;
+ border-radius: 8px;
+ border: 1px solid #e9ecef;
+ overflow-x: auto;
+ font-family: 'Courier New', monospace;
+ font-size: 14px;
+ line-height: 1.5;
+}
+
+/* Tooltip personalizado */
+::ng-deep .custom-tooltip {
+ background: rgba(0, 0, 0, 0.9) !important;
+ color: white !important;
+ padding: 12px !important;
+ border-radius: 8px !important;
+ font-size: 12px !important;
+ max-width: 250px !important;
+ white-space: pre-line !important;
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .header-container {
+ flex-direction: column;
+ gap: 16px;
+ align-items: stretch;
+ }
+
+ .button-row {
+ justify-content: center;
+ }
+
+ .selector {
+ flex-direction: column;
+ }
+
+ .half-width {
+ min-width: auto;
+ }
+
+ .clients-grid {
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ gap: 8px;
+ }
+
+ .client-card {
+ padding: 8px;
+ }
+
+ .client-image {
+ width: 24px;
+ height: 24px;
+ margin-bottom: 6px;
+ }
+
+ .client-name {
+ font-size: 11px;
+ }
+
+ .client-ip {
+ font-size: 9px;
+ }
+
+ .input-group {
+ grid-template-columns: 1fr;
+ }
+
+ .select-container {
+ padding: 0 16px;
+ }
+
+ .form-section {
+ padding: 20px;
+ }
+
+ .destination-badge {
+ padding: 10px 14px;
+ border-radius: 10px;
+ }
+
+ .destination-icon {
+ font-size: 18px;
+ width: 18px;
+ height: 18px;
+ margin-right: 10px;
+ }
+
+ .destination-value {
+ max-width: 150px;
+ font-size: 13px;
+ }
+
+ .destination-label {
+ font-size: 10px;
+ }
+}
+
+/* Estilos para elementos específicos */
+.unit-name {
+ font-weight: 500;
+ color: #2c3e50;
+}
+
+/* Eliminar sombra de la tabla */
+.mat-elevation-z8 {
+ box-shadow: none !important;
+}
+
+/* Estilos para el expansion panel */
+::ng-deep .mat-expansion-panel {
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important;
+ border-radius: 12px !important;
+ margin-bottom: 20px;
+ background: #f7fbff !important;
+ border: 1px solid #bbdefb !important;
+}
+
+::ng-deep .mat-expansion-panel-header {
+ padding: 20px 24px !important;
+ border-radius: 12px !important;
+}
+
+::ng-deep .mat-expansion-panel-header-title {
+ font-weight: 600 !important;
+ color: #2c3e50 !important;
+}
+
+::ng-deep .mat-expansion-panel-header-description {
+ color: #6c757d !important;
+}
+
+/* Otros estilos */
+.divider {
+ margin: 20px 0;
+}
+
.disabled-client {
pointer-events: none;
opacity: 0.5;
}
-.error-message {
- background-color: #de2323;
- padding: 20px;
- border-radius: 12px;
- margin-top: 20px;
- color: white;
- font-weight: bold;
-}
-
-.action-button {
- margin-top: 10px;
- margin-bottom: 10px;
-}
-
.mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
}
-.instructions-box {
- margin-top: 15px;
- background-color: #f5f5f5;
- border: 1px solid #ccc;
- padding: 15px;
- border-radius: 6px;
-}
-
.instructions-textarea textarea {
font-family: monospace;
white-space: pre;
}
-.instructions-card {
- background-color: #f5f5f5;
- box-shadow: none !important;
- margin-top: 15px;
+/* Estilo para hacer el backdrop no clickeable */
+::ng-deep .non-clickable-backdrop {
+ pointer-events: none;
+}
+
+/* Estilos modernos para el badge de destino */
+.destination-info {
+ margin-top: 12px;
+}
+
+.destination-badge {
+ display: inline-flex;
+ align-items: center;
+ background: #e3f2fd;
+ color: #1565c0;
+ padding: 12px 16px;
+ border-radius: 12px;
+ border: 1px solid #bbdefb;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+ transition: all 0.2s ease;
+}
+
+.destination-icon {
+ font-size: 20px;
+ width: 20px;
+ height: 20px;
+ margin-right: 12px;
+ color: #1976d2;
+}
+
+.destination-content {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.destination-label {
+ font-size: 11px;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: #1976d2;
+ line-height: 1;
+}
+
+.destination-value {
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1.2;
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: #0d47a1;
+}
+
+.filters-row {
+ display: flex;
+ gap: 20px;
+ align-items: flex-end;
+ margin-bottom: 20px;
+}
+
+.git-gap {
+ gap: 40px;
+}
+
+.monolithic-row {
+ display: flex;
+ gap: 24px;
+ align-items: flex-end;
+ margin-bottom: 20px;
+}
+
+.monolithic-row .half-width {
+ flex: 1 1 200px;
+ min-width: 200px;
+}
+
+.monolithic-row .full-width {
+ flex: 2;
+}
+
+@media (max-width: 768px) {
+ .monolithic-row .full-width {
+ flex: 2;
+ }
}
diff --git a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html
index 1a5bd76..442a438 100644
--- a/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html
+++ b/ogWebconsole/src/app/components/groups/components/client-main-view/deploy-image/deploy-image.component.html
@@ -5,12 +5,18 @@
{{ 'deployImage' | translate }}
-
- {{ runScriptTitle }}
-
+
+
+
cloud_download
+
+ Destino
+ {{ runScriptTitle }}
+
+
+
- Ejecutar
@@ -21,18 +27,15 @@
-
-
+
- schedule Opciones de programación
+ Opciones de programación
-
-
-
@@ -53,7 +56,7 @@
@@ -88,84 +91,166 @@
-
-
- Tipo de imagen
-
- Monolítica
-
-
-
-
-
-
-
- Seleccione imagen
-
-
- {{ image.name }}
- {{ image.description }}
-
-
-
-
-
- Seleccione método de deploy
-
- {{ method.name }}
-
-
-
-
-
- {{ errorMessage }}
-
-
-
-
-
-
- Instrucciones generadas
-
- close
-
-
-
- {{ ogInstructions }}
-
-
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
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 5ebe993..5e655bb 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
@@ -7,6 +7,7 @@ import { ActivatedRoute, Router } from "@angular/router";
import { ConfigService } from '@services/config.service';
import {CreateTaskComponent} from "../../../../commands/commands-task/create-task/create-task.component";
import {MatDialog} from "@angular/material/dialog";
+import {QueueConfirmationModalComponent} from "../../../../../shared/queue-confirmation-modal/queue-confirmation-modal.component";
@Component({
selector: 'app-deploy-image',
@@ -23,7 +24,7 @@ export class DeployImageComponent implements OnInit{
images: any[] = [];
selectedImage: any = null;
selectedMethod: string | null = null;
- selectedPartition: any = null;
+ private _selectedPartition: any = null;
mcastIp: string = '';
mcastPort: Number = 0;
mcastMode: string = '';
@@ -40,6 +41,8 @@ export class DeployImageComponent implements OnInit{
ogInstructions: string = '';
deployImage: boolean = true;
showInstructions: boolean = false;
+ loadingCommits: boolean = false;
+ selectedGitRepository: string = '';
protected p2pModeOptions = [
{ name: 'Leecher', value: 'leecher' },
@@ -97,6 +100,15 @@ export class DeployImageComponent implements OnInit{
displayedColumns = ['select', ...this.columns.map(column => column.columnDef)];
selection = new SelectionModel(true, []);
+ repositories: string[] = [];
+ loadingRepositories: boolean = false;
+ branches: string[] = [];
+ selectedBranch: string = '';
+ loadingBranches: boolean = false;
+ commits: any[] = [];
+ selectedCommit: any = null;
+ private initialGitLoad = true;
+
constructor(
private http: HttpClient,
private toastService: ToastrService,
@@ -115,18 +127,11 @@ export class DeployImageComponent implements OnInit{
}
});
this.clientId = this.clientData?.length ? this.clientData[0]['@id'] : null;
- this.clientData.forEach((client: { selected: boolean; status: string}) => {
- if (client.status === 'og-live') {
- client.selected = true;
- }
- });
- this.selectedClients = this.clientData.filter(
- (client: { status: string }) => client.status === 'og-live'
- );
+ this.clientData.forEach((client: { selected: boolean; status: string}) => { client.selected = true; });
- this.selectedModelClient = this.clientData.find(
- (client: { status: string }) => client.status === 'og-live'
- ) || null;
+ this.selectedClients = this.clientData.filter((client: { selected: boolean; status: string}) => client.selected);
+
+ this.selectedModelClient = this.clientData.find((client: { selected: boolean; status: string}) => client.selected) || null;
if (this.selectedModelClient) {
this.loadPartitions(this.selectedModelClient);
@@ -141,7 +146,14 @@ export class DeployImageComponent implements OnInit{
onImageTypeSelected(event: any) {
this.imageType = event;
+ if (event === 'git') {
+ this.selectedMethod = null;
+ this.loadGitRepositories();
+ }
this.loadImages();
+ setTimeout(() => {
+ this.scrollToPartitionSection();
+ }, 200);
}
get runScriptTitle(): string {
@@ -211,7 +223,7 @@ export class DeployImageComponent implements OnInit{
this.loadImages();
},
(error) => {
- console.error('Error al cargar los datos completos del cliente:', error);
+ console.error('Error al cargar las particiones:', error);
}
);
} else {
@@ -223,11 +235,7 @@ export class DeployImageComponent implements OnInit{
toggleSelectAll() {
this.allSelected = !this.allSelected;
- this.clientData.forEach((client: { selected: boolean; status: string }) => {
- if (client.status === "og-live") {
- client.selected = this.allSelected;
- }
- });
+ this.clientData.forEach((client: { selected: boolean; status: string }) => { client.selected = this.allSelected; });
}
loadImages() {
@@ -265,90 +273,143 @@ export class DeployImageComponent implements OnInit{
}
}
this.errorMessage = "";
+
+ if (this.selectedMethod) {
+ setTimeout(() => {
+ this.scrollToPartitionSection();
+ }, 300);
+ }
+
return true;
}
save(): void {
- this.loading = true;
-
if (!this.selectedClients.length) {
this.toastService.error('Debe seleccionar al menos un cliente');
- this.loading = false;
return;
}
- if (!this.selectedImage) {
+ if (!this.selectedImage && this.imageType !== 'git') {
this.toastService.error('Debe seleccionar una imagen');
- this.loading = false;
return;
}
- if (!this.selectedMethod) {
+ if (!this.selectedMethod && this.imageType !== 'git') {
this.toastService.error('Debe seleccionar un método');
- this.loading = false;
return;
}
if (!this.selectedPartition) {
this.toastService.error('Debe seleccionar una partición');
- this.loading = false;
return;
}
+ if (this.imageType === 'git' && !this.selectedCommit) {
+ this.toastService.error('Debe seleccionar un commit');
+ return;
+ }
+
+ const dialogRef = this.dialog.open(QueueConfirmationModalComponent, {
+ width: '400px',
+ disableClose: true,
+ hasBackdrop: true,
+ backdropClass: 'non-clickable-backdrop'
+ });
+
this.toastService.info('Preparando petición de despliegue');
- const payload = {
- clients: this.selectedClients.map((client: any) => client.uuid),
- method: this.selectedMethod,
- // partition: this.selectedPartition['@id'],
- diskNumber: this.selectedPartition.diskNumber,
- partitionNumber: this.selectedPartition.partitionNumber,
- p2pMode: this.p2pMode,
- p2pTime: this.p2pTime,
- mcastIp: this.mcastIp,
- mcastPort: this.mcastPort,
- mcastMode: this.mcastMode,
- mcastSpeed: this.mcastSpeed,
- maxTime: this.mcastMaxTime,
- maxClients: this.mcastMaxClients,
- };
+ dialogRef.afterClosed().subscribe(result => {
+ if (result !== undefined) {
+ this.loading = true;
+
+ let payload: any;
+ let url: string;
- this.http.post(`${this.baseUrl}/image-image-repositories/${this.selectedImage.uuid}/deploy-image`, payload)
- .subscribe({
- next: (response) => {
- this.toastService.success('Petición de despliegue enviada correctamente');
- this.loading = false;
- this.router.navigate(['/commands-logs']);
- },
- error: (error) => {
- this.toastService.error(error.error['hydra:description'], 'Se ha detectado un error en el despliegue de imágenes.', {
- "closeButton": true,
- "newestOnTop": false,
- "progressBar": false,
- "positionClass": "toast-bottom-right",
- "timeOut": 0,
- "extendedTimeOut": 0,
- "tapToDismiss": false
- });
- this.loading = false;
+ if (this.imageType === 'git') {
+ payload = {
+ type: 'git',
+ clients: this.selectedClients.map((client: any) => client.uuid),
+ diskNumber: this.selectedPartition.diskNumber,
+ partitionNumber: this.selectedPartition.partitionNumber,
+ repositoryName: this.selectedGitRepository,
+ branch: this.selectedBranch,
+ hexsha: this.selectedCommit.hexsha,
+ queue: result
+ };
+ url = `${this.baseUrl}/git-repositories/deploy-image`;
+ } else {
+ payload = {
+ clients: this.selectedClients.map((client: any) => client.uuid),
+ method: this.selectedMethod,
+ diskNumber: this.selectedPartition.diskNumber,
+ partitionNumber: this.selectedPartition.partitionNumber,
+ p2pMode: this.p2pMode,
+ p2pTime: this.p2pMode === 'seeder' ? this.p2pTime : null,
+ mcastIp: this.mcastIp,
+ mcastPort: this.mcastPort,
+ mcastMode: this.mcastMode,
+ mcastSpeed: this.mcastSpeed,
+ maxTime: this.mcastMaxTime,
+ maxClients: this.mcastMaxClients,
+ type: this.imageType,
+ queue: result
+ };
+ url = `${this.baseUrl}/image-image-repositories/${this.selectedImage.uuid}/deploy-image`;
}
- });
+
+ this.http.post(url, payload)
+ .subscribe({
+ next: (response) => {
+ this.toastService.success('Petición de despliegue enviada correctamente');
+ this.loading = false;
+ this.router.navigate(['/commands-logs']);
+ },
+ error: (error) => {
+ this.toastService.error(error.error['hydra:description'], 'Se ha detectado un error en el despliegue de imágenes.', {
+ "closeButton": true,
+ "newestOnTop": false,
+ "progressBar": false,
+ "positionClass": "toast-bottom-right",
+ "timeOut": 0,
+ "extendedTimeOut": 0,
+ "tapToDismiss": false
+ });
+ this.loading = false;
+ }
+ });
+ }
+ });
}
isFormValid(): boolean {
- if (!this.allSelected || !this.selectedModelClient || !this.selectedImage || !this.selectedMethod || !this.selectedPartition) {
+ if (!this.allSelected || !this.selectedModelClient || !this.selectedPartition) {
return false;
}
- if (this.isMethod('udpcast') || this.isMethod('uftp') || this.isMethod('udpcast-direct')) {
- if (!this.mcastPort || !this.mcastIp || !this.mcastMode || !this.mcastSpeed || !this.mcastMaxClients || !this.mcastMaxTime) {
+ if (this.imageType === 'git') {
+ if (!this.selectedCommit) {
return false;
}
}
- if (this.isMethod('p2p')) {
- if (!this.p2pMode || !this.p2pTime) {
- return false;
+ if (this.imageType !== 'git' && !this.selectedMethod) {
+ return false;
+ }
+
+ if (this.imageType !== 'git') {
+ if (this.isMethod('udpcast') || this.isMethod('uftp') || this.isMethod('udpcast-direct')) {
+ if (!this.mcastPort || !this.mcastIp || !this.mcastMode || !this.mcastSpeed || !this.mcastMaxClients || !this.mcastMaxTime) {
+ return false;
+ }
+ }
+
+ if (this.isMethod('p2p')) {
+ if (!this.p2pMode) {
+ return false;
+ }
+ if (this.p2pMode === 'seeder' && !this.p2pTime) {
+ return false;
+ }
}
}
@@ -372,14 +433,14 @@ export class DeployImageComponent implements OnInit{
method: this.selectedMethod,
diskNumber: this.selectedPartition.diskNumber,
partitionNumber: this.selectedPartition.partitionNumber,
- p2pMode: this.selectedMethod === 'torrent' ? this.p2pMode : null,
- p2pTime: this.selectedMethod === 'torrent' ? this.p2pTime : null,
- mcastIp: this.selectedMethod === 'multicast' ? this.mcastIp : null,
- mcastPort: this.selectedMethod === 'multicast' ? this.mcastPort : null,
- mcastMode: this.selectedMethod === 'multicast' ? this.mcastMode : null,
- mcastSpeed: this.selectedMethod === 'multicast' ? this.mcastSpeed : null,
- maxTime: this.selectedMethod === 'multicast' ? this.mcastMaxTime : null,
- maxClients: this.selectedMethod === 'multicast' ? this.mcastMaxClients : null,
+ p2pMode: this.selectedMethod === 'p2p' ? this.p2pMode : null,
+ p2pTime: this.selectedMethod === 'p2p' && this.p2pMode === 'seeder' ? this.p2pTime : null,
+ mcastIp: this.selectedMethod === 'udpcast' ? this.mcastIp : null,
+ mcastPort: this.selectedMethod === 'udpcast' ? this.mcastPort : null,
+ mcastMode: this.selectedMethod === 'udpcast' ? this.mcastMode : null,
+ mcastSpeed: this.selectedMethod === 'udpcast' ? this.mcastSpeed : null,
+ maxTime: this.selectedMethod === 'udpcast' ? this.mcastMaxTime : null,
+ maxClients: this.selectedMethod === 'udpcast' ? this.mcastMaxClients : null,
};
this.http.post(`${this.baseUrl}/command-task-scripts`, {
@@ -400,37 +461,108 @@ export class DeployImageComponent implements OnInit{
}
generateOgInstructions() {
- let script = '';
- const disk = this.selectedPartition?.disk;
- const partition = this.selectedPartition?.partition;
+ this.showInstructions = true;
+ this.ogInstructions = `og-deploy-image --image ${this.selectedImage.name} --partition ${this.selectedPartition.partitionNumber} --method ${this.selectedMethod}`;
+ }
- let ip = this.selectedImage?.repository?.ip || 'REPO';
- let imgName = this.selectedImage?.canonicalName || '';
- let target = ` ${disk} ${partition}`;
- let log = `ogEcho log session "[0] $MSG_SCRIPTS_TASK_START `;
-
- if (this.deployImage) {
- script = 'deployImage ';
- } else {
- script = 'updateCache ';
- imgName += '.img';
- target = '';
+ scrollToPartitionSection() {
+ const partitionSection = document.getElementById('partition-selection');
+ if (partitionSection) {
+ partitionSection.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start'
+ });
+ console.log('Scroll ejecutado');
}
+ }
- script += `${ip} /${imgName}${target} ${this.selectedMethod}`;
- log += `${script}"\n`;
- script = log + script;
-
- let params = '';
- if (['udpcast', 'uftp', 'udpcast-direct'].includes(
this.selectedMethod)) {
- params = `${this.mcastPort}:${this.mcastMode}:${this.mcastIp}:${this.mcastSpeed}M:${this.mcastMaxClients}:${this.mcastMaxTime}`;
- } else if (this.selectedMethod === 'p2p') {
- params = `${this.p2pMode}:${this.p2pTime}`;
+ scrollToAdvancedOptions() {
+ const advancedOptions = document.getElementById('advanced-options');
+
+ if (advancedOptions) {
+ advancedOptions.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start'
+ });
+ console.log('Scroll hacia opciones avanzadas ejecutado');
}
+ }
- script += ` ${params}`;
+ get selectedPartition(): any {
+ return this._selectedPartition;
+ }
- this.ogInstructions = script;
- this.showInstructions = true
+ set selectedPartition(value: any) {
+ this._selectedPartition = value;
+ }
+
+ loadGitRepositories() {
+ this.loadingRepositories = true;
+ this.http.get(`${this.baseUrl}/image-repositories/server/git/${this.selectedRepository?.uuid}/get-collection`).subscribe(
+ data => {
+ this.repositories = data.repositories || [];
+ this.loadingRepositories = false;
+ if (this.repositories.length > 0) {
+ this.selectedGitRepository = this.repositories[0];
+ this.loadGitBranches();
+ }
+ },
+ error => {
+ this.toastService.error('Error al cargar los repositorios git');
+ this.loadingRepositories = false;
+ }
+ );
+ }
+
+ onGitRepositoryChange(event: any) {
+ this.selectedGitRepository = event;
+ this.selectedBranch = '';
+ this.branches = [];
+ this.commits = [];
+ this.selectedCommit = null;
+ this.loadGitBranches();
+ }
+
+ loadGitBranches() {
+ if (!this.selectedGitRepository) return;
+ this.loadingBranches = true;
+ this.http.post(`${this.baseUrl}/image-repositories/server/git/${this.selectedRepository?.uuid}/branches`, { repositoryName: this.selectedGitRepository }).subscribe(
+ data => {
+ this.branches = data.branches || [];
+ this.loadingBranches = false;
+ if (this.branches.length > 0) {
+ this.selectedBranch = this.branches[0];
+ this.loadGitCommits();
+ }
+ },
+ error => {
+ this.toastService.error('Error al cargar las ramas');
+ this.loadingBranches = false;
+ }
+ );
+ }
+
+ onGitBranchChange() {
+ this.selectedCommit = null;
+ this.commits = [];
+ this.loadGitCommits();
+ }
+
+ loadGitCommits() {
+ if (!this.selectedGitRepository || !this.selectedBranch) return;
+ this.loadingCommits = true;
+ this.http.post(`${this.baseUrl}/image-repositories/server/git/${this.selectedRepository?.uuid}/commits`, {
+ repositoryName: this.selectedGitRepository,
+ branch: this.selectedBranch
+ }).subscribe(
+ data => {
+ this.commits = data.commits || [];
+ this.loadingCommits = false;
+ },
+ error => {
+ this.toastService.error('Error al cargar los commits');
+ this.loadingCommits = false;
+ }
+ );
}
}
diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.html b/ogWebconsole/src/app/components/repositories/repositories.component.html
index e5a9239..ab330e5 100644
--- a/ogWebconsole/src/app/components/repositories/repositories.component.html
+++ b/ogWebconsole/src/app/components/repositories/repositories.component.html
@@ -51,7 +51,6 @@
(click)="openShowMonoliticImagesDialog(repository)">
{{ 'monolithicImage' | translate }}
-
+
diff --git a/ogWebconsole/src/app/components/repositories/repositories.component.ts b/ogWebconsole/src/app/components/repositories/repositories.component.ts
index 1c2dd73..82b5338 100644
--- a/ogWebconsole/src/app/components/repositories/repositories.component.ts
+++ b/ogWebconsole/src/app/components/repositories/repositories.component.ts
@@ -10,7 +10,7 @@ import { Router } from '@angular/router';
import { ConfigService } from '@services/config.service';
import {Subnet} from "../ogdhcp/og-dhcp-subnets.component";
import {ShowMonoliticImagesComponent} from "./show-monolitic-images/show-monolitic-images.component";
-import {ShowGitImagesComponent} from "./show-git-images/show-git-images.component";
+import {ShowGitCommitsComponent} from "./show-git-images/show-git-images.component";
import {ManageRepositoryComponent} from "./manage-repository/manage-repository.component";
@Component({
@@ -146,7 +146,7 @@ export class RepositoriesComponent implements OnInit {
}
openShowGitImagesDialog(repository: Subnet) {
- const dialogRef = this.dialog.open(ShowGitImagesComponent, {
+ const dialogRef = this.dialog.open(ShowGitCommitsComponent, {
width: '85vw',
height: '85vh',
maxWidth: '85vw',
diff --git a/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.css b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.css
index 45b503c..c2c252a 100644
--- a/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.css
+++ b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.css
@@ -98,3 +98,96 @@ table {
gap: 1em;
padding: 1.5em;
}
+
+/* Estilos específicos para commits */
+.repository-selector-container {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 20px;
+}
+
+.branch-selector-container {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 20px;
+}
+
+.commit-id {
+ font-family: 'Courier New', monospace;
+ background-color: #f5f5f5;
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-size: 0.9em;
+}
+
+.commit-message {
+ max-width: 300px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.commit-stats {
+ font-size: 0.85em;
+ color: #666;
+}
+
+.commit-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+}
+
+.commit-tags .mat-chip {
+ font-size: 0.8em;
+ height: 24px;
+}
+
+.no-tags {
+ color: #999;
+ font-style: italic;
+ font-size: 0.9em;
+}
+
+/* Mejoras en la tabla */
+.mat-mdc-table {
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.mat-mdc-header-cell {
+ background-color: #f8f9fa;
+ font-weight: 600;
+ color: #495057;
+}
+
+.mat-mdc-row:hover {
+ background-color: #f8f9fa;
+}
+
+/* Estilos para los botones de acción */
+.action-buttons {
+ display: flex;
+ gap: 4px;
+ justify-content: center;
+}
+
+.action-buttons .mat-mdc-icon-button {
+ width: 32px;
+ height: 32px;
+ line-height: 32px;
+}
+
+.filters-row {
+ display: flex;
+ gap: 20px;
+ align-items: flex-end;
+ margin-bottom: 20px;
+}
+
+.repository-selector-container,
+.branch-selector-container {
+ margin-bottom: 0 !important;
+}
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 e591807..2b7612a 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
@@ -6,96 +6,96 @@
help
- Gestionar imágenes git en {{data.repositoryName}}
+ Commits de Git en {{data.repositoryName}}
Ver Información
- Sincronizar base de datos
-
- {{ 'importImageButton' | translate }}
-
-
-
- {{ 'searchLabel' | translate }}
-
- search
- {{ 'searchHint' | translate }}
-
-
- Estado
-
- Fallido
- Pendiente
- Transfiriendo
- Creado con éxito
- En progreso
- Papelera
- Creando archivos auxiliares
+
+
+ Seleccionar Repositorio
+
+
+ {{ repo }}
+
+ hourglass_empty
+
+
+
+ Seleccionar Rama
+
+
+ {{ branch }}
+
+
+ hourglass_empty
-
+
+
+ Buscar commits
+
+ search
+ Buscar por mensaje del commit
+
+
+
+
{{ column.header }}
-
-
-
- {{ image.isGlobal ? 'Sí' : 'No' }}
-
+
+
+
+ {{ column.cell(commit) }}
+
-
-
- {{ getStatusLabel(image[column.columnDef]) }}
-
+
+
+ {{ column.cell(commit) }}
+
-
- {{ column.cell(image) }}
+
+
+ {{ column.cell(commit) }}
+
+
+
+
+
+ {{ tag }}
+
+
+ Sin tags
+
+
+
+
+ {{ column.cell(commit) }}
Acciones
-
-
- visibility
-
+
+
diff --git a/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.spec.ts b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.spec.ts
index 187a9b5..63b3eff 100644
--- a/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.spec.ts
+++ b/ogWebconsole/src/app/components/repositories/show-git-images/show-git-images.component.spec.ts
@@ -1,5 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ShowGitImagesComponent } from './show-git-images.component';
+import { ShowGitCommitsComponent } from './show-git-images.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ToastrModule } from 'ngx-toastr';
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
@@ -17,9 +17,9 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
-describe('ShowGitImagesComponent', () => {
- let component: ShowGitImagesComponent;
- let fixture: ComponentFixture;
+describe('ShowGitCommitsComponent', () => {
+ let component: ShowGitCommitsComponent;
+ let fixture: ComponentFixture;
beforeEach(async () => {
const mockConfigService = {
@@ -27,7 +27,7 @@ describe('ShowGitImagesComponent', () => {
};
await TestBed.configureTestingModule({
- declarations: [ShowGitImagesComponent, LoadingComponent],
+ declarations: [ShowGitCommitsComponent, LoadingComponent],
imports: [
HttpClientTestingModule,
ToastrModule.forRoot(),
@@ -52,7 +52,7 @@ describe('ShowGitImagesComponent', () => {
})
.compileComponents();
- fixture = TestBed.createComponent(ShowGitImagesComponent);
+ fixture = TestBed.createComponent(ShowGitCommitsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
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 45728b9..b7a368b 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
@@ -16,12 +16,12 @@ import {BackupImageComponent} from "../backup-image/backup-image.component";
import {EditImageComponent} from "../edit-image/edit-image.component";
@Component({
- selector: 'app-show-git-images',
+ selector: 'app-show-git-commits',
templateUrl: './show-git-images.component.html',
styleUrl: './show-git-images.component.css'
})
-export class ShowGitImagesComponent implements OnInit{
-baseUrl: string;
+export class ShowGitCommitsComponent implements OnInit{
+ baseUrl: string;
private apiUrl: string;
dataSource = new MatTableDataSource();
length: number = 0;
@@ -32,46 +32,54 @@ baseUrl: string;
alertMessage: string | null = null;
repository: any = {};
datePipe: DatePipe = new DatePipe('es-ES');
+
+ // Nuevas propiedades para manejar branches
+ branches: string[] = [];
+ selectedBranch: string = '';
+ loadingBranches: boolean = false;
+
+ // Nuevas propiedades para manejar repositorios
+ repositories: string[] = [];
+ selectedRepository: string = '';
+ loadingRepositories: boolean = false;
+
+ private initialLoad = true;
+
columns = [
{
- columnDef: 'id',
- header: 'Id',
- cell: (image: any) => `${image.id}`
+ columnDef: 'hexsha',
+ header: 'Commit ID',
+ cell: (commit: any) => commit.hexsha
},
{
- columnDef: 'repositoryName',
- header: 'Nombre del repositorio',
- cell: (image: any) => image.image?.name
+ columnDef: 'message',
+ header: 'Mensaje del commit',
+ cell: (commit: any) => commit.message
},
{
- columnDef: 'name',
- header: 'Nombre de imagen',
- cell: (image: any) => image.name
+ columnDef: 'committed_date',
+ header: 'Fecha del commit',
+ cell: (commit: any) => `${this.datePipe.transform(commit.committed_date * 1000, 'dd/MM/yyyy hh:mm:ss')}`
},
{
- columnDef: 'tag',
- header: 'Tag',
- cell: (image: any) => image.tag
+ columnDef: 'size',
+ header: 'Tamaño',
+ cell: (commit: any) => `${commit.size} bytes`
},
{
- columnDef: 'isGlobal',
- header: 'Imagen global',
- cell: (image: any) => image.image?.isGlobal
+ columnDef: 'stats_total',
+ header: 'Estadísticas',
+ cell: (commit: any) => {
+ if (commit.stats_total) {
+ return `+${commit.stats_total.insertions} -${commit.stats_total.deletions} (${commit.stats_total.files} archivos)`;
+ }
+ return '';
+ }
},
{
- columnDef: 'status',
- header: 'Estado',
- cell: (image: any) => image.status
- },
- {
- columnDef: 'description',
- header: 'Descripción',
- cell: (image: any) => image.description
- },
- {
- columnDef: 'createdAt',
- header: 'Fecha de creación',
- cell: (image: any) => `${this.datePipe.transform(image.createdAt, 'dd/MM/yyyy hh:mm:ss')}`
+ columnDef: 'tags',
+ header: 'Tags',
+ cell: (commit: any) => commit.tags?.length > 0 ? commit.tags.join(', ') : 'Sin tags'
}
];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
@@ -83,214 +91,131 @@ baseUrl: string;
private joyrideService: JoyrideService,
private configService: ConfigService,
private router: Router,
- public dialogRef: MatDialogRef,
+ public dialogRef: MatDialogRef,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.baseUrl = this.configService.apiUrl;
- this.apiUrl = `${this.baseUrl}/git-image-repositories`;
+ this.apiUrl = `${this.baseUrl}/image-repositories/server/git/${this.data.repositoryUuid}`;
}
ngOnInit(): void {
if (this.data) {
- this.loadData();
+ this.loadRepositories();
}
}
+ loadRepositories(): void {
+ this.loadingRepositories = true;
+ this.http.get(`${this.apiUrl}/get-collection`).subscribe(
+ data => {
+ this.repositories = data.repositories || [];
+ this.loadingRepositories = false;
+ if (this.repositories.length > 0) {
+ this.selectedRepository = this.repositories[0];
+ this.loadBranches();
+ }
+ },
+ error => {
+ console.error('Error fetching repositories', error);
+ this.toastService.error('Error al cargar los repositorios');
+ this.loadingRepositories = false;
+ }
+ );
+ }
+
+ onRepositoryChange(): void {
+ this.selectedBranch = '';
+ this.branches = [];
+ this.page = 0;
+ this.loadBranches();
+ }
+
+ loadBranches(): void {
+ if (!this.selectedRepository) {
+ return;
+ }
+ this.loadingBranches = true;
+ this.http.post(`${this.apiUrl}/branches`, { repositoryName: this.selectedRepository }).subscribe(
+ data => {
+ this.branches = data.branches || [];
+ this.loadingBranches = false;
+ if (this.branches.length > 0) {
+ this.selectedBranch = this.branches[0];
+ this.loadData();
+ if (this.initialLoad) {
+ this.initialLoad = false;
+ }
+ }
+ },
+ error => {
+ console.error('Error fetching branches', error);
+ this.toastService.error('Error al cargar las ramas del repositorio');
+ this.loadingBranches = false;
+ }
+ );
+ }
+
+ onBranchChange(): void {
+ this.page = 0; // Resetear a la primera página
+ this.loadData();
+ }
+
loadData(): void {
+ if (!this.selectedBranch || !this.selectedRepository) {
+ return;
+ }
+
this.loading = true;
- this.http.get(`${this.apiUrl}?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}&repository.id=${this.data.repositoryId}`, { params: this.filters }).subscribe(
+ const payload = {
+ repositoryName: this.selectedRepository,
+ branch: this.selectedBranch
+ };
+
+ this.http.post(`${this.apiUrl}/commits`, payload).subscribe(
data => {
- this.dataSource.data = data['hydra:member'];
- this.length = data['hydra:totalItems'];
+ this.dataSource.data = data.commits || [];
+ this.length = data.commits?.length || 0;
this.loading = false;
},
error => {
- console.error('Error fetching image repositories', error);
+ console.error('Error fetching commits', error);
+ this.toastService.error('Error al cargar los commits');
+ this.loading = false;
}
- )
- }
-
- getStatusLabel(status: string): string {
- switch (status) {
- case 'pending':
- return 'Pendiente';
- case 'in-progress':
- return 'En progreso';
- case 'aux-files-pending':
- return 'Archivos auxiliares pendientes';
- case 'success':
- return 'Creado con éxito';
- case 'trash':
- return 'Papelera temporal';
- case 'failed':
- return 'Fallido';
- case 'transferring':
- return 'Transfiriendo';
- default:
- return 'Estado desconocido';
- }
+ );
}
onPageChange(event: any): void {
this.page = event.pageIndex;
this.itemsPerPage = event.pageSize;
this.length = event.length;
- this.loadData();
+ // Para commits, no necesitamos paginación del servidor ya que obtenemos todos los commits
+ // La paginación se maneja en el cliente
}
- loadImageAlert(image: any): Observable {
- return this.http.get(`${this.apiUrl}/server/${image.uuid}/get`, {});
- }
-
- importImage(): void {
- this.dialog.open(ImportImageComponent, {
- width: '600px',
- data: {
- repositoryUuid: this.data.repositoryUuid,
- name: this.data.repositoryName
- }
- }).afterClosed().subscribe((result) => {
- if (result) {
- this.loadData();
- }
- });
- }
-
- toggleAction(image: any, action:string): void {
+ toggleAction(commit: any, action: string): void {
switch (action) {
- case 'delete-trash':
- if (!image.imageFullsum) {
- const dialogRef = this.dialog.open(DeleteModalComponent, {
- width: '400px',
- data: { name: image.name },
- });
-
- dialogRef.afterClosed().subscribe((result) => {
- this.http.delete(`${this.baseUrl}${image['@id']}`).subscribe({
- next: () => {
- this.toastService.success('Image deleted successfully');
- this.loadData()
- },
- error: (error) => {
- this.toastService.error('Error deleting image');
- }
- });
- });
- } else {
- this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/delete-trash`,
- { repository: `/image-repositories/${this.data.repositoryUuid}` })
- .subscribe({
- next: () => {
- this.toastService.success('Petición de eliminación de la papelera temporal enviada');
- this.loadData()
- },
- error: (error) => {
- this.toastService.error(error.error['hydra:description']);
- }
- });
- }
-
- break;
- case 'delete-permanent':
- this.dialog.open(DeleteModalComponent, {
- width: '300px',
- data: { name: image.name },
- }).afterClosed().subscribe((result) => {
- if (result) {
- this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/delete-permanent`, {}).subscribe({
- next: () => {
- this.toastService.success('Petición de eliminación de la papelera temporal enviada');
- this.loadData()
- },
- error: (error) => {
- this.toastService.error(error.error['hydra:description']);
- }
- });
- }
- });
- break;
- case 'edit':
- this.dialog.open(EditImageComponent, {
- width: '600px',
+ case 'view-details':
+ this.dialog.open(ServerInfoDialogComponent, {
+ width: '800px',
data: {
- image: image,
- }
- }).afterClosed().subscribe((result) => {
- if (result) {
- this.loadData();
+ title: 'Detalles del Commit',
+ content: {
+ 'Commit ID': commit.hexsha,
+ 'Mensaje': commit.message,
+ 'Fecha': this.datePipe.transform(commit.committed_date * 1000, 'dd/MM/yyyy hh:mm:ss'),
+ 'Tamaño': `${commit.size} bytes`,
+ 'Archivos modificados': commit.stats_total?.files || 0,
+ 'Líneas añadidas': commit.stats_total?.insertions || 0,
+ 'Líneas eliminadas': commit.stats_total?.deletions || 0,
+ 'Tags': commit.tags?.join(', ') || 'Sin tags'
+ }
}
});
break;
- case 'recover':
- this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/recover`, {}).subscribe({
- next: () => {
- this.toastService.success('Petición de recuperación de la imagen enviada');
- this.loadData()
- },
- error: (error) => {
- this.toastService.error(error.error['hydra:description']);
- }
- });
- break;
- case 'transfer':
- this.http.get(`${this.baseUrl}${image.image['@id']}`).subscribe({
- next: (response) => {
- this.dialog.open(ExportImageComponent, {
- width: '600px',
- data: {
- image: response,
- imageImageRepository: image
- }
- });
- },
- error: (error) => {
- this.toastService.error(error.error['hydra:description']);
- }
- });
- break;
- case 'transfer-global':
- this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/transfer-global`, {
- }).subscribe({
- next: (response) => {
- this.toastService.success('Petición de exportación de imagen realizada correctamente');
- this.loading = false;
- this.router.navigate(['/commands-logs']);
- },
- error: error => {
- this.loading = false;
- this.toastService.error('Error en la petición de exportación de imagen');
- }
- });
- break;
- case 'backup':
- this.http.get(`${this.baseUrl}${image.image['@id']}`).subscribe({
- next: (response) => {
- this.dialog.open(BackupImageComponent, {
- width: '600px',
- data: {
- image: response,
- imageImageRepository: image
- }
- });
- },
- error: (error) => {
- this.toastService.error(error.error['hydra:description']);
- }
- });
- break;
- case 'show-tags':
- this.http.get(`${this.baseUrl}/git-image-repositories/server/${image.uuid}/get-tags`, {}).subscribe({
- next: (response) => {
- this.dialog.open(ServerInfoDialogComponent, {
- width: '800px',
- data: {
- repositories: response
- }
- });
- },
- error: (error) => {
- this.toastService.error(error.error['hydra:description']);
- }
+ case 'copy-commit-id':
+ navigator.clipboard.writeText(commit.hexsha).then(() => {
+ this.toastService.success('Commit ID copiado al portapapeles');
});
break;
default:
@@ -302,55 +227,41 @@ baseUrl: string;
iniciarTour(): void {
this.joyrideService.startTour({
steps: [
- 'imagesTitleStep',
- 'addImageButton',
- 'searchImagesField',
- 'imagesTable',
+ 'commitsTitleStep',
+ 'repositorySelector',
+ 'branchSelector',
+ 'searchCommitsField',
+ 'commitsTable',
'actionsHeader',
- 'editImageButton',
- 'deleteImageButton',
- 'imagesPagination'
+ 'viewCommitButton',
+ 'copyCommitButton',
+ 'commitsPagination'
],
showPrevButton: true,
themeColor: '#3f51b5'
});
}
- loadAlert(): Observable {
- return this.http.post(`${this.baseUrl}/image-repositories/server/git/${this.data.repositoryUuid}/get-collection`, {});
- }
-
- syncRepository() {
- this.http.post(`${this.baseUrl}/image-repositories/server/git/${this.data.repositoryUuid}/sync`, {})
- .subscribe(response => {
- this.toastService.success('Sincronización completada');
- this.loadData()
- }, error => {
- console.error('Error al sincronizar', error);
- this.toastService.error('Error al sincronizar');
- });
- }
-
openImageInfoDialog() {
- this.loadAlert().subscribe(
- response => {
- this.alertMessage = response.repositories;
-
- this.dialog.open(ServerInfoDialogComponent, {
- width: '800px',
- data: {
- repositories: this.alertMessage
- }
- });
- },
- error => {
- console.error('Error al cargar la información del alert', error);
+ this.dialog.open(ServerInfoDialogComponent, {
+ width: '800px',
+ data: {
+ title: 'Información del Repositorio',
+ content: {
+ 'Nombre del repositorio': this.selectedRepository || 'No seleccionado',
+ 'UUID del repositorio': this.data.repositoryUuid,
+ 'Rama seleccionada': this.selectedBranch || 'No seleccionada',
+ 'Total de repositorios': this.repositories.length,
+ 'Total de ramas': this.branches.length,
+ 'Total de commits': this.length
+ }
}
- );
+ });
}
- goToPage( image: any) {
- window.location.href = `http://192.168.68.20:3000/oggit/${image.image.name}`;
+ goToPage(commit: any) {
+ // Abrir el commit en una nueva pestaña (puedes adaptar la URL según tu necesidad)
+ window.open(`http://localhost:3100/oggit/${this.selectedRepository}/commit/${commit.hexsha}`, '_blank');
}
onNoClick(): void {
diff --git a/ogWebconsole/src/locale/en.json b/ogWebconsole/src/locale/en.json
index 6e91915..c327f13 100644
--- a/ogWebconsole/src/locale/en.json
+++ b/ogWebconsole/src/locale/en.json
@@ -543,5 +543,42 @@
"clientMacOS": "MacOS client",
"clientOgLive": "OGLive client",
"clientWindowsSession": "Windows session client",
- "clientWindows": "Windows client"
+ "clientWindows": "Windows client",
+ "exportCSV": "Export CSV",
+ "totalTraces": "Total traces",
+ "todayTraces": "Executed today",
+ "successful": "Successful",
+ "failed": "Failed",
+ "inProgress": "In progress",
+ "showAdvanced": "Show advanced",
+ "hideAdvanced": "Hide advanced",
+ "fromDate": "From",
+ "toDate": "To",
+ "sortBy": "Sort by",
+ "executionDate": "Execution date",
+ "command": "Command",
+ "client": "Client",
+ "showingResults": "Showing {{from}} to {{to}} of {{total}} results",
+ "refresh": "Refresh",
+ "autoRefresh": "Auto-refresh",
+ "viewInput": "View input",
+ "viewOutput": "View output",
+ "deleteTrace": "Delete trace",
+ "cancelTrace": "Cancel trace",
+ "enterClientName": "Please enter the client name",
+ "organizationalUnits": "Organizational Units",
+ "totalEquipments": "Total Equipment",
+ "onlineEquipments": "Online Equipment",
+ "offlineEquipments": "Offline Equipment",
+ "busyEquipments": "Busy Equipment",
+ "pending": "Pending",
+ "cancelled": "Cancelled",
+ "cancelImageTransmission": "Cancel image transmission",
+ "success": "Success",
+ "limpiarAcciones": "Clear actions",
+ "totalClients": "Total clients",
+ "offline": "Offline",
+ "online": "Online",
+ "busy": "Busy",
+ "cancelTask": "Cancel task"
}
diff --git a/ogWebconsole/src/locale/es.json b/ogWebconsole/src/locale/es.json
index 16c0409..9c7f9c1 100644
--- a/ogWebconsole/src/locale/es.json
+++ b/ogWebconsole/src/locale/es.json
@@ -546,5 +546,45 @@
"clientMacOS": "Cliente MacOS",
"clientOgLive": "Cliente OGLive",
"clientWindowsSession": "Cliente con sesión Windows",
- "clientWindows": "Cliente Windows"
+ "clientWindows": "Cliente Windows",
+ "colaAcciones": "Cola de acciones",
+ "exportCSV": "Exportar CSV",
+ "totalTraces": "Total de trazas",
+ "todayTraces": "Ejecutadas hoy",
+ "successful": "Exitoso",
+ "failed": "Fallido",
+ "inProgress": "En progreso",
+ "showAdvanced": "Mostrar avanzados",
+ "hideAdvanced": "Ocultar avanzados",
+ "fromDate": "Desde",
+ "toDate": "Hasta",
+ "sortBy": "Ordenar por",
+ "executionDate": "Fecha de ejecución",
+ "command": "Comando",
+ "client": "Cliente",
+ "showingResults": "Mostrando {{from}} a {{to}} de {{total}} resultados",
+ "refresh": "Actualizar",
+ "autoRefresh": "Auto-actualizar",
+ "viewInput": "Ver entrada",
+ "viewOutput": "Ver salida",
+ "deleteTrace": "Eliminar traza",
+ "cancelTrace": "Cancelar traza",
+ "enterClientName": "Por favor, ingrese el nombre del cliente",
+ "organizationalUnits": "Unidades Organizacionales",
+ "totalEquipments": "Total de Equipos",
+ "onlineEquipments": "Equipos Online",
+ "offlineEquipments": "Equipos Offline",
+ "busyEquipments": "Equipos Ocupados",
+ "organizationalStructure": "Estructura Organizacional",
+ "pending": "Pendiente",
+ "cancelled": "Cancelado",
+ "cancelImageTransmission": "Cancelar transmisión de imagen",
+ "success": "Exitoso",
+ "limpiarAcciones": "Limpiar acciones",
+ "totalClients": "Total de clientes",
+ "offline": "Offline",
+ "online": "Online",
+ "busy": "Ocupado",
+ "cancelTask": "Cancelar tarea"
}
+
\ No newline at end of file
diff --git a/ogWebconsole/src/styles.css b/ogWebconsole/src/styles.css
index 1612d8e..d399c19 100644
--- a/ogWebconsole/src/styles.css
+++ b/ogWebconsole/src/styles.css
@@ -10,6 +10,58 @@ body {
font-family: Roboto, "Helvetica Neue", sans-serif;
}
+/* Estilos globales para asegurar que el botón del sidebar sea visible */
+.sidebar-toggle-button {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ min-width: 48px !important;
+ height: 48px !important;
+ border-radius: 50% !important;
+ transition: all 0.3s ease !important;
+ color: #3f51b5 !important;
+ background-color: #ff0000 !important; /* Fondo rojo para hacerlo visible */
+ border: 2px solid #000 !important; /* Borde negro para hacerlo visible */
+ cursor: pointer !important;
+ z-index: 1001 !important;
+ position: relative !important;
+ opacity: 1 !important;
+ visibility: visible !important;
+}
+
+.sidebar-toggle-button:hover {
+ background-color: rgba(63, 81, 181, 0.1) !important;
+ transform: scale(1.05) !important;
+ box-shadow: 0 2px 8px rgba(63, 81, 181, 0.2) !important;
+}
+
+.sidebar-toggle-button mat-icon {
+ color: #ffffff !important; /* Color blanco para que sea visible sobre el fondo rojo */
+ font-size: 24px !important;
+ width: 24px !important;
+ height: 24px !important;
+ line-height: 24px !important;
+}
+
+/* Asegurar que todos los botones mat-icon-button en el toolbar sean visibles */
+mat-toolbar button[mat-icon-button] {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ min-width: 48px !important;
+ height: 48px !important;
+ border-radius: 50% !important;
+ transition: all 0.3s ease !important;
+ color: #3f51b5 !important;
+ background-color: transparent !important;
+ border: none !important;
+ cursor: pointer !important;
+ z-index: 1001 !important;
+ position: relative !important;
+ flex-shrink: 0 !important;
+ opacity: 1 !important;
+ visibility: visible !important;
+}
/* Clase general para el contenedor de carga */
.loading-container {
@@ -101,3 +153,47 @@ body {
gap: 1em;
padding: 1.5em;
}
+
+/* Overlay blur reutilizable para modales */
+.modal-overlay-blur {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background: rgba(0, 0, 0, 0.7);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ z-index: 9999;
+ color: white;
+ backdrop-filter: blur(4px);
+ -webkit-backdrop-filter: blur(4px); /* Para Safari */
+}
+
+.modal-overlay-blur p {
+ margin-top: 16px;
+ font-size: 16px;
+ font-weight: 500;
+}
+
+.modal-overlay-blur .spinner-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+}
+
+/* Variantes del overlay */
+.modal-overlay-blur.success {
+ background: rgba(40, 167, 69, 0.8);
+}
+
+.modal-overlay-blur.warning {
+ background: rgba(255, 193, 7, 0.8);
+}
+
+.modal-overlay-blur.error {
+ background: rgba(220, 53, 69, 0.8);
+}