refs #1984. Integration ogGit. Crete and deploy Image. Show git images in repository
testing/ogGui-multibranch/pipeline/head There was a failure building this commit
Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit
Details
parent
9216137dee
commit
a1c2fb7c2e
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,87 +1,205 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
<!-- Overlay de carga para creación de repositorio -->
|
||||
<app-modal-overlay
|
||||
[isVisible]="creatingRepository"
|
||||
message="Creando repositorio...">
|
||||
</app-modal-overlay>
|
||||
|
||||
<div class="header-container">
|
||||
<div class="header-container-title">
|
||||
<h2 >
|
||||
<h2>
|
||||
Crear imagen desde {{ clientName }}
|
||||
</h2>
|
||||
<div class="destination-info">
|
||||
<div class="destination-badge">
|
||||
<mat-icon class="destination-icon">cloud_upload</mat-icon>
|
||||
<div class="destination-content">
|
||||
<span class="destination-label">Destino</span>
|
||||
<span class="destination-value">{{ selectedRepository?.name || 'No hay repositorio asociado' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button class="action-button" [disabled]="!selectedPartition" (click)="save()">Ejecutar</button>
|
||||
<button class="action-button" id="execute-button" [disabled]="!selectedPartition" (click)="save()">Ejecutar</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<div class="select-container">
|
||||
<div class="selector">
|
||||
<mat-form-field appearance="fill" class="half-width">
|
||||
<mat-label>Tipo de imagen</mat-label>
|
||||
<mat-select [(ngModel)]="imageType" class="full-width" (selectionChange)="onImageTypeSelected($event.value)">
|
||||
<mat-option [value]="'monolithic'">Monolítica</mat-option>
|
||||
<!--
|
||||
<mat-option [value]="'git'">Git</mat-option>
|
||||
-->
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<!-- Sección: Configuración de tipo de imagen -->
|
||||
<div class="form-section">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>settings</mat-icon>
|
||||
Configuración de tipo de imagen
|
||||
</div>
|
||||
|
||||
<div class="selector">
|
||||
<mat-form-field appearance="fill" class="half-width">
|
||||
<mat-label>Tipo de imagen</mat-label>
|
||||
<mat-select [(ngModel)]="imageType" class="full-width" (selectionChange)="onImageTypeSelected($event.value)">
|
||||
<mat-option [value]="'monolithic'">Monolítica</mat-option>
|
||||
<mat-option [value]="'git'">Git</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="selector">
|
||||
<mat-form-field appearance="fill" class="half-width">
|
||||
<mat-label>Nombre canónico</mat-label>
|
||||
<input matInput [disabled]="selectedImage" [(ngModel)]="name" placeholder="Nombre canónico. En minúscula y sin espacios" required>
|
||||
</mat-form-field>
|
||||
<!-- Sección: Configuración Git (solo para tipo git) -->
|
||||
<div class="form-section" *ngIf="imageType === 'git'">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>code</mat-icon>
|
||||
Configuración Git
|
||||
</div>
|
||||
|
||||
<div class="git-repository-section">
|
||||
<div class="repository-header">
|
||||
<button *ngIf="imageType === 'git'"
|
||||
class="create-repository-button"
|
||||
(click)="openCreateRepositoryModal()"
|
||||
[disabled]="creatingRepository">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span>Crear nuevo repositorio / SO</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="selector">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccionar repositorio Git</mat-label>
|
||||
<mat-select [(ngModel)]="selectedGitRepository" (selectionChange)="onGitRepositorySelected($event.value)" required>
|
||||
<mat-option [value]="null">Seleccionar repositorio git / SO</mat-option>
|
||||
<mat-option *ngFor="let repo of gitRepositories" [value]="repo">{{ repo.name }}</mat-option>
|
||||
</mat-select>
|
||||
<mat-spinner *ngIf="loadingGitRepositories" matSuffix diameter="20"></mat-spinner>
|
||||
<mat-hint>
|
||||
<mat-icon>info</mat-icon>
|
||||
Selecciona el repositorio git para obtener las imágenes disponibles.
|
||||
<span *ngIf="gitRepositories.length === 0" class="no-repositories-hint">
|
||||
No hay repositorios disponibles. Crea uno nuevo para continuar.
|
||||
</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="fill" class="half-width">
|
||||
<mat-label>Seleccione imagen</mat-label>
|
||||
<mat-select [disabled]="!imageType" [(ngModel)]="selectedImage" name="selectedImage" (selectionChange)="resetCanonicalName()" required>
|
||||
<mat-option *ngFor="let image of images" [value]="image">{{ image?.name }}</mat-option>
|
||||
</mat-select>
|
||||
<button *ngIf="selectedImage" mat-icon-button matSuffix aria-label="Clear client search"
|
||||
(click)="selectedImage = null; resetCanonicalName()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
<mat-hint>Seleccione la imagen para sobreescribir si se requiere. </mat-hint>
|
||||
</mat-form-field>
|
||||
<!-- Opciones de acción Git -->
|
||||
<div class="git-action-selector">
|
||||
<div class="action-chips-container">
|
||||
<mat-chip-listbox [(ngModel)]="gitAction" required class="action-chip-listbox">
|
||||
<mat-chip-option value="create" class="action-chip create-chip firmware-chip" (click)="onGitActionSelected({value: 'create'})">
|
||||
<span>Crear imagen</span>
|
||||
</mat-chip-option>
|
||||
<mat-chip-option value="update" class="action-chip update-chip firmware-chip" (click)="onGitActionSelected({value: 'update'})">
|
||||
<span>Actualizar imagen</span>
|
||||
</mat-chip-option>
|
||||
</mat-chip-listbox>
|
||||
</div>
|
||||
<div class="action-hint">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span *ngIf="gitAction === 'create'">Crea una nueva imagen con el nombre especificado</span>
|
||||
<span *ngIf="gitAction === 'update'">Actualiza una imagen existente seleccionada</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sección: Configuración general -->
|
||||
<div class="form-section" *ngIf="imageType !== 'git'">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>image</mat-icon>
|
||||
Configuración de imagen
|
||||
</div>
|
||||
|
||||
<!-- Opciones de acción para imágenes monolíticas -->
|
||||
<div class="action-chips-container">
|
||||
<mat-chip-listbox [(ngModel)]="monolithicAction" required class="action-chip-listbox">
|
||||
<mat-chip-option value="create" class="action-chip create-chip firmware-chip" (click)="onMonolithicActionSelected({value: 'create'})">
|
||||
<span>Crear imagen</span>
|
||||
</mat-chip-option>
|
||||
<mat-chip-option value="update" class="action-chip update-chip firmware-chip" (click)="onMonolithicActionSelected({value: 'update'})">
|
||||
<span>Actualizar imagen</span>
|
||||
</mat-chip-option>
|
||||
</mat-chip-listbox>
|
||||
</div>
|
||||
<div class="action-hint">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span *ngIf="monolithicAction === 'create'">Crea una nueva imagen con el nombre especificado</span>
|
||||
<span *ngIf="monolithicAction === 'update'">Actualiza una imagen existente seleccionada</span>
|
||||
</div>
|
||||
|
||||
<div class="partition-table-container">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: start">Seleccionar partición</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-radio-group
|
||||
[(ngModel)]="selectedPartition"
|
||||
[disabled]="!row.operativeSystem"
|
||||
>
|
||||
<mat-radio-button [value]="row">
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</td>
|
||||
</ng-container>
|
||||
<div class="selector" *ngIf="monolithicAction === 'create'">
|
||||
<mat-form-field appearance="fill" class="half-width">
|
||||
<mat-label>Nombre canónico</mat-label>
|
||||
<input matInput [(ngModel)]="name" placeholder="Nombre canónico. En minúscula y sin espacios" required>
|
||||
<mat-hint>Introduce el nombre para la nueva imagen que se creará.</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef !== 'size'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
|
||||
|
||||
|
||||
<ng-container *ngIf="column.columnDef === 'size'">
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<span> {{ image.size }} MB</span>
|
||||
<span style="font-size: 0.75rem; color: gray;">{{ image.size / 1024 }} GB</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<div class="selector" *ngIf="monolithicAction === 'update'">
|
||||
<mat-form-field appearance="fill" class="half-width">
|
||||
<mat-label>Seleccione imagen</mat-label>
|
||||
<mat-select [(ngModel)]="selectedImage" name="selectedImage" (selectionChange)="resetCanonicalName()" required>
|
||||
<mat-option [value]="null">Seleccionar imagen para actualizar</mat-option>
|
||||
<mat-option *ngFor="let image of images" [value]="image">{{ image?.name }}</mat-option>
|
||||
</mat-select>
|
||||
<button *ngIf="selectedImage" mat-icon-button matSuffix aria-label="Clear client search"
|
||||
(click)="selectedImage = null; resetCanonicalName()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
<mat-hint>Selecciona la imagen existente que quieres actualizar.</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sección: Selección de partición -->
|
||||
<div class="form-section" #partitionSection id="partition-selection">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>storage</mat-icon>
|
||||
Selección de partición
|
||||
</div>
|
||||
|
||||
<div class="partition-table-container">
|
||||
<table mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: start">Seleccionar partición</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-radio-group
|
||||
[(ngModel)]="selectedPartition"
|
||||
[disabled]="!row.operativeSystem"
|
||||
>
|
||||
<mat-radio-button [value]="row">
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef !== 'size'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="column.columnDef === 'size'">
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<span> {{ image.size }} MB</span>
|
||||
<span style="font-size: 0.75rem; color: gray;">{{ image.size / 1024 }} GB</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-scroll-to-top
|
||||
[threshold]="200"
|
||||
targetElement=".header-container"
|
||||
position="bottom-right"
|
||||
[showTooltip]="true"
|
||||
tooltipText="Volver arriba"
|
||||
tooltipPosition="left">
|
||||
</app-scroll-to-top>
|
||||
|
||||
|
|
|
@ -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<any>();
|
||||
@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<any>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<h2 mat-dialog-title>Crear nuevo repositorio de imágenes git</h2>
|
||||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<form [formGroup]="repositoryForm" (ngSubmit)="save()" class="repository-form">
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>Nombre del repositorio</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
|
||||
<div mat-dialog-actions class="action-container">
|
||||
<button class="ordinary-button" (click)="close()">Cancelar</button>
|
||||
<button class="submit-button" (click)="save()">Guardar</button>
|
||||
</div>
|
|
@ -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<any>;
|
||||
loading: boolean = false;
|
||||
clientRepository: any = null;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private http: HttpClient,
|
||||
public dialogRef: MatDialogRef<CreateRepositoryModalComponent>,
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -5,12 +5,18 @@
|
|||
<h2>
|
||||
{{ 'deployImage' | translate }}
|
||||
</h2>
|
||||
<h4>
|
||||
{{ runScriptTitle }}
|
||||
</h4>
|
||||
<div class="destination-info">
|
||||
<div class="destination-badge">
|
||||
<mat-icon class="destination-icon">cloud_download</mat-icon>
|
||||
<div class="destination-content">
|
||||
<span class="destination-label">Destino</span>
|
||||
<span class="destination-value">{{ runScriptTitle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button class="action-button"
|
||||
<button class="action-button" id="execute-button"
|
||||
[disabled]="!isFormValid()"
|
||||
(click)="save()">Ejecutar</button>
|
||||
</div>
|
||||
|
@ -21,18 +27,15 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button mat-stroked-button color="accent"
|
||||
<div class="button-row">
|
||||
<button class="action-button" color="accent"
|
||||
[disabled]="!isFormValid()"
|
||||
(click)="openScheduleModal()">
|
||||
<mat-icon>schedule</mat-icon> Opciones de programación
|
||||
Opciones de programación
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<div class="select-container">
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
|
@ -53,7 +56,7 @@
|
|||
<div *ngFor="let client of clientData" class="client-item">
|
||||
<div class="client-card"
|
||||
(click)="client.status === 'og-live' && toggleClientSelection(client)"
|
||||
[ngClass]="{'selected-client': client.selected, 'disabled-client': client.status !== 'og-live'}"
|
||||
[ngClass]="{'selected-client': client.selected}"
|
||||
[matTooltip]="getPartitionsTooltip(client)"
|
||||
matTooltipPosition="above"
|
||||
matTooltipClass="custom-tooltip">
|
||||
|
@ -88,84 +91,166 @@
|
|||
<mat-divider style="margin-top: 20px;"></mat-divider>
|
||||
|
||||
<div class="select-container">
|
||||
<div class="deploy-container">
|
||||
<mat-form-field appearance="fill" class="half-width">
|
||||
<mat-label>Tipo de imagen</mat-label>
|
||||
<mat-select [(ngModel)]="imageType" (selectionChange)="onImageTypeSelected($event.value)">
|
||||
<mat-option [value]="'monolithic'">Monolítica</mat-option>
|
||||
<!--
|
||||
<mat-option [value]="'git'">Git</mat-option>
|
||||
-->
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="deploy-container">
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione imagen</mat-label>
|
||||
<mat-select [(ngModel)]="selectedImage" name="selectedImage" [disabled] = "!imageType" >
|
||||
<mat-option *ngFor="let image of images" [value]="image">
|
||||
<div class="unit-name"> {{ image.name }}</div>
|
||||
<div style="font-size: smaller; color: gray;">{{ image.description }}</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione método de deploy</mat-label>
|
||||
<mat-select [(ngModel)]="selectedMethod" name="selectedMethod" (selectionChange)="validateImageSize()">
|
||||
<mat-option *ngFor="let method of allMethods" [value]="method.value">{{ method.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="errorMessage" class="error-message">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
<div class="partition-table-container">
|
||||
<div *ngIf="showInstructions" class="instructions-box">
|
||||
<mat-card class="instructions-card">
|
||||
<mat-card-title>
|
||||
Instrucciones generadas
|
||||
<button mat-icon-button (click)="showInstructions = false" style="float: right;">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-card-title>
|
||||
<mat-card-content>
|
||||
<pre>{{ ogInstructions }}</pre>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<!-- Sección: Configuración de imagen -->
|
||||
<div class="form-section">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>image</mat-icon>
|
||||
Configuración de imagen
|
||||
</div>
|
||||
|
||||
<div class="selector">
|
||||
<mat-form-field appearance="fill" class="half-width">
|
||||
<mat-label>Tipo de imagen</mat-label>
|
||||
<mat-select [(ngModel)]="imageType" (selectionChange)="onImageTypeSelected($event.value)">
|
||||
<mat-option [value]="'monolithic'">Monolítica</mat-option>
|
||||
<mat-option [value]="'git'">Git</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="filteredPartitions" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: start">Seleccionar partición</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-radio-group [(ngModel)]="selectedPartition" name="selectedPartition" (change)="validateImageSize()">
|
||||
<mat-radio-button [value]="row">
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Selectores Git (solo si imageType === 'git') -->
|
||||
<div *ngIf="imageType === 'git'" class="filters-row git-gap">
|
||||
<mat-form-field appearance="fill" style="width: 300px;">
|
||||
<mat-label>Seleccionar Repositorio</mat-label>
|
||||
<mat-select [(ngModel)]="selectedGitRepository" (selectionChange)="onGitRepositoryChange($event.value)" [disabled]="loadingRepositories">
|
||||
<mat-option *ngFor="let repo of repositories" [value]="repo">
|
||||
{{ repo }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-icon matSuffix *ngIf="loadingRepositories">hourglass_empty</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
{{ column.cell(image) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<mat-form-field appearance="fill" style="width: 300px;">
|
||||
<mat-label>Seleccionar Rama</mat-label>
|
||||
<mat-select [(ngModel)]="selectedBranch" (selectionChange)="onGitBranchChange()" [disabled]="loadingBranches || !selectedRepository">
|
||||
<mat-option *ngFor="let branch of branches" [value]="branch">
|
||||
{{ branch }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-icon matSuffix *ngIf="loadingBranches">hourglass_empty</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<!-- Tabla de commits (solo si imageType === 'git') -->
|
||||
<div *ngIf="imageType === 'git' && commits.length > 0" class="commits-table-container">
|
||||
<table mat-table [dataSource]="commits" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef>Seleccionar</th>
|
||||
<td mat-cell *matCellDef="let commit">
|
||||
<mat-radio-group [(ngModel)]="selectedCommit" name="selectedCommit">
|
||||
<mat-radio-button [value]="commit"></mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="hexsha">
|
||||
<th mat-header-cell *matHeaderCellDef>Commit ID</th>
|
||||
<td mat-cell *matCellDef="let commit">
|
||||
<code style="background-color: #f5f5f5; padding: 2px 4px; border-radius: 3px; font-family: monospace;">
|
||||
{{ commit.hexsha }}
|
||||
</code>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="message">
|
||||
<th mat-header-cell *matHeaderCellDef>Mensaje</th>
|
||||
<td mat-cell *matCellDef="let commit">{{ commit.message }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="committed_date">
|
||||
<th mat-header-cell *matHeaderCellDef>Fecha</th>
|
||||
<td mat-cell *matCellDef="let commit">{{ commit.committed_date * 1000 | date:'dd/MM/yyyy HH:mm:ss' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="tags">
|
||||
<th mat-header-cell *matHeaderCellDef>Tags</th>
|
||||
<td mat-cell *matCellDef="let commit">
|
||||
<mat-chip-list>
|
||||
<mat-chip *ngFor="let tag of commit.tags" color="primary" selected>{{ tag }}</mat-chip>
|
||||
<span *ngIf="!commit.tags || commit.tags.length === 0" style="color: #999; font-style: italic;">Sin tags</span>
|
||||
</mat-chip-list>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="['select','hexsha','message','committed_date','tags']"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['select','hexsha','message','committed_date','tags'];"></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Selector de método y de imagen solo si imageType === 'monolithic' -->
|
||||
<div class="monolithic-row" *ngIf="imageType === 'monolithic'">
|
||||
<mat-form-field appearance="fill" class="half-width">
|
||||
<mat-label>Seleccione método de deploy</mat-label>
|
||||
<mat-select [(ngModel)]="selectedMethod" name="selectedMethod" (selectionChange)="validateImageSize()">
|
||||
<mat-option *ngFor="let method of allMethods" [value]="method.value">{{ method.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione imagen</mat-label>
|
||||
<mat-select [(ngModel)]="selectedImage" name="selectedImage" [disabled] = "!imageType" >
|
||||
<mat-option *ngFor="let image of images" [value]="image">
|
||||
<div class="unit-name"> {{ image.name }}</div>
|
||||
<div style="font-size: smaller; color: gray;">{{ image.description }}</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="errorMessage" class="error-message">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
<div class="options-container">
|
||||
<h3 *ngIf="isMethod('udpcast') || isMethod('uftp') || isMethod('udpcast-direct')" class="input-group">Opciones multicast</h3>
|
||||
<h3 *ngIf="isMethod('p2p')" class="input-group">Opciones torrent</h3>
|
||||
<div *ngIf="isMethod('udpcast') || isMethod('uftp') || isMethod('udpcast-direct')" class="input-group">
|
||||
<!-- Sección: Selección de partición -->
|
||||
<div class="form-section" id="partition-selection">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>storage</mat-icon>
|
||||
Selección de partición
|
||||
</div>
|
||||
|
||||
<div class="partition-table-container">
|
||||
<div *ngIf="showInstructions" class="instructions-box">
|
||||
<mat-card class="instructions-card">
|
||||
<mat-card-title>
|
||||
Instrucciones generadas
|
||||
<button mat-icon-button (click)="showInstructions = false" style="float: right;">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-card-title>
|
||||
<mat-card-content>
|
||||
<pre>{{ ogInstructions }}</pre>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="filteredPartitions">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: start">Seleccionar partición</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-radio-group [(ngModel)]="selectedPartition" name="selectedPartition">
|
||||
<mat-radio-button [value]="row">
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
{{ column.cell(image) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sección: Opciones avanzadas -->
|
||||
<div class="form-section" id="advanced-options" *ngIf="isMethod('udpcast') || isMethod('uftp') || isMethod('udpcast-direct') || isMethod('p2p')">
|
||||
<div class="form-section-title">
|
||||
<mat-icon>settings</mat-icon>
|
||||
<span *ngIf="isMethod('udpcast') || isMethod('uftp') || isMethod('udpcast-direct')">Opciones multicast</span>
|
||||
<span *ngIf="isMethod('p2p')">Opciones torrent</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group" *ngIf="isMethod('udpcast') || isMethod('uftp') || isMethod('udpcast-direct')">
|
||||
<mat-form-field appearance="fill" class="input-field">
|
||||
<mat-label>Puerto</mat-label>
|
||||
<input matInput [(ngModel)]="mcastPort" name="mcastPort" type="number"
|
||||
|
@ -207,7 +292,7 @@
|
|||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isMethod('p2p')" class="input-group">
|
||||
<div class="input-group" *ngIf="isMethod('p2p')">
|
||||
<mat-form-field appearance="fill" class="input-field">
|
||||
<mat-label i18n="@@p2pModeLabel">Modo P2P</mat-label>
|
||||
<mat-select [(ngModel)]="p2pMode" name="p2pMode"
|
||||
|
@ -218,14 +303,22 @@
|
|||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="input-field">
|
||||
<mat-label>Semilla</mat-label>
|
||||
<mat-form-field appearance="fill" class="input-field" *ngIf="p2pMode === 'seeder'">
|
||||
<mat-label>Semilla (minutos)</mat-label>
|
||||
<input matInput [(ngModel)]="p2pTime" name="p2pTime" type="number"
|
||||
[required]="isMethod('p2p')">
|
||||
[required]="isMethod('p2p') && p2pMode === 'seeder'">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<app-scroll-to-top
|
||||
[threshold]="100"
|
||||
targetElement=".header-container"
|
||||
position="bottom-right"
|
||||
[showTooltip]="true"
|
||||
tooltipText="Volver arriba"
|
||||
tooltipPosition="left">
|
||||
</app-scroll-to-top>
|
||||
|
||||
|
||||
|
|
|
@ -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(<string>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<any>(`${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<any>(`${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<any>(`${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;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,6 @@
|
|||
(click)="openShowMonoliticImagesDialog(repository)">
|
||||
{{ 'monolithicImage' | translate }}
|
||||
</button>
|
||||
<!--
|
||||
<button
|
||||
class="action-button"
|
||||
joyrideStep="gitImageStep"
|
||||
|
@ -60,8 +59,7 @@
|
|||
[disabled]="!isGitModuleInstalled"
|
||||
(click)="openShowGitImagesDialog(repository)">
|
||||
{{ 'gitImage' | translate }}
|
||||
</button
|
||||
-->
|
||||
</button>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -6,96 +6,96 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2>Gestionar imágenes git en {{data.repositoryName}}</h2>
|
||||
<h2>Commits de Git en {{data.repositoryName}}</h2>
|
||||
</div>
|
||||
<div class="images-button-row">
|
||||
<button class="action-button" (click)="openImageInfoDialog()">Ver Información</button>
|
||||
<button class="action-button" disabled (click)="syncRepository()">Sincronizar base de datos</button>
|
||||
<button class="action-button" disabled (click)="importImage()">
|
||||
{{ 'importImageButton' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchImagesField"
|
||||
text="Busca una imagen por nombre. Pulsa 'enter' para iniciar la búsqueda.">
|
||||
<mat-label>{{ 'searchLabel' | translate }}</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="loadData()"
|
||||
i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-boolean">
|
||||
<mat-label i18n="@@searchLabel">Estado</mat-label>
|
||||
<mat-select [(ngModel)]="filters['status']" (selectionChange)="loadData()" placeholder="Seleccionar opción">
|
||||
<mat-option [value]="'failed'">Fallido</mat-option>
|
||||
<mat-option [value]="'pending'">Pendiente</mat-option>
|
||||
<mat-option [value]="'in-progress'">Transfiriendo</mat-option>
|
||||
<mat-option [value]="'success'">Creado con éxito</mat-option>
|
||||
<mat-option [value]="'transferring'">En progreso</mat-option>
|
||||
<mat-option [value]="'trash'">Papelera</mat-option>
|
||||
<mat-option [value]="'aux-files-pending'">Creando archivos auxiliares</mat-option>
|
||||
<div class="filters-row">
|
||||
<mat-form-field appearance="fill" style="width: 300px;">
|
||||
<mat-label>Seleccionar Repositorio</mat-label>
|
||||
<mat-select [(ngModel)]="selectedRepository" (selectionChange)="onRepositoryChange()" [disabled]="loadingRepositories">
|
||||
<mat-option *ngFor="let repo of repositories" [value]="repo">
|
||||
{{ repo }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-icon matSuffix *ngIf="loadingRepositories">hourglass_empty</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" style="width: 300px;">
|
||||
<mat-label>Seleccionar Rama</mat-label>
|
||||
<mat-select [(ngModel)]="selectedBranch" (selectionChange)="onBranchChange()" [disabled]="loadingBranches || !selectedRepository">
|
||||
<mat-option *ngFor="let branch of branches" [value]="branch">
|
||||
{{ branch }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-icon matSuffix *ngIf="loadingBranches">hourglass_empty</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="imagesTable"
|
||||
text="Esta tabla muestra las imágenes disponibles.">
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchCommitsField"
|
||||
text="Busca un commit por mensaje. Pulsa 'enter' para iniciar la búsqueda.">
|
||||
<mat-label>Buscar commits</mat-label>
|
||||
<input matInput placeholder="Búsqueda por mensaje" [(ngModel)]="filters['message']" (keyup.enter)="loadData()"
|
||||
i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>Buscar por mensaje del commit</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="commitsTable"
|
||||
text="Esta tabla muestra los commits disponibles.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
<ng-container *ngIf="column.columnDef === 'isGlobal'">
|
||||
<mat-chip>
|
||||
{{ image.isGlobal ? 'Sí' : 'No' }}
|
||||
</mat-chip>
|
||||
<td mat-cell *matCellDef="let commit">
|
||||
<ng-container *ngIf="column.columnDef === 'hexsha'">
|
||||
<code class="commit-id">
|
||||
{{ column.cell(commit) }}
|
||||
</code>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'status'">
|
||||
<mat-chip [ngClass]="{
|
||||
'chip-failed': image.status === 'failed',
|
||||
'chip-success': image.status === 'success',
|
||||
'chip-pending': image.status === 'pending',
|
||||
'chip-in-progress': image.status === 'in-progress',
|
||||
'chip-transferring': image.status === 'transferring',
|
||||
}">
|
||||
{{ getStatusLabel(image[column.columnDef]) }}
|
||||
</mat-chip>
|
||||
<ng-container *ngIf="column.columnDef === 'message'">
|
||||
<div class="commit-message">
|
||||
{{ column.cell(commit) }}
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="column.columnDef !== 'remotePc' && column.columnDef !== 'status' && column.columnDef !== 'isGlobal'">
|
||||
{{ column.cell(image) }}
|
||||
<ng-container *ngIf="column.columnDef === 'stats_total'">
|
||||
<div class="commit-stats">
|
||||
{{ column.cell(commit) }}
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'tags'">
|
||||
<div class="commit-tags">
|
||||
<mat-chip *ngFor="let tag of commit.tags" color="primary" selected>
|
||||
{{ tag }}
|
||||
</mat-chip>
|
||||
<span *ngIf="!commit.tags || commit.tags.length === 0" class="no-tags">
|
||||
Sin tags
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef !== 'hexsha' && column.columnDef !== 'message' && column.columnDef !== 'stats_total' && column.columnDef !== 'tags'">
|
||||
{{ column.cell(commit) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let image" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="goToPage(image)">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
</button>
|
||||
<td mat-cell *matCellDef="let commit" style="text-align: center;">
|
||||
<div class="action-buttons">
|
||||
<a [href]="'http://localhost:3100/oggit/' + selectedRepository + '/commit/' + commit.hexsha" target="_blank" matTooltip="Ver commit en Git">
|
||||
<button mat-icon-button color="primary">
|
||||
<mat-icon>open_in_new</mat-icon>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<button mat-icon-button color="primary" (click)="toggleAction(image, 'edit')">
|
||||
<mat-icon i18n="@@deleteElementTooltip">edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="toggleAction(image, 'delete-trash')">
|
||||
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item
|
||||
(click)="toggleAction(image, 'show-tags')">Ver tags</button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'"
|
||||
(click)="toggleAction(image, 'show-branches')">Ver ramas</button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'"
|
||||
(click)="toggleAction(image, 'transfer')">Transferir imagen</button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'"
|
||||
(click)="toggleAction(image, 'transfer-global')">Transferir imagen globalmente </button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'"
|
||||
(click)="toggleAction(image, 'backup')">Realizar backup </button>
|
||||
</mat-menu>
|
||||
<button mat-icon-button color="primary" (click)="toggleAction(commit, 'view-details')" matTooltip="Ver detalles">
|
||||
<mat-icon>info</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
|
|
|
@ -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<ShowGitImagesComponent>;
|
||||
describe('ShowGitCommitsComponent', () => {
|
||||
let component: ShowGitCommitsComponent;
|
||||
let fixture: ComponentFixture<ShowGitCommitsComponent>;
|
||||
|
||||
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();
|
||||
});
|
||||
|
|
|
@ -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<any>();
|
||||
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<ShowGitImagesComponent>,
|
||||
public dialogRef: MatDialogRef<ShowGitCommitsComponent>,
|
||||
@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<any>(`${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<any>(`${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<any>(`${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<any>(`${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<any> {
|
||||
return this.http.get<any>(`${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<any>(`${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<any> {
|
||||
return this.http.post<any>(`${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 {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue