Compare commits
36 Commits
4d757c4379
...
8ed2d62a81
Author | SHA1 | Date |
---|---|---|
|
8ed2d62a81 | |
|
b0ede36d3b | |
|
024914d993 | |
|
952938a253 | |
|
7698be2bca | |
|
ee37287f9e | |
|
3c9458d15a | |
|
c6a81a8152 | |
|
8d6e4615c7 | |
|
9af35975f5 | |
|
12f7bad764 | |
|
97556caa95 | |
|
805c0026bc | |
|
9669202dbd | |
|
c8c1bdd0bd | |
|
bada1762aa | |
|
218816d1f1 | |
|
9e071814e9 | |
|
4437acdac2 | |
|
7b3e1534eb | |
|
828f711549 | |
|
75357b82c8 | |
|
3e64ae03ba | |
|
7490456b71 | |
|
ecd3980f61 | |
|
2b21a621ae | |
|
e2056524cf | |
|
9401448d0c | |
|
112eb23195 | |
|
c7d33ff502 | |
|
3c1663aeb1 | |
|
7e174c9617 | |
|
17c22c8714 | |
|
c139bd6b05 | |
|
1299a0607f | |
|
3bd923cbbb |
|
@ -32,15 +32,14 @@ import { AddRoleModalComponent } from './components/admin/roles/roles/add-role-m
|
|||
import { ChangePasswordModalComponent } from './components/admin/users/users/change-password-modal/change-password-modal.component';
|
||||
import { GroupsComponent } from './components/groups/groups.component';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { CreateOrganizationalUnitComponent } from './components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component';
|
||||
import { MatStepperModule } from '@angular/material/stepper';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { CreateClientComponent } from './components/groups/shared/clients/create-client/create-client.component';
|
||||
import { DeleteModalComponent } from './shared/delete_modal/delete-modal/delete-modal.component';
|
||||
import { EditOrganizationalUnitComponent } from './components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component';
|
||||
import { EditClientComponent } from './components/groups/shared/clients/edit-client/edit-client.component';
|
||||
import { ClassroomViewComponent } from './components/groups/shared/classroom-view/classroom-view.component';
|
||||
import { MatProgressSpinner } from "@angular/material/progress-spinner";
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatMenu, MatMenuItem, MatMenuTrigger } from "@angular/material/menu";
|
||||
import {MatAutocomplete, MatAutocompleteTrigger} from "@angular/material/autocomplete";
|
||||
import { MatChip, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule } from "@angular/material/chips";
|
||||
|
@ -127,6 +126,9 @@ import { CreateMultipleClientComponent } from './components/groups/shared/client
|
|||
import { ExportImageComponent } from './components/images/export-image/export-image.component';
|
||||
import {ImportImageComponent} from "./components/repositories/import-image/import-image.component";
|
||||
import { LoadingComponent } from './shared/loading/loading.component';
|
||||
import { RepositoryImagesComponent } from './components/repositories/repository-images/repository-images.component';
|
||||
import { InputDialogComponent } from './components/commands/commands-task/task-logs/input-dialog/input-dialog.component';
|
||||
import { ManageOrganizationalUnitComponent } from './components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component';
|
||||
export function HttpLoaderFactory(http: HttpClient) {
|
||||
return new TranslateHttpLoader(http, './locale/', '.json');
|
||||
}
|
||||
|
@ -147,10 +149,8 @@ export function HttpLoaderFactory(http: HttpClient) {
|
|||
AddRoleModalComponent,
|
||||
ChangePasswordModalComponent,
|
||||
GroupsComponent,
|
||||
CreateOrganizationalUnitComponent,
|
||||
CreateClientComponent,
|
||||
DeleteModalComponent,
|
||||
EditOrganizationalUnitComponent,
|
||||
EditClientComponent,
|
||||
ClassroomViewComponent,
|
||||
ClientViewComponent,
|
||||
|
@ -212,6 +212,9 @@ export function HttpLoaderFactory(http: HttpClient) {
|
|||
ExportImageComponent,
|
||||
ImportImageComponent,
|
||||
LoadingComponent,
|
||||
RepositoryImagesComponent,
|
||||
InputDialogComponent,
|
||||
ManageOrganizationalUnitComponent,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [BrowserModule,
|
||||
|
@ -232,6 +235,7 @@ export function HttpLoaderFactory(http: HttpClient) {
|
|||
MatDialogModule,
|
||||
MatSelectModule,
|
||||
MatDividerModule,
|
||||
MatProgressBarModule,
|
||||
MatStepperModule,
|
||||
DragDropModule,
|
||||
MatSlideToggleModule, MatMenu, MatMenuTrigger, MatMenuItem, MatAutocomplete, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule, MatChip, MatProgressSpinner, MatTabGroup, MatTab, MatTooltip,
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
.env-settings {
|
||||
padding: 16px;
|
||||
|
||||
h1 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
padding: 0rem 1rem 0rem 1rem;
|
||||
|
||||
.mat-table {
|
||||
margin-bottom: 16px;
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<div class="env-settings">
|
||||
<h1>Editar Variables de Entorno</h1>
|
||||
<div class="header-container">
|
||||
<div class="header-container-title">
|
||||
<h2>Editar Variables de Entorno</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-table [dataSource]="envVars" class="mat-elevation-z8">
|
||||
<!-- Nombre de la variable -->
|
||||
|
@ -22,8 +26,8 @@
|
|||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<div class="actions" >
|
||||
<div class="actions">
|
||||
<button mat-raised-button color="primary" (click)="saveEnvVars()">Guardar Cambios</button>
|
||||
<button mat-raised-button color="accent" (click)="loadEnvVars()">Recargar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,21 +1,30 @@
|
|||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.search-string {
|
||||
flex: 2;
|
||||
padding: 5px;
|
||||
|
@ -26,20 +35,12 @@ table {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,24 +1,25 @@
|
|||
<div class="header-container">
|
||||
<h2 class="title">{{ 'adminRolesTitle' | translate }}</h2>
|
||||
<div class="header-container-title">
|
||||
<h2>{{ 'adminRolesTitle' | translate }}</h2>
|
||||
</div>
|
||||
<div class="images-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addUser()">
|
||||
{{ 'addRole' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>{{ 'searchRoleLabel' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()">
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
|
||||
(keyup.enter)="search()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div *ngIf="!loading">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
|
@ -33,7 +34,8 @@
|
|||
<button mat-icon-button color="primary" (click)="editRole(role)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="deleteRole(role)" [disabled]="role.permissions.includes('ROLE_SUPER_ADMIN')">
|
||||
<button mat-icon-button color="warn" (click)="deleteRole(role)"
|
||||
[disabled]="role.permissions.includes('ROLE_SUPER_ADMIN')">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
|
@ -44,10 +46,7 @@
|
|||
</div>
|
||||
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
|
@ -12,6 +12,7 @@ import { MatIcon } from '@angular/material/icon';
|
|||
import { MatHint } from '@angular/material/form-field';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { LoadingComponent } from '../../../../shared/loading/loading.component';
|
||||
describe('RolesComponent', () => {
|
||||
let component: RolesComponent;
|
||||
let fixture: ComponentFixture<RolesComponent>;
|
||||
|
@ -27,7 +28,7 @@ describe('RolesComponent', () => {
|
|||
const dataServiceSpy = jasmine.createSpyObj('DataService', ['getRoles']);
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [RolesComponent],
|
||||
declarations: [RolesComponent, LoadingComponent],
|
||||
imports: [MatDivider, MatFormField, MatLabel, MatIcon, MatHint, MatPaginator,
|
||||
TranslateModule.forRoot()],
|
||||
providers: [
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
|
@ -8,14 +21,10 @@ table {
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.search-string {
|
||||
flex: 2;
|
||||
padding: 5px;
|
||||
|
@ -26,13 +35,6 @@ table {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
<div class="header-container">
|
||||
<h2 class="title">{{ 'adminUsersTitle' | translate }}</h2>
|
||||
<div class="header-container-title">
|
||||
<h2>{{ 'adminUsersTitle' | translate }}</h2>
|
||||
</div>
|
||||
<div class="images-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addUser()">
|
||||
{{ 'addUser' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>{{ 'searchLabel' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()">
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
|
||||
(keyup.enter)="search()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div *ngIf="!loading">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
|
@ -44,10 +46,7 @@
|
|||
</div>
|
||||
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
|
@ -1,15 +1,20 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.calendar-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.lists-container {
|
||||
|
@ -23,15 +28,14 @@
|
|||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -45,19 +49,12 @@ table {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
|
@ -2,30 +2,33 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}" class="title">{{ 'adminCalendarsTitle' | translate }}</h2>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCalendarsTitle' | translate }}</h2>
|
||||
</div>
|
||||
<div class="calendar-button-row">
|
||||
<button joyrideStep="addButtonStep" text="{{ 'addButtonStepText' | translate }}" mat-flat-button color="primary" (click)="addImage()">
|
||||
<button joyrideStep="addButtonStep" text="{{ 'addButtonStepText' | translate }}" mat-flat-button color="primary"
|
||||
(click)="addImage()">
|
||||
{{ 'addCalendar' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}" appearance="fill" class="search-string">
|
||||
<mat-form-field joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}" appearance="fill"
|
||||
class="search-string">
|
||||
<mat-label>{{ 'searchCalendarLabel' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()">
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
|
||||
(keyup.enter)="search()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div *ngIf="!loading">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableStepText' | translate }}">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
text="{{ 'tableStepText' | translate }}">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
|
@ -34,7 +37,8 @@
|
|||
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl'">
|
||||
<ng-container
|
||||
*ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
|
@ -50,7 +54,7 @@
|
|||
<mat-icon>sync</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="syncUds" mat-icon-button color="primary">
|
||||
<mat-spinner diameter="24"></mat-spinner>
|
||||
<app-loading [isLoading]="syncUds"></app-loading>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="deleteCalendar(calendar)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
|
@ -64,10 +68,7 @@
|
|||
</div>
|
||||
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
|
@ -15,6 +15,7 @@ import { CalendarComponent } from './calendar.component';
|
|||
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||
import { JoyrideModule, JoyrideService } from 'ngx-joyride';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { LoadingComponent } from '../../shared/loading/loading.component';
|
||||
|
||||
describe('CalendarComponent', () => {
|
||||
let component: CalendarComponent;
|
||||
|
@ -22,7 +23,7 @@ describe('CalendarComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [CalendarComponent],
|
||||
declarations: [CalendarComponent, LoadingComponent],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
ToastrModule.forRoot(),
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.calendar-button-row {
|
||||
.command-groups-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
|
@ -27,16 +28,15 @@
|
|||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
box-sizing: border-box;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
}
|
||||
|
||||
.search-string {
|
||||
|
@ -53,11 +53,12 @@ table {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
|
@ -74,5 +75,4 @@ table {
|
|||
.mat-chip-readonly-false {
|
||||
background-color: #F44336 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,9 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCommandGroupsTitle' | translate }}</h2>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCommandGroupsTitle' | translate }}</h2>
|
||||
</div>
|
||||
<div class="command-groups-button-row">
|
||||
<button mat-flat-button color="primary" (click)="openCreateCommandGroupModal()" joyrideStep="addCommandGroupStep" text="{{ 'addCommandGroupStepText' | translate }}">
|
||||
{{ 'addCommandGroup' | translate }}
|
||||
|
@ -10,8 +12,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>{{ 'searchGroupNameLabel' | translate }}</mat-label>
|
||||
|
@ -22,7 +22,7 @@
|
|||
</div>
|
||||
|
||||
<div *ngIf="loading" class="loading-container" joyrideStep="loadingStep" text="{{ 'loadingStepText' | translate }}">
|
||||
<mat-spinner></mat-spinner>
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!loading">
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
<h2 mat-dialog-title>{{ editing ? ('editCommandGroup' | translate) : ('createCommandGroup' | translate) }}</h2>
|
||||
<mat-dialog-content class="form-container">
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<form *ngIf="!loading" class="command-group-form" (ngSubmit)="onSubmit()">
|
||||
<mat-form-field>
|
||||
|
@ -58,4 +56,4 @@
|
|||
<mat-dialog-actions align="end">
|
||||
<button mat-button (click)="close()">{{ 'buttonCancel' | translate }}</button>
|
||||
<button mat-button (click)="onSubmit()" cdkFocusInitial>{{ 'buttonSave' | translate }}</button>
|
||||
</mat-dialog-actions>
|
||||
</mat-dialog-actions>
|
|
@ -1,10 +1,7 @@
|
|||
<div class="detail-command-group-container">
|
||||
<h2>{{ 'commandGroupDetailsTitle' | translate }}</h2>
|
||||
|
||||
<!-- Indicador de carga -->
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<mat-card *ngIf="!loading">
|
||||
<mat-card-header>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.calendar-button-row {
|
||||
.task-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
|
@ -27,7 +28,6 @@
|
|||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
|
@ -35,7 +35,7 @@ table {
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,8 @@ table {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'manageTasksTitle' | translate }}</h2>
|
||||
<div class="header-container-title">
|
||||
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'manageTasksTitle' | translate }}</h2>
|
||||
</div>
|
||||
<div class="task-button-row">
|
||||
<button mat-flat-button color="primary" (click)="openCreateTaskModal()" joyrideStep="addTaskStep" text="{{ 'addTaskStepText' | translate }}">
|
||||
{{ 'addTask' | translate }}
|
||||
|
@ -10,8 +12,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>{{ 'searchTaskLabel' | translate }}</mat-label>
|
||||
|
|
|
@ -7,10 +7,11 @@ import { MatDividerModule } from '@angular/material/divider';
|
|||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { LoadingComponent } from '../../../shared/loading/loading.component';
|
||||
import { JoyrideModule, JoyrideService, JoyrideStepService } from 'ngx-joyride';
|
||||
describe('CommandsTaskComponent', () => {
|
||||
let component: CommandsTaskComponent;
|
||||
|
@ -31,7 +32,7 @@ describe('CommandsTaskComponent', () => {
|
|||
TranslateModule.forRoot(),
|
||||
JoyrideModule.forRoot(),
|
||||
],
|
||||
declarations: [CommandsTaskComponent],
|
||||
declarations: [CommandsTaskComponent, LoadingComponent],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<h1 mat-dialog-title>{{ 'inputDetails' | translate }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<pre>{{ data.input | json }}</pre>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-button (click)="close()">{{ 'closeButton' | translate }}</button>
|
||||
</div>
|
|
@ -0,0 +1,47 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InputDialogComponent } from './input-dialog.component';
|
||||
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog";
|
||||
import {FormBuilder} from "@angular/forms";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {provideHttpClient} from "@angular/common/http";
|
||||
import {provideHttpClientTesting} from "@angular/common/http/testing";
|
||||
import {TranslateModule} from "@ngx-translate/core";
|
||||
|
||||
describe('InputDialogComponent', () => {
|
||||
let component: InputDialogComponent;
|
||||
let fixture: ComponentFixture<InputDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [InputDialogComponent],
|
||||
imports: [
|
||||
MatDialogModule,
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
FormBuilder,
|
||||
ToastrService,
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
{
|
||||
provide: MatDialogRef,
|
||||
useValue: {}
|
||||
},
|
||||
{
|
||||
provide: MAT_DIALOG_DATA,
|
||||
useValue: {}
|
||||
}
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(InputDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-input-dialog',
|
||||
templateUrl: './input-dialog.component.html',
|
||||
styleUrl: './input-dialog.component.css'
|
||||
})
|
||||
export class InputDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<InputDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { input: any }
|
||||
) {}
|
||||
|
||||
close(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
|
@ -1,15 +1,20 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.calendar-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.lists-container {
|
||||
|
@ -27,14 +32,13 @@
|
|||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -53,15 +57,14 @@ table {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
.progress-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
|
@ -88,12 +91,4 @@ table {
|
|||
.chip-in-progress {
|
||||
background-color: #f5a623 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -4,31 +4,37 @@
|
|||
</button>
|
||||
|
||||
<div class="header-container-title">
|
||||
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCommandsTitle' | translate }}</h2>
|
||||
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCommandsTitle' |
|
||||
translate }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="images-button-row">
|
||||
<button mat-flat-button color="primary" (click)="resetFilters()" joyrideStep="resetFiltersStep" text="{{ 'resetFiltersStepText' | translate }}">
|
||||
<button mat-flat-button color="primary" (click)="resetFilters()" joyrideStep="resetFiltersStep"
|
||||
text="{{ 'resetFiltersStepText' | translate }}">
|
||||
{{ 'resetFilters' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-select" joyrideStep="clientSelectStep" text="{{ 'clientSelectStepText' | translate }}">
|
||||
<input type="text" matInput [formControl]="clientControl" [matAutocomplete]="clientAuto" placeholder="{{ 'selectClientPlaceholder' | translate }}">
|
||||
<mat-autocomplete #clientAuto="matAutocomplete" [displayWith]="displayFnClient" (optionSelected)="onOptionClientSelected($event.option.value)">
|
||||
<mat-form-field appearance="fill" class="search-select" joyrideStep="clientSelectStep"
|
||||
text="{{ 'clientSelectStepText' | translate }}">
|
||||
<input type="text" matInput [formControl]="clientControl" [matAutocomplete]="clientAuto"
|
||||
placeholder="{{ 'selectClientPlaceholder' | translate }}">
|
||||
<mat-autocomplete #clientAuto="matAutocomplete" [displayWith]="displayFnClient"
|
||||
(optionSelected)="onOptionClientSelected($event.option.value)">
|
||||
<mat-option *ngFor="let client of filteredClients | async" [value]="client">
|
||||
{{ client.name }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="search-select" joyrideStep="commandSelectStep" text="{{ 'commandSelectStepText' | translate }}">
|
||||
<input type="text" matInput [formControl]="commandControl" [matAutocomplete]="commandAuto" placeholder="{{ 'selectCommandPlaceholder' | translate }}">
|
||||
<mat-autocomplete #commandAuto="matAutocomplete" [displayWith]="displayFnCommand" (optionSelected)="onOptionCommandSelected($event.option.value)">
|
||||
<mat-form-field appearance="fill" class="search-select" joyrideStep="commandSelectStep"
|
||||
text="{{ 'commandSelectStepText' | translate }}">
|
||||
<input type="text" matInput [formControl]="commandControl" [matAutocomplete]="commandAuto"
|
||||
placeholder="{{ 'selectCommandPlaceholder' | translate }}">
|
||||
<mat-autocomplete #commandAuto="matAutocomplete" [displayWith]="displayFnCommand"
|
||||
(optionSelected)="onOptionCommandSelected($event.option.value)">
|
||||
<mat-option *ngFor="let command of filteredCommands | async" [value]="command">
|
||||
{{ command.name }}
|
||||
</mat-option>
|
||||
|
@ -37,7 +43,7 @@
|
|||
|
||||
<mat-form-field appearance="fill" class="search-boolean">
|
||||
<mat-label i18n="@@searchLabel">Estado</mat-label>
|
||||
<mat-select [(ngModel)]="filters['status']" (selectionChange)="loadTraces()" placeholder="Seleccionar opción" >
|
||||
<mat-select [(ngModel)]="filters['status']" (selectionChange)="loadTraces()" placeholder="Seleccionar opción">
|
||||
<mat-option [value]="'failed'">Fallido</mat-option>
|
||||
<mat-option [value]="'pending'">Pendiente de ejecutar</mat-option>
|
||||
<mat-option [value]="'in-progress'">Ejecutando</mat-option>
|
||||
|
@ -46,50 +52,67 @@
|
|||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div *ngIf="!loading">
|
||||
<table mat-table [dataSource]="traces" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableStepText' | translate }}">
|
||||
<table mat-table [dataSource]="traces" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
text="{{ 'tableStepText' | translate }}">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let trace">
|
||||
|
||||
<ng-container *ngIf="column.columnDef === 'status'; else defaultCell">
|
||||
<mat-chip [ngClass]="{
|
||||
'chip-failed': trace.status === 'failed',
|
||||
'chip-success': trace.status === 'success',
|
||||
'chip-pending': trace.status === 'pending',
|
||||
'chip-in-progress': trace.status === 'in-progress'
|
||||
}">
|
||||
{{
|
||||
trace.status === 'failed' ? 'Fallido' :
|
||||
<ng-container [ngSwitch]="column.columnDef">
|
||||
<!-- Caso para "status" -->
|
||||
<ng-container *ngSwitchCase="'status'">
|
||||
<ng-container *ngIf="trace.status === 'in-progress' && trace.progress; else statusChip">
|
||||
<div class="progress-container">
|
||||
<mat-progress-bar class="example-margin" [mode]="mode" [value]="progress" [bufferValue]="bufferValue">
|
||||
</mat-progress-bar>
|
||||
<span>{{progress}}%</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #statusChip>
|
||||
<mat-chip [ngClass]="{
|
||||
'chip-failed': trace.status === 'failed',
|
||||
'chip-success': trace.status === 'success',
|
||||
'chip-pending': trace.status === 'pending',
|
||||
'chip-in-progress': trace.status === 'in-progress'
|
||||
}">
|
||||
{{
|
||||
trace.status === 'failed' ? 'Fallido' :
|
||||
trace.status === 'in-progress' ? 'En ejecución' :
|
||||
trace.status === 'success' ? 'Finalizado con éxito' :
|
||||
trace.status === 'pending' ? 'Pendiente de ejecutar' :
|
||||
trace.status === 'in-progress' ? 'Ejecutando' :
|
||||
trace.status
|
||||
}}
|
||||
</mat-chip>
|
||||
</ng-container>
|
||||
trace.status === 'pending' ? 'Pendiente de ejecutar' :
|
||||
trace.status
|
||||
}}
|
||||
</mat-chip>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #defaultCell>
|
||||
{{ column.cell(trace) }}
|
||||
</ng-template>
|
||||
<!-- Caso para "input" con modal -->
|
||||
<ng-container *ngSwitchCase="'input'">
|
||||
<button mat-icon-button (click)="openInputModal(trace.input)">
|
||||
<mat-icon>info</mat-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<!-- Para cualquier otro caso (default) -->
|
||||
<ng-container *ngSwitchDefault>
|
||||
{{ column.cell(trace) }}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="paginator-container" joyrideStep="paginationStep" text="{{ 'paginationStepText' | translate }}">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,9 @@ import { FormControl } from '@angular/forms';
|
|||
import { map, startWith } from 'rxjs/operators';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
import { MatDialog } from "@angular/material/dialog";
|
||||
import { InputDialogComponent } from "./input-dialog/input-dialog.component";
|
||||
import { ProgressBarMode, MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
|
||||
@Component({
|
||||
selector: 'app-task-logs',
|
||||
|
@ -23,6 +26,9 @@ export class TaskLogsComponent implements OnInit {
|
|||
loading: boolean = true;
|
||||
pageSizeOptions: number[] = [10, 20, 30, 50];
|
||||
datePipe: DatePipe = new DatePipe('es-ES');
|
||||
mode: ProgressBarMode = 'buffer';
|
||||
progress = 65;
|
||||
bufferValue = 0;
|
||||
|
||||
columns = [
|
||||
{
|
||||
|
@ -50,6 +56,11 @@ export class TaskLogsComponent implements OnInit {
|
|||
header: 'Hilo de trabajo',
|
||||
cell: (trace: any) => `${trace.jobId}`
|
||||
},
|
||||
{
|
||||
columnDef: 'input',
|
||||
header: 'Input',
|
||||
cell: (trace: any) => `${trace.input}`
|
||||
},
|
||||
{
|
||||
columnDef: 'output',
|
||||
header: 'Logs',
|
||||
|
@ -75,7 +86,9 @@ export class TaskLogsComponent implements OnInit {
|
|||
commandControl = new FormControl();
|
||||
|
||||
constructor(private http: HttpClient,
|
||||
private joyrideService: JoyrideService) {}
|
||||
private joyrideService: JoyrideService,
|
||||
private dialog: MatDialog
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadTraces();
|
||||
|
@ -121,6 +134,13 @@ export class TaskLogsComponent implements OnInit {
|
|||
this.loadTraces();
|
||||
}
|
||||
|
||||
openInputModal(inputData: any): void {
|
||||
this.dialog.open(InputDialogComponent, {
|
||||
width: '700px',
|
||||
data: { input: inputData }
|
||||
});
|
||||
}
|
||||
|
||||
loadTraces(): void {
|
||||
this.loading = true;
|
||||
const url = `${this.baseUrl}/traces?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`;
|
||||
|
@ -199,12 +219,12 @@ export class TaskLogsComponent implements OnInit {
|
|||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'resetFiltersStep',
|
||||
'clientSelectStep',
|
||||
'commandSelectStep',
|
||||
'tableStep',
|
||||
'paginationStep'
|
||||
'titleStep',
|
||||
'resetFiltersStep',
|
||||
'clientSelectStep',
|
||||
'commandSelectStep',
|
||||
'tableStep',
|
||||
'paginationStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.calendar-button-row {
|
||||
.command-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
|
@ -27,7 +28,6 @@
|
|||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
|
@ -35,8 +35,8 @@ table {
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
box-sizing: border-box;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
}
|
||||
|
||||
.search-string {
|
||||
|
@ -53,7 +53,8 @@ table {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'CommandsTitle' | translate }}</h2>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'CommandsTitle' | translate }}</h2>
|
||||
</div>
|
||||
<div class="command-button-row" joyrideStep="addCommandStep" text="{{ 'addCommandStepText' | translate }}">
|
||||
<button mat-flat-button color="primary" (click)="openCreateCommandModal()">{{ 'addCommand' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>{{ 'searchCommandLabel' | translate }}</mat-label>
|
||||
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
|
||||
<div *ngIf="loading" class="loading-container" joyrideStep="loadingStep" text="{{ 'loadingStepText' | translate }}">
|
||||
<mat-spinner></mat-spinner>
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!loading">
|
||||
|
|
|
@ -19,6 +19,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|||
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import { LoadingComponent } from '../../../shared/loading/loading.component';
|
||||
|
||||
describe('CommandsComponent', () => {
|
||||
let component: CommandsComponent;
|
||||
|
@ -26,7 +27,7 @@ describe('CommandsComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [CommandsComponent],
|
||||
declarations: [CommandsComponent, LoadingComponent],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
ToastrModule.forRoot(),
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
<h2 class="title">{{ 'clientDetailsTitle' | translate }}</h2>
|
||||
<div class="client-button-row">
|
||||
<button mat-flat-button color="primary" (click)="onEditClick($event, clientData.uuid)" i18n="@@editImage">Editar</button>
|
||||
<button mat-flat-button color="primary" [matMenuTriggerFor]="commandMenu">{{ 'commandsButton' | translate }}</button>
|
||||
<button mat-flat-button color="primary" (click)="onEditClick($event, clientData.uuid)"
|
||||
i18n="@@editImage">Editar</button>
|
||||
<button mat-flat-button color="primary" [matMenuTriggerFor]="commandMenu">{{ 'commandsButton' | translate
|
||||
}}</button>
|
||||
</div>
|
||||
<mat-menu #commandMenu="matMenu">
|
||||
<button mat-menu-item *ngFor="let command of arrayCommands" (click)="onCommandSelect(command.slug)">
|
||||
|
@ -17,9 +19,7 @@
|
|||
{{ 'Back' | translate }}
|
||||
</button>
|
||||
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div *ngIf="!loading" class="client-info">
|
||||
<div class="info-section">
|
||||
|
@ -67,11 +67,7 @@
|
|||
<div class="charts-container">
|
||||
<ng-container *ngIf="diskUsageData && diskUsageData.length > 0">
|
||||
<div *ngFor="let disk of chartDisk" class="disk-usage">
|
||||
<ngx-charts-pie-chart
|
||||
[view]="view"
|
||||
[results]="disk.chartData"
|
||||
[doughnut]="true"
|
||||
>
|
||||
<ngx-charts-pie-chart [view]="view" [results]="disk.chartData" [doughnut]="true">
|
||||
</ngx-charts-pie-chart>
|
||||
|
||||
<h3>Disco {{ disk.diskNumber }}</h3>
|
||||
|
@ -80,5 +76,4 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -140,11 +140,11 @@ export class ClientMainViewComponent implements OnInit {
|
|||
|
||||
updateNetworkData() {
|
||||
this.networkData = [
|
||||
{ property: 'Padre', value: this.clientData?.organizationalUnit?.name || '' },
|
||||
{ property: 'Pxe', value: this.clientData?.template?.name || '' },
|
||||
{ property: 'Remote Pc', value: this.clientData.remotePc || '' },
|
||||
{ property: 'Subred', value: this.clientData?.subnet || '' },
|
||||
{ property: 'OGlive', value: this.clientData?.ogLive?.name || '' },
|
||||
{ property: 'Autoexec', value: '' },
|
||||
{ property: 'Repositorio', value: this.clientData?.repository?.name || '' },
|
||||
];
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ export class CreateClientImageComponent {
|
|||
next: (response) => {
|
||||
this.toastService.success('Petición de creación de imagen enviada');
|
||||
this.loading = false;
|
||||
this.router.navigate(['/images']);
|
||||
this.router.navigate(['/commands-logs']);
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
|
|
|
@ -52,8 +52,9 @@
|
|||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione imagen</mat-label>
|
||||
<mat-select [(ngModel)]="selectedImage" name="selectedImage">
|
||||
<mat-option *ngFor="let image of images" [value]="image['@id']">{{ image.name }}</mat-option>
|
||||
<mat-option *ngFor="let image of images" [value]="image">{{ image.image?.name }}</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint *ngIf="clientData">Imágenes alojadas en {{ clientData[0].repository?.name }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
|
@ -138,6 +139,6 @@
|
|||
|
||||
<mat-form-field appearance="fill" class="input-field">
|
||||
<mat-label>Semilla</mat-label>
|
||||
<input matInput [(ngModel)]="p2pTime" name="p2pTime">
|
||||
<input matInput [(ngModel)]="p2pTime" name="p2pTime" type="number">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,7 @@ export class DeployImageComponent {
|
|||
partitions: any[] = [];
|
||||
images: any[] = [];
|
||||
clientName: string = '';
|
||||
selectedImage: string | null = null;
|
||||
selectedImage: any = null;
|
||||
selectedOption: string | null = 'deploy-image';
|
||||
selectedMethod: string | null = null;
|
||||
selectedPartition: any = null;
|
||||
|
@ -139,10 +139,25 @@ export class DeployImageComponent {
|
|||
}
|
||||
|
||||
loadImages() {
|
||||
const url = `${this.baseUrl}/images?status=success&page=1&itemsPerPage=1000`;
|
||||
if (!this.clientData || this.clientData.length === 0 || !this.clientData[0]) {
|
||||
console.error('Error: clientData es nulo, indefinido o vacío.');
|
||||
return;
|
||||
}
|
||||
|
||||
const repositoryId =
|
||||
this.clientData[0]?.repository?.id ??
|
||||
this.clientData[0]?.organizationalUnit?.networkSettings?.repository?.id;
|
||||
|
||||
if (!repositoryId) {
|
||||
console.error('Error: No se encontró repositoryId en clientData.');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `${this.baseUrl}/image-image-repositories?status=success&repository.id=${repositoryId}&page=1&itemsPerPage=1000`;
|
||||
|
||||
this.http.get(url).subscribe(
|
||||
(response: any) => {
|
||||
this.images = response['hydra:member'];
|
||||
this.images = response?.['hydra:member'] || [];
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar las imágenes:', error);
|
||||
|
@ -155,22 +170,24 @@ export class DeployImageComponent {
|
|||
|
||||
if (!this.selectedImage) {
|
||||
this.toastService.error('Debe seleccionar una imagen');
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.selectedMethod) {
|
||||
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;
|
||||
}
|
||||
|
||||
this.toastService.info('Preparando petición de despliegue');
|
||||
|
||||
|
||||
const payload = {
|
||||
clients: this.clientData.map((client: any) => client['@id']),
|
||||
method: this.selectedMethod,
|
||||
|
@ -187,7 +204,7 @@ export class DeployImageComponent {
|
|||
maxClients: this.mcastMaxClients,
|
||||
};
|
||||
|
||||
this.http.post(`${this.baseUrl}${this.selectedImage}/deploy-image`, payload)
|
||||
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');
|
||||
|
|
|
@ -265,14 +265,14 @@ export class PartitionAssistantComponent {
|
|||
|
||||
if (totalPartitionSize > this.selectedDisk.totalDiskSize) {
|
||||
this.errorMessage = 'El tamaño total de las particiones en el disco seleccionado excede el tamaño total del disco.';
|
||||
this.loading = false;
|
||||
return;
|
||||
} else {
|
||||
this.errorMessage = '';
|
||||
}
|
||||
|
||||
const modifiedPartitions = this.selectedDisk.partitions.filter((partition: { removed: any; format: any; }) => !partition.removed || partition.format);
|
||||
|
||||
if (modifiedPartitions.length === 0) {
|
||||
this.loading = false;
|
||||
this.errorMessage = 'No hay cambios para guardar en el disco seleccionado.';
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -343,7 +343,6 @@ mat-tree mat-tree-node.disabled:hover {
|
|||
|
||||
.clients-container {
|
||||
width: 75%;
|
||||
padding: 0px 16px 16px 16px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
@ -364,7 +363,6 @@ mat-tree mat-tree-node.disabled:hover {
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 2px;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
|
@ -400,13 +398,13 @@ button[mat-raised-button] {
|
|||
.clients-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 8px; /* Espaciado reducido entre cards */
|
||||
gap: 4px; /* Espaciado reducido entre cards */
|
||||
}
|
||||
|
||||
.clients-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.client-item-list {
|
||||
|
@ -424,9 +422,10 @@ button[mat-raised-button] {
|
|||
}
|
||||
|
||||
.clients-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr); /* 6 columnas por fila */
|
||||
gap: 16px; /* Espaciado entre tarjetas */
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.clients-list .list-item-content {
|
||||
|
@ -436,9 +435,15 @@ button[mat-raised-button] {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.action-icons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.client-card, .list-item-content {
|
||||
border: 1px solid #ccc;
|
||||
padding: 1rem;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
@ -484,7 +489,6 @@ button[mat-raised-button] {
|
|||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
@ -510,6 +514,7 @@ button[mat-raised-button] {
|
|||
font-size: x-large;
|
||||
display: block;
|
||||
padding: 1rem 1rem 1rem 13px;
|
||||
margin-left: 0.6rem;
|
||||
}
|
||||
|
||||
.no-clients-info {
|
||||
|
@ -517,5 +522,5 @@ button[mat-raised-button] {
|
|||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 1.5rem;
|
||||
margin-left: 16px;
|
||||
}
|
||||
margin-left: 1.6rem;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
matTooltip="{{ 'newOrganizationalUnitTooltip' | translate }}" matTooltipShowDelay="1000">
|
||||
{{ 'newOrganizationalUnitButton' | translate }}
|
||||
</button>
|
||||
<button mat-flat-button color="primary" [matMenuTriggerFor]="menuClients">{{ 'newClientButton' | translate }}</button>
|
||||
<button mat-flat-button color="primary" [matMenuTriggerFor]="menuClients">{{ 'newClientButton' | translate
|
||||
}}</button>
|
||||
<mat-menu #menuClients="matMenu">
|
||||
<button mat-menu-item (click)="addClient($event)">{{ 'newSingleClientButton' | translate }}</button>
|
||||
<button mat-menu-item (click)="addMultipleClients($event)">{{ 'newMultipleClientButton' | translate }}</button>
|
||||
|
@ -26,332 +27,324 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters Panel -->
|
||||
<div class="filters-panel" joyrideStep="filtersPanelStep" text="{{ 'filtersPanelStepText' | translate }}">
|
||||
<div class="filters-container">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>{{ 'searchTree' | translate }}</mat-label>
|
||||
<input matInput #treeSearchInput (input)="onTreeFilterInput($event)" placeholder="Centro, aula, grupos ..." />
|
||||
<button
|
||||
*ngIf="treeSearchInput.value"
|
||||
mat-icon-button
|
||||
matSuffix
|
||||
aria-label="Clear tree search"
|
||||
(click)="clearTreeSearch(treeSearchInput)"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>{{ 'searchClient' | translate }}</mat-label>
|
||||
<input matInput #clientSearchInput (input)="onClientFilterInput($event)" placeholder="Nombre, IP, estado o MAC" />
|
||||
<button
|
||||
*ngIf="clientSearchInput.value"
|
||||
mat-icon-button
|
||||
matSuffix
|
||||
aria-label="Clear client search"
|
||||
(click)="clearClientSearch(clientSearchInput)"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Funcionalidad actualmente deshabilitada-->
|
||||
<!-- <mat-form-field appearance="outline">
|
||||
<mat-select (selectionChange)="loadSelectedFilter($event.value)" placeholder="Cargar filtro" disabled>
|
||||
<mat-option *ngFor="let savedFilter of savedFilterNames" [value]="savedFilter">
|
||||
{{ savedFilter[0] }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>{{ 'filterByType' | translate }}</mat-label>
|
||||
<mat-select [(value)]="selectedTreeFilter" (selectionChange)="filterTree(searchTerm, $event.value)" disabled>
|
||||
<mat-option [value]=""> {{ 'all' | translate }} </mat-option>
|
||||
<mat-option value="classrooms-group">{{ 'classroomsGroup' | translate }}</mat-option>
|
||||
<mat-option value="classroom">{{ 'classrooms' | translate }}</mat-option>
|
||||
<mat-option value="group">{{ 'computerGroups' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field> -->
|
||||
</div>
|
||||
<div *ngIf="initialLoading; else contentTemplate">
|
||||
<app-loading [isLoading]="initialLoading"></app-loading>
|
||||
</div>
|
||||
|
||||
<!-- Unit details view-->
|
||||
<div class="main-container">
|
||||
<!-- Tree view -->
|
||||
<div class="tree-container">
|
||||
<mat-tree [dataSource]="treeDataSource" [treeControl]="treeControl">
|
||||
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id}" *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding (click)="onNodeClick(node)">
|
||||
<button mat-icon-button matTreeNodeToggle [disabled]="!node.expandable" [ngClass]="{'disabled-toggle': !node.expandable}">
|
||||
<mat-icon>{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}</mat-icon>
|
||||
<ng-template #contentTemplate>
|
||||
<!-- Filters Panel -->
|
||||
<div class="filters-panel" joyrideStep="filtersPanelStep" text="{{ 'filtersPanelStepText' | translate }}">
|
||||
<div class="filters-container">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>{{ 'searchTree' | translate }}</mat-label>
|
||||
<input matInput #treeSearchInput (input)="onTreeFilterInput($event)" placeholder="Centro, aula, grupos ..." />
|
||||
<button *ngIf="treeSearchInput.value" mat-icon-button matSuffix aria-label="Clear tree search"
|
||||
(click)="clearTreeSearch(treeSearchInput)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
<mat-icon class="node-icon {{ node.type }}">
|
||||
{{
|
||||
node.type === 'organizational-unit' ? 'apartment'
|
||||
: node.type === 'classrooms-group' ? 'meeting_room'
|
||||
: node.type === 'classroom' ? 'school'
|
||||
: node.type === 'clients-group' ? 'lan'
|
||||
: node.type === 'client' ? 'computer'
|
||||
: 'group'
|
||||
}}
|
||||
</mat-icon>
|
||||
<span>{{ node.name }}</span>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuNode" (click)="onNodeClick(node)">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>{{ 'searchClient' | translate }}</mat-label>
|
||||
<input matInput #clientSearchInput (input)="onClientFilterInput($event)"
|
||||
placeholder="Nombre, IP, estado o MAC" />
|
||||
<button *ngIf="clientSearchInput.value" mat-icon-button matSuffix aria-label="Clear client search"
|
||||
(click)="clearClientSearch(clientSearchInput)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-tree-node>
|
||||
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id}" *matTreeNodeDef="let node; when: isLeafNode" matTreeNodePadding (click)="onNodeClick(node)">
|
||||
<button mat-icon-button matTreeNodeToggle [disabled]="true" class="disabled-toggle"></button>
|
||||
<mat-icon style="color: green;">
|
||||
{{
|
||||
node.type === 'organizational-unit' ? 'apartment'
|
||||
: node.type === 'classrooms-group' ? 'meeting_room'
|
||||
: node.type === 'classroom' ? 'school'
|
||||
: node.type === 'clients-group' ? 'lan'
|
||||
: node.type === 'client' ? 'computer'
|
||||
: 'group'
|
||||
}}
|
||||
</mat-icon>
|
||||
<span>{{ node.name }}</span>
|
||||
<ng-container *ngIf="node.type === 'client'">
|
||||
<span> - IP: {{ node.ip }}</span>
|
||||
</ng-container>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuNode" (click)="onNodeClick(node)">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-tree-node>
|
||||
</mat-tree>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Funcionalidad actualmente deshabilitada-->
|
||||
<!-- <mat-form-field appearance="outline">
|
||||
<mat-select (selectionChange)="loadSelectedFilter($event.value)" placeholder="Cargar filtro" disabled>
|
||||
<mat-option *ngFor="let savedFilter of savedFilterNames" [value]="savedFilter">
|
||||
{{ savedFilter[0] }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>{{ 'filterByType' | translate }}</mat-label>
|
||||
<mat-select [(value)]="selectedTreeFilter" (selectionChange)="filterTree(searchTerm, $event.value)" disabled>
|
||||
<mat-option [value]=""> {{ 'all' | translate }} </mat-option>
|
||||
<mat-option value="classrooms-group">{{ 'classroomsGroup' | translate }}</mat-option>
|
||||
<mat-option value="classroom">{{ 'classrooms' | translate }}</mat-option>
|
||||
<mat-option value="group">{{ 'computerGroups' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider [vertical]="true"></mat-divider>
|
||||
|
||||
<!-- Tree node actions -->
|
||||
<mat-menu restoreFocus=false #commandMenu="matMenu">
|
||||
<button mat-menu-item *ngFor="let command of commands" (click)="executeCommand(command, selectedNode)">
|
||||
<span>{{ command.name }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
<mat-menu #menuNode="matMenu">
|
||||
<button mat-menu-item (click)="onShowDetailsClick($event, selectedNode)">
|
||||
<mat-icon matTooltip="{{ 'viewUnitTooltip' | translate }}" matTooltipHideDelay="0">visibility</mat-icon>
|
||||
<span>{{ 'viewUnitMenu' | translate }}</span>
|
||||
</button>
|
||||
<button *ngIf="selectedNode?.type === 'classroom'" mat-menu-item (click)="onRoomMap(selectedNode)">
|
||||
<mat-icon>map</mat-icon>
|
||||
<span>{{ 'roomMap' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addClient($event, selectedNode)">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span>{{ 'newSingleClientButton' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addMultipleClients($event, selectedNode)">
|
||||
<mat-icon>playlist_add</mat-icon>
|
||||
<span>{{ 'newMultipleClientButton' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addOU($event, selectedNode)">
|
||||
<mat-icon>account_tree</mat-icon>
|
||||
<span>{{ 'addOrganizationalUnit' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onEditNode($event, selectedNode)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span>{{ 'edit' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, selectedNode)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>{{ 'delete' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
<!-- Clients view -->
|
||||
<div class="clients-container">
|
||||
<div class="clients-view-header">
|
||||
<span class="clients-title-name">{{ 'clients' | translate }}
|
||||
<strong>{{ selectedNode?.name }}</strong>
|
||||
</span>
|
||||
<div class="view-type-container">
|
||||
<app-execute-command
|
||||
[clientData]="arrayClients"
|
||||
[buttonType]="'text'"
|
||||
[buttonText]="'Ejecutar comandos'"
|
||||
></app-execute-command>
|
||||
<button mat-button color="primary" (click)="toggleView('card')" [disabled]="currentView === 'card'">
|
||||
<mat-icon>grid_view</mat-icon> {{ 'Vista Tarjeta' | translate }}
|
||||
</button>
|
||||
<button mat-button color="primary" (click)="toggleView('list')" [disabled]="currentView === 'list'">
|
||||
<mat-icon>list</mat-icon> {{ 'Vista Lista' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- Unit details view-->
|
||||
<div class="main-container">
|
||||
<!-- Tree view -->
|
||||
<div class="tree-container">
|
||||
<mat-tree [dataSource]="treeDataSource" [treeControl]="treeControl">
|
||||
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id}"
|
||||
*matTreeNodeDef="let node; when: hasChild" matTreeNodePadding (click)="onNodeClick(node)">
|
||||
<button mat-icon-button matTreeNodeToggle [disabled]="!node.expandable"
|
||||
[ngClass]="{'disabled-toggle': !node.expandable}">
|
||||
<mat-icon>{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}</mat-icon>
|
||||
</button>
|
||||
<mat-icon class="node-icon {{ node.type }}">
|
||||
{{
|
||||
node.type === 'organizational-unit' ? 'apartment'
|
||||
: node.type === 'classrooms-group' ? 'meeting_room'
|
||||
: node.type === 'classroom' ? 'school'
|
||||
: node.type === 'clients-group' ? 'lan'
|
||||
: node.type === 'client' ? 'computer'
|
||||
: 'group'
|
||||
}}
|
||||
</mat-icon>
|
||||
<span>{{ node.name }}</span>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuNode" (click)="onNodeClick(node)">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-tree-node>
|
||||
<mat-tree-node [ngClass]="{'selected-node': selectedNode?.id === node.id}"
|
||||
*matTreeNodeDef="let node; when: isLeafNode" matTreeNodePadding (click)="onNodeClick(node)">
|
||||
<button mat-icon-button matTreeNodeToggle [disabled]="true" class="disabled-toggle"></button>
|
||||
<mat-icon style="color: green;">
|
||||
{{
|
||||
node.type === 'organizational-unit' ? 'apartment'
|
||||
: node.type === 'classrooms-group' ? 'meeting_room'
|
||||
: node.type === 'classroom' ? 'school'
|
||||
: node.type === 'clients-group' ? 'lan'
|
||||
: node.type === 'client' ? 'computer'
|
||||
: 'group'
|
||||
}}
|
||||
</mat-icon>
|
||||
<span>{{ node.name }}</span>
|
||||
<ng-container *ngIf="node.type === 'client'">
|
||||
<span> - IP: {{ node.ip }}</span>
|
||||
</ng-container>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuNode" (click)="onNodeClick(node)">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</mat-tree-node>
|
||||
</mat-tree>
|
||||
</div>
|
||||
|
||||
<div *ngIf="(selectedClients.data?.length || 0) > 0; else noClientsTemplate">
|
||||
<!-- Cards view -->
|
||||
<div class="clients-grid" *ngIf="currentView === 'card'">
|
||||
<div *ngFor="let client of selectedClients.data" class="client-item">
|
||||
<div class="client-card">
|
||||
<img
|
||||
[src]="'assets/images/ordenador_' + client.status + '.png'"
|
||||
alt="Client Icon"
|
||||
class="client-image" />
|
||||
<mat-divider [vertical]="true"></mat-divider>
|
||||
|
||||
<div class="client-details">
|
||||
<span class="client-name">{{ client.name }}</span>
|
||||
<span class="client-ip">{{ client.ip }}</span>
|
||||
<span class="client-ip">{{ client.mac }}</span>
|
||||
<!-- Tree node actions -->
|
||||
<mat-menu restoreFocus=false #commandMenu="matMenu">
|
||||
<button mat-menu-item *ngFor="let command of commands" (click)="executeCommand(command, selectedNode)">
|
||||
<span>{{ command.name }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
<mat-menu #menuNode="matMenu">
|
||||
<button mat-menu-item (click)="onShowDetailsClick($event, selectedNode)">
|
||||
<mat-icon matTooltip="{{ 'viewUnitTooltip' | translate }}" matTooltipHideDelay="0">visibility</mat-icon>
|
||||
<span>{{ 'viewUnitMenu' | translate }}</span>
|
||||
</button>
|
||||
<button *ngIf="selectedNode?.type === 'classroom'" mat-menu-item (click)="onRoomMap(selectedNode)">
|
||||
<mat-icon>map</mat-icon>
|
||||
<span>{{ 'roomMap' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addClient($event, selectedNode)">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span>{{ 'newSingleClientButton' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addMultipleClients($event, selectedNode)">
|
||||
<mat-icon>playlist_add</mat-icon>
|
||||
<span>{{ 'newMultipleClientButton' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addOU($event, selectedNode)">
|
||||
<mat-icon>account_tree</mat-icon>
|
||||
<span>{{ 'addOrganizationalUnit' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onEditNode($event, selectedNode)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span>{{ 'edit' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, selectedNode)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>{{ 'delete' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
<div class="action-icons">
|
||||
<button
|
||||
*ngIf="(!syncStatus || syncingClientId !== client.uuid)"
|
||||
mat-icon-button color="primary"
|
||||
(click)="getStatus(client, selectedNode)">
|
||||
<mat-icon>sync</mat-icon>
|
||||
</button>
|
||||
<!-- Clients view -->
|
||||
<div class="clients-container">
|
||||
<div class="clients-view-header">
|
||||
<span [ngStyle]="{ visibility: isLoadingClients ? 'hidden' : 'visible' }" class="clients-title-name">
|
||||
{{ 'clients' | translate }}
|
||||
<strong>{{ selectedNode?.name }}</strong>
|
||||
</span>
|
||||
<div class="view-type-container">
|
||||
<app-execute-command [clientData]="arrayClients" [buttonType]="'text'"
|
||||
[buttonText]="'Ejecutar comandos'"></app-execute-command>
|
||||
<button mat-button color="primary" (click)="toggleView('card')" [disabled]="currentView === 'card'">
|
||||
<mat-icon>grid_view</mat-icon> {{ 'Vista Tarjeta' | translate }}
|
||||
</button>
|
||||
<button mat-button color="primary" (click)="toggleView('list')" [disabled]="currentView === 'list'">
|
||||
<mat-icon>list</mat-icon> {{ 'Vista Lista' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
*ngIf="syncStatus && syncingClientId === client.uuid"
|
||||
mat-icon-button color="primary">
|
||||
<mat-spinner diameter="24"></mat-spinner>
|
||||
</button>
|
||||
<app-loading [isLoading]="isLoadingClients"></app-loading>
|
||||
|
||||
<button mat-icon-button color="primary" (click)="onShowClientDetail($event, client)">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
</button>
|
||||
<app-execute-command
|
||||
[clientData]="[client]"
|
||||
[buttonType]="'icon'"
|
||||
[icon]="'terminal'"
|
||||
></app-execute-command>
|
||||
<div *ngIf="!isLoadingClients">
|
||||
<div *ngIf="hasClients; else noClientsTemplate">
|
||||
<!-- Cards view -->
|
||||
<div class="clients-grid" *ngIf="currentView === 'card'">
|
||||
<div *ngFor="let client of arrayClients" class="client-item">
|
||||
<div class="client-card">
|
||||
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(client)"
|
||||
[checked]="selection.isSelected(client)" [disabled]="client.status === 'busy'">
|
||||
</mat-checkbox>
|
||||
<img [src]="'assets/images/ordenador_' + client.status + '.png'" alt="Client Icon"
|
||||
class="client-image" />
|
||||
|
||||
<div class="client-details">
|
||||
<span class="client-name">{{ client.name }}</span>
|
||||
<span class="client-ip">{{ client.ip }}</span>
|
||||
<span class="client-ip">{{ client.mac }}</span>
|
||||
<div class="action-icons">
|
||||
<button *ngIf="(!syncStatus || syncingClientId !== client.uuid)" mat-icon-button color="primary"
|
||||
(click)="getStatus(client, selectedNode)">
|
||||
<mat-icon>sync</mat-icon>
|
||||
</button>
|
||||
|
||||
<button *ngIf="syncStatus && syncingClientId === client.uuid" mat-icon-button color="primary">
|
||||
<mat-spinner diameter="24"></mat-spinner>
|
||||
</button>
|
||||
|
||||
<app-execute-command [clientData]="[client]" [buttonType]="'icon'"
|
||||
[icon]="'terminal'"></app-execute-command>
|
||||
|
||||
<button mat-icon-button [matMenuTriggerFor]="clientMenu" color="primary">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
|
||||
<mat-menu #clientMenu="matMenu">
|
||||
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span>{{ 'edit' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onShowClientDetail($event, client)">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
<span>{{ 'viewDetails' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, client)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>{{ 'delete' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- List view -->
|
||||
<div class="clients-table" *ngIf="currentView === 'list'">
|
||||
<table mat-table matSort [dataSource]="selectedClients" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox (change)="$event ? toggleAllRows() : null"
|
||||
[checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||
</mat-checkbox>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-checkbox (click)="$event.stopPropagation()" (change)="toggleRow(row)"
|
||||
[checked]="selection.isSelected(row)" [disabled]="row.status === 'busy'">
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="status">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'status' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}" matTooltipPosition="left"
|
||||
matTooltipShowDelay="500">
|
||||
<img [src]="'assets/images/ordenador_' + client.status + '.png'" alt="Client Icon"
|
||||
class="client-image" />
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="sync">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'sync' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client">
|
||||
<button *ngIf="(!syncStatus || syncingClientId !== client.uuid)" mat-icon-button color="primary"
|
||||
(click)="getStatus(client, selectedNode)">
|
||||
<mat-icon>sync</mat-icon>
|
||||
</button>
|
||||
|
||||
<button *ngIf="syncStatus && syncingClientId === client.uuid" mat-icon-button color="primary">
|
||||
<mat-spinner diameter="24"></mat-spinner>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'name' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}" matTooltipPosition="left"
|
||||
matTooltipShowDelay="500">
|
||||
<div class="client-info">
|
||||
<div class="client-name">{{ client.name }}</div>
|
||||
<div class="client-ip">{{ client.ip }}</div>
|
||||
<div class="client-ip">{{ client.mac }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="oglive">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> OG Live </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.ogLive?.date | date }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="maintenace">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'maintenance' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.maintenance }} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="subnet">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'subnet' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.subnet }} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="pxeTemplate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'pxeTemplate' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.template?.name }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="parentName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'parent' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.parentName }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'actions' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client">
|
||||
<button mat-icon-button [matMenuTriggerFor]="clientMenu" color="primary">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<app-execute-command [clientData]="[client]" [buttonType]="'icon'"
|
||||
[icon]="'terminal'"></app-execute-command>
|
||||
<mat-menu #clientMenu="matMenu">
|
||||
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span>{{ 'edit' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onShowClientDetail($event, client)">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
<span>{{ 'viewDetails' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, client)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>{{ 'delete' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<mat-paginator [pageSize]="10" [pageSizeOptions]="[5, 10, 20, 50]" showFirstLastButtons></mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- List view -->
|
||||
<div class="clients-table" *ngIf="currentView === 'list'">
|
||||
<table mat-table matSort [dataSource]="selectedClients" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox (change)="$event ? toggleAllRows() : null"
|
||||
[checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()"
|
||||
>
|
||||
</mat-checkbox>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-checkbox (click)="$event.stopPropagation()"
|
||||
(change)="toggleRow(row)"
|
||||
[checked]="selection.isSelected(row)"
|
||||
[disabled]="row.status === 'busy'"
|
||||
>
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="status">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'status' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}"
|
||||
matTooltipPosition="left" matTooltipShowDelay="500">
|
||||
<img
|
||||
[src]="'assets/images/ordenador_' + client.status + '.png'"
|
||||
alt="Client Icon"
|
||||
class="client-image" />
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="sync">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'sync' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client">
|
||||
<button
|
||||
*ngIf="(!syncStatus || syncingClientId !== client.uuid)"
|
||||
mat-icon-button color="primary"
|
||||
(click)="getStatus(client, selectedNode)">
|
||||
<mat-icon>sync</mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
*ngIf="syncStatus && syncingClientId === client.uuid"
|
||||
mat-icon-button color="primary">
|
||||
<mat-spinner diameter="24"></mat-spinner>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'name' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client" matTooltip="{{ getClientPath(client) }}"
|
||||
matTooltipPosition="left" matTooltipShowDelay="500">
|
||||
<div class="client-info">
|
||||
<div class="client-name">{{ client.name }}</div>
|
||||
<div class="client-ip">{{ client.ip }}</div>
|
||||
<div class="client-ip">{{ client.mac }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="oglive">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> OG Live </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ (client.ogLive?.filename || '').slice(0, 15) }}{{ (client.ogLive?.filename?.length > 15) ? '...' : '' }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="maintenace">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'maintenance' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.maintenance }} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="subnet">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'subnet' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.subnet }} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="pxeTemplate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'pxeTemplate' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.template?.name }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="parentName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'parent' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client"> {{ client.parentName }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions" >
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'actions' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let client">
|
||||
<button mat-icon-button [matMenuTriggerFor]="clientMenu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<app-execute-command
|
||||
[clientData]="[client]"
|
||||
[buttonType]="'icon'"
|
||||
[icon]="'terminal'"
|
||||
></app-execute-command>
|
||||
<mat-menu #clientMenu="matMenu">
|
||||
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span>{{ 'edit' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onShowClientDetail($event, client)">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
<span>{{ 'viewDetails' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, client)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>{{ 'delete' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<mat-paginator [pageSize]="10" [pageSizeOptions]="[5, 10, 20, 50]" showFirstLastButtons></mat-paginator>
|
||||
<!-- No clients view -->
|
||||
<ng-template #noClientsTemplate>
|
||||
<div *ngIf="!initialLoading" class="no-clients-info">
|
||||
<span>{{ 'noClients' | translate }}</span>
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- No clients view -->
|
||||
<ng-template #noClientsTemplate>
|
||||
<div *ngIf="isLoadingClients" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
<div *ngIf="!isLoadingClients" class="no-clients-info">
|
||||
<span>{{ 'noClients' | translate }}</span>
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -25,7 +25,8 @@ import { JoyrideModule } from 'ngx-joyride';
|
|||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTreeModule } from '@angular/material/tree';
|
||||
import { TreeNode } from './model/model';
|
||||
import {ExecuteCommandComponent} from "../commands/main-commands/execute-command/execute-command.component";
|
||||
import { LoadingComponent } from '../../shared/loading/loading.component';
|
||||
import { ExecuteCommandComponent } from '../commands/main-commands/execute-command/execute-command.component';
|
||||
|
||||
describe('GroupsComponent', () => {
|
||||
let component: GroupsComponent;
|
||||
|
@ -33,7 +34,7 @@ describe('GroupsComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [GroupsComponent, ExecuteCommandComponent],
|
||||
declarations: [GroupsComponent, ExecuteCommandComponent, LoadingComponent],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
ToastrModule.forRoot(),
|
||||
|
@ -64,8 +65,7 @@ describe('GroupsComponent', () => {
|
|||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -76,18 +76,6 @@ describe('GroupsComponent', () => {
|
|||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call search on ngOnInit', () => {
|
||||
spyOn(component, 'search');
|
||||
component.ngOnInit();
|
||||
expect(component.search).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call search method', () => {
|
||||
spyOn(component, 'search');
|
||||
component.search();
|
||||
expect(component.search).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should clear selection', () => {
|
||||
spyOn(component, 'clearSelection');
|
||||
component.clearSelection();
|
||||
|
@ -121,4 +109,20 @@ describe('GroupsComponent', () => {
|
|||
component.expandPathToNode(node);
|
||||
expect(component.expandPathToNode).toHaveBeenCalledWith(node);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle node click', () => {
|
||||
const node: TreeNode = { id: '1', name: 'Node 1', type: 'type', children: [] };
|
||||
spyOn<any>(component, 'fetchClientsForNode');
|
||||
component.onNodeClick(node);
|
||||
expect(component.selectedNode).toBe(node);
|
||||
expect(component['fetchClientsForNode']).toHaveBeenCalledWith(node);
|
||||
});
|
||||
|
||||
it('should fetch clients for node', () => {
|
||||
const node: TreeNode = { id: '1', name: 'Node 1', type: 'type', children: [] };
|
||||
spyOn(component['http'], 'get').and.callThrough();
|
||||
component.fetchClientsForNode(node);
|
||||
expect(component.isLoadingClients).toBeTrue();
|
||||
expect(component['http'].get).toHaveBeenCalledWith(`${component.baseUrl}/clients?organizationalUnit.id=${node.id}`);
|
||||
});
|
||||
});
|
|
@ -10,9 +10,8 @@ import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree'
|
|||
import { Subscription } from 'rxjs';
|
||||
import { DataService } from './services/data.service';
|
||||
import { UnidadOrganizativa, Client, TreeNode, FlatNode, Command } from './model/model';
|
||||
import { CreateOrganizationalUnitComponent } from './shared/organizational-units/create-organizational-unit/create-organizational-unit.component';
|
||||
import { CreateClientComponent } from './shared/clients/create-client/create-client.component';
|
||||
import { EditOrganizationalUnitComponent } from './shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component';
|
||||
import { ManageOrganizationalUnitComponent } from './shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component';
|
||||
import { EditClientComponent } from './shared/clients/edit-client/edit-client.component';
|
||||
import { ShowOrganizationalUnitComponent } from './shared/organizational-units/show-organizational-unit/show-organizational-unit.component';
|
||||
import { LegendComponent } from './shared/legend/legend.component';
|
||||
|
@ -21,8 +20,8 @@ import { ClassroomViewDialogComponent } from './shared/classroom-view/classroom-
|
|||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import {CreateMultipleClientComponent} from "./shared/clients/create-multiple-client/create-multiple-client.component";
|
||||
import {SelectionModel} from "@angular/cdk/collections";
|
||||
import { CreateMultipleClientComponent } from "./shared/clients/create-multiple-client/create-multiple-client.component";
|
||||
import { SelectionModel } from "@angular/cdk/collections";
|
||||
|
||||
enum NodeType {
|
||||
OrganizationalUnit = 'organizational-unit',
|
||||
|
@ -42,13 +41,14 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
organizationalUnits: UnidadOrganizativa[] = [];
|
||||
selectedUnidad: UnidadOrganizativa | null = null;
|
||||
selectedDetail: UnidadOrganizativa | null = null;
|
||||
loading = false;
|
||||
initialLoading: boolean = true;
|
||||
isLoadingClients: boolean = false;
|
||||
searchTerm = '';
|
||||
treeControl: FlatTreeControl<FlatNode>;
|
||||
treeFlattener: MatTreeFlattener<TreeNode, FlatNode>;
|
||||
treeDataSource: MatTreeFlatDataSource<TreeNode, FlatNode>;
|
||||
selectedNode: TreeNode | null = null;
|
||||
hasClients: boolean = false;
|
||||
commands: Command[] = [];
|
||||
commandsLoading = false;
|
||||
selectedClients = new MatTableDataSource<Client>([]);
|
||||
|
@ -62,7 +62,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
private originalTreeData: TreeNode[] = [];
|
||||
arrayClients: any[] = [];
|
||||
|
||||
displayedColumns: string[] = ['select', 'status','sync', 'name', 'oglive', 'subnet', 'pxeTemplate', 'actions'];
|
||||
displayedColumns: string[] = ['select', 'status', 'sync', 'name', 'oglive', 'subnet', 'pxeTemplate', 'actions'];
|
||||
|
||||
private _sort!: MatSort;
|
||||
private _paginator!: MatPaginator;
|
||||
|
@ -109,8 +109,8 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
this.treeDataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
|
||||
}
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.search();
|
||||
this.updateGridCols();
|
||||
this.refreshData();
|
||||
window.addEventListener('resize', this.updateGridCols);
|
||||
|
@ -124,13 +124,17 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
client.mac?.toLowerCase().includes(lowerTerm)
|
||||
);
|
||||
};
|
||||
|
||||
this.arrayClients = this.selectedClients.data;
|
||||
}
|
||||
|
||||
|
||||
ngOnDestroy(): void {
|
||||
window.removeEventListener('resize', this.updateGridCols);
|
||||
this.subscriptions.unsubscribe();
|
||||
}
|
||||
|
||||
|
||||
private transformer = (node: TreeNode, level: number): FlatNode => ({
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
|
@ -142,15 +146,18 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
'@id': node['@id'],
|
||||
});
|
||||
|
||||
|
||||
toggleView(view: 'card' | 'list'): void {
|
||||
this.currentView = view;
|
||||
}
|
||||
|
||||
|
||||
updateGridCols = (): void => {
|
||||
const width = window.innerWidth;
|
||||
this.cols = width <= 600 ? 1 : width <= 960 ? 2 : width <= 1280 ? 3 : 4;
|
||||
};
|
||||
|
||||
|
||||
clearSelection(): void {
|
||||
this.selectedUnidad = null;
|
||||
this.selectedDetail = null;
|
||||
|
@ -158,6 +165,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
this.selectedNode = null;
|
||||
}
|
||||
|
||||
|
||||
// Función para obtener los filtros guardados actualmente deshabilitada
|
||||
// getFilters(): void {
|
||||
// this.subscriptions.add(
|
||||
|
@ -171,6 +179,8 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
// )
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
getFilters(): void {
|
||||
this.subscriptions.add(
|
||||
this.dataService.getFilters().subscribe(
|
||||
|
@ -184,44 +194,32 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
);
|
||||
}
|
||||
|
||||
search(): void {
|
||||
this.loading = true;
|
||||
this.subscriptions.add(
|
||||
this.dataService.getOrganizationalUnits(this.searchTerm).subscribe(
|
||||
(data) => {
|
||||
this.organizationalUnits = data;
|
||||
this.loading = false;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching organizational units', error);
|
||||
this.loading = false;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private convertToTreeData(data: UnidadOrganizativa): TreeNode {
|
||||
const processNode = (node: UnidadOrganizativa): TreeNode => ({
|
||||
id: node.id,
|
||||
uuid: node.uuid,
|
||||
name: node.name,
|
||||
type: node.type,
|
||||
'@id': node['@id'],
|
||||
children: node.children?.map(processNode) || [],
|
||||
hasClients: (node.clients?.length ?? 0) > 0,
|
||||
});
|
||||
const processNode = (node: UnidadOrganizativa): TreeNode => {
|
||||
const children = node.children?.map(processNode) || [];
|
||||
const hasClients = (node.clients?.length ?? 0) > 0 || children.some(child => child.hasClients);
|
||||
|
||||
return {
|
||||
id: node.id,
|
||||
uuid: node.uuid,
|
||||
name: node.name,
|
||||
type: node.type,
|
||||
'@id': node['@id'],
|
||||
children: children,
|
||||
hasClients: hasClients,
|
||||
};
|
||||
};
|
||||
|
||||
return processNode(data);
|
||||
}
|
||||
|
||||
private refreshData(selectedNodeIdOrUuid?: string): void {
|
||||
this.loading = true;
|
||||
this.isLoadingClients = !!selectedNodeIdOrUuid;
|
||||
|
||||
private refreshData(selectedNodeIdOrUuid?: string): void {
|
||||
this.dataService.getOrganizationalUnits().subscribe({
|
||||
next: (data) => {
|
||||
this.originalTreeData = data.map((unidad) => this.convertToTreeData(unidad));
|
||||
this.treeDataSource.data = [...this.originalTreeData];
|
||||
|
||||
if (selectedNodeIdOrUuid) {
|
||||
this.selectedNode = this.findNodeByIdOrUuid(this.treeDataSource.data, selectedNodeIdOrUuid);
|
||||
|
||||
|
@ -238,21 +236,18 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
} else {
|
||||
this.selectedNode = null;
|
||||
this.selectedClients.data = [];
|
||||
this.initialLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
this.isLoadingClients = false;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error fetching organizational units', error);
|
||||
this.toastr.error('Ocurrió un error al cargar las unidades organizativas');
|
||||
this.loading = false;
|
||||
this.isLoadingClients = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
expandPathToNode(node: TreeNode): void {
|
||||
const path: TreeNode[] = [];
|
||||
let currentNode: TreeNode | null = node;
|
||||
|
@ -270,6 +265,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
private findParentNode(treeData: TreeNode[], childId: string): TreeNode | null {
|
||||
for (const node of treeData) {
|
||||
if (node.children?.some((child) => child.id === childId)) {
|
||||
|
@ -286,6 +282,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
private findNodeByIdOrUuid(treeData: TreeNode[], identifier: string): TreeNode | null {
|
||||
const search = (nodes: TreeNode[]): TreeNode | null => {
|
||||
for (const node of nodes) {
|
||||
|
@ -300,38 +297,45 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
return search(treeData);
|
||||
}
|
||||
|
||||
|
||||
onNodeClick(node: TreeNode): void {
|
||||
this.selectedNode = node;
|
||||
this.fetchClientsForNode(node);
|
||||
}
|
||||
|
||||
private fetchClientsForNode(node: TreeNode): void {
|
||||
|
||||
public fetchClientsForNode(node: TreeNode): void {
|
||||
this.isLoadingClients = true;
|
||||
this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}`).subscribe({
|
||||
next: (response) => {
|
||||
this.selectedClients.data = response['hydra:member'];
|
||||
this.arrayClients = this.selectedClients.data;
|
||||
this.hasClients = node.hasClients ?? false;
|
||||
this.isLoadingClients = false;
|
||||
this.initialLoading = false;
|
||||
},
|
||||
error: () => {
|
||||
this.isLoadingClients = false;
|
||||
this.initialLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
addOU(event: MouseEvent, parent: TreeNode | null = null): void {
|
||||
event.stopPropagation();
|
||||
const dialogRef = this.dialog.open(CreateOrganizationalUnitComponent, {
|
||||
const dialogRef = this.dialog.open(ManageOrganizationalUnitComponent, {
|
||||
data: { parent },
|
||||
width: '900px',
|
||||
});
|
||||
dialogRef.afterClosed().subscribe((newUnit) => {
|
||||
if (newUnit?.uuid) {
|
||||
console.log('Unidad organizativa creada:', newUnit);
|
||||
this.refreshData(newUnit.uuid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
addClient(event: MouseEvent, organizationalUnit: TreeNode | null = null): void {
|
||||
event.stopPropagation();
|
||||
const targetNode = organizationalUnit || this.selectedNode;
|
||||
|
@ -353,6 +357,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
addMultipleClients(event: MouseEvent, organizationalUnit: TreeNode | null = null): void {
|
||||
event.stopPropagation();
|
||||
const targetNode = organizationalUnit || this.selectedNode;
|
||||
|
@ -377,14 +382,15 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
onEditNode(event: MouseEvent, node: TreeNode | null): void {
|
||||
event.stopPropagation();
|
||||
const uuid = node ? this.extractUuid(node['@id']) : null;
|
||||
if (!uuid) return;
|
||||
|
||||
const dialogRef = node?.type !== NodeType.Client
|
||||
? this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
|
||||
: this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
||||
? this.dialog.open(ManageOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
|
||||
: this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
||||
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
if (node) {
|
||||
|
@ -393,24 +399,28 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
onDeleteClick(event: MouseEvent, node: TreeNode | null): void {
|
||||
onDeleteClick(event: MouseEvent, entity: TreeNode | Client | null): void {
|
||||
event.stopPropagation();
|
||||
const uuid = node ? this.extractUuid(node['@id']) : null;
|
||||
if (!entity) return;
|
||||
|
||||
const uuid = entity['@id'] ? this.extractUuid(entity['@id']) : null;
|
||||
const type = entity.hasOwnProperty('mac') ? NodeType.Client : NodeType.OrganizationalUnit;
|
||||
|
||||
if (!uuid) return;
|
||||
|
||||
if (!node) return;
|
||||
const dialogRef = this.dialog.open(DeleteModalComponent, {
|
||||
width: '400px',
|
||||
data: { name: node.name },
|
||||
data: { name: entity.name },
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
if (result === true) {
|
||||
this.deleteEntityorClient(uuid, node?.type);
|
||||
this.deleteEntityorClient(uuid, type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private deleteEntityorClient(uuid: string, type: string): void {
|
||||
if (!this.selectedNode) return;
|
||||
|
||||
|
@ -441,17 +451,19 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
onEditClick(event: MouseEvent, type: string, uuid: string): void {
|
||||
event.stopPropagation();
|
||||
const dialogRef = type !== NodeType.Client
|
||||
? this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
|
||||
: this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
||||
? this.dialog.open(ManageOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
|
||||
: this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
||||
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
this.refreshData(this.selectedNode?.id);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onRoomMap(room: TreeNode | null): void {
|
||||
if (!room || !room['@id']) return;
|
||||
this.subscriptions.add(
|
||||
|
@ -469,6 +481,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
executeCommand(command: Command, selectedNode: TreeNode | null): void {
|
||||
|
||||
if (!selectedNode) {
|
||||
|
@ -479,15 +492,17 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
onShowClientDetail(event: MouseEvent, client: Client): void {
|
||||
event.stopPropagation();
|
||||
this.router.navigate(['clients', client.uuid], { state: { clientData: client } });
|
||||
}
|
||||
|
||||
|
||||
onShowDetailsClick(event: MouseEvent, data: TreeNode | null): void {
|
||||
event.stopPropagation();
|
||||
if (data && data.type !== NodeType.Client) {
|
||||
this.dialog.open(ShowOrganizationalUnitComponent, { data: { data }, width: '700px' });
|
||||
this.dialog.open(ShowOrganizationalUnitComponent, { data: { data }, width: '800px' });
|
||||
} else {
|
||||
if (data) {
|
||||
this.router.navigate(['clients', this.extractUuid(data['@id'])], { state: { clientData: data } });
|
||||
|
@ -495,10 +510,12 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
openBottomSheet(): void {
|
||||
this.bottomSheet.open(LegendComponent);
|
||||
}
|
||||
|
||||
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: ['groupsTitleStepText', 'filtersPanelStep', 'addStep', 'keyStep', 'tabsStep'],
|
||||
|
@ -507,9 +524,11 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
hasChild = (_: number, node: FlatNode): boolean => node.expandable;
|
||||
isLeafNode = (_: number, node: FlatNode): boolean => !node.expandable;
|
||||
|
||||
|
||||
filterTree(searchTerm: string): void {
|
||||
const expandPaths: TreeNode[][] = [];
|
||||
|
||||
|
@ -545,6 +564,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private expandPath(path: TreeNode[]): void {
|
||||
path.forEach((pathNode) => {
|
||||
const flatNode = this.treeControl.dataNodes?.find((n) => n.id === pathNode.id);
|
||||
|
@ -554,57 +574,68 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
onTreeFilterInput(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const searchTerm = input?.value.trim() || '';
|
||||
this.filterTree(searchTerm);
|
||||
}
|
||||
|
||||
|
||||
onClientFilterInput(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const searchTerm = input?.value || '';
|
||||
this.filterClients(searchTerm);
|
||||
}
|
||||
|
||||
|
||||
filterClients(searchTerm: string): void {
|
||||
this.searchTerm = searchTerm.trim().toLowerCase();
|
||||
this.selectedClients.filter = this.searchTerm;
|
||||
this.arrayClients = this.selectedClients.filteredData;
|
||||
}
|
||||
|
||||
|
||||
public setSelectedNode(node: TreeNode): void {
|
||||
this.selectedNode = node;
|
||||
}
|
||||
|
||||
|
||||
getStatus(client: Client, node: any): void {
|
||||
if (!client.uuid || !client['@id']) return;
|
||||
|
||||
this.syncingClientId = client.uuid;
|
||||
this.syncStatus = true;
|
||||
|
||||
const parentNodeId = client.organizationalUnit?.id || node.id;
|
||||
console.log('Parent node id:', parentNodeId);
|
||||
|
||||
this.subscriptions.add(
|
||||
this.http.post(`${this.baseUrl}${client['@id']}/agent/status`, {}).subscribe(
|
||||
() => {
|
||||
this.toastr.success('Cliente actualizado correctamente');
|
||||
this.syncStatus = false;
|
||||
this.syncingClientId = null;
|
||||
this.refreshData()
|
||||
this.refreshData(parentNodeId)
|
||||
},
|
||||
() => {
|
||||
this.toastr.error('Error de conexión con el cliente');
|
||||
this.syncStatus = false;
|
||||
this.syncingClientId = null;
|
||||
this.refreshData()
|
||||
this.refreshData(parentNodeId)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
isAllSelected() {
|
||||
const numSelected = this.selection.selected.length;
|
||||
const numRows = this.selectedClients.data.length;
|
||||
return numSelected === numRows;
|
||||
}
|
||||
|
||||
|
||||
toggleAllRows() {
|
||||
if (this.isAllSelected()) {
|
||||
this.selection.clear();
|
||||
|
@ -616,39 +647,44 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
this.arrayClients = [...this.selection.selected];
|
||||
}
|
||||
|
||||
|
||||
toggleRow(row: any) {
|
||||
this.selection.toggle(row);
|
||||
this.updateSelectedClients();
|
||||
}
|
||||
|
||||
|
||||
updateSelectedClients() {
|
||||
this.arrayClients = [...this.selection.selected];
|
||||
}
|
||||
|
||||
|
||||
getClientPath(client: Client): string {
|
||||
const path: string[] = [];
|
||||
let currentNode: TreeNode | null = this.findNodeByIdOrUuid(this.treeDataSource.data, client.organizationalUnit.uuid);
|
||||
|
||||
|
||||
while (currentNode) {
|
||||
path.unshift(currentNode.name);
|
||||
currentNode = currentNode.id ? this.findParentNode(this.treeDataSource.data, currentNode.id) : null;
|
||||
}
|
||||
|
||||
|
||||
return path.join(' / ');
|
||||
}
|
||||
|
||||
|
||||
private extractUuid(idPath: string | undefined): string | null {
|
||||
return idPath ? idPath.split('/').pop() || null : null;
|
||||
}
|
||||
|
||||
|
||||
clearTreeSearch(inputElement: HTMLInputElement): void {
|
||||
inputElement.value = '';
|
||||
this.filterTree('');
|
||||
inputElement.value = '';
|
||||
this.filterTree('');
|
||||
}
|
||||
|
||||
|
||||
|
||||
clearClientSearch(inputElement: HTMLInputElement): void {
|
||||
inputElement.value = '';
|
||||
this.filterClients('');
|
||||
this.filterClients('');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,21 +4,15 @@ h1 {
|
|||
font-weight: 400;
|
||||
color: #3f51b5;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.network-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
padding: 50px;
|
||||
padding: 15px 50px 15px 50px;
|
||||
}
|
||||
|
||||
button {
|
||||
|
@ -27,10 +21,6 @@ button {
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mat-slide-toggle {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
mat-option .unit-name {
|
||||
display: block;
|
||||
}
|
||||
|
@ -55,9 +45,6 @@ mat-option .unit-path {
|
|||
.grid-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
}
|
||||
column-gap: 20px;
|
||||
row-gap: 20px;
|
||||
}
|
|
@ -5,11 +5,11 @@
|
|||
<form [formGroup]="clientForm" class="client-form grid-form" *ngIf="!loading">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label i18n="@@organizational-unit-label">Padre</mat-label>
|
||||
<mat-select formControlName="organizationalUnit">
|
||||
<mat-select formControlName="organizationalUnit" (selectionChange)="onParentChange($event)">
|
||||
<mat-select-trigger>
|
||||
{{ getSelectedParentName() }}
|
||||
</mat-select-trigger>
|
||||
<mat-option *ngFor="let unit of parentUnitsWithPaths" [value]="unit.id">
|
||||
<mat-option *ngFor="let unit of parentUnitsWithPaths" [value]="unit.id" >
|
||||
<div class="unit-name">{{ unit.name }}</div>
|
||||
<div style="font-size: smaller; color: gray;">{{ unit.path }}</div>
|
||||
</mat-option>
|
||||
|
@ -25,7 +25,7 @@
|
|||
<mat-label i18n="@@oglive-label">OgLive</mat-label>
|
||||
<mat-select formControlName="ogLive">
|
||||
<mat-option *ngFor="let oglive of ogLives" [value]="oglive['@id']">
|
||||
{{ oglive.filename }}
|
||||
{{ oglive.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
|
|
@ -43,13 +43,13 @@ export class CreateClientComponent implements OnInit {
|
|||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initForm();
|
||||
this.loadParentUnits();
|
||||
this.loadHardwareProfiles();
|
||||
this.loadOgLives();
|
||||
this.loadPxeTemplates();
|
||||
this.loadRepositories();
|
||||
this.loadMenus()
|
||||
this.initForm();
|
||||
}
|
||||
|
||||
initForm(): void {
|
||||
|
@ -65,9 +65,7 @@ export class CreateClientComponent implements OnInit {
|
|||
mac: ['', Validators.required],
|
||||
ip: ['', Validators.required],
|
||||
template: [null],
|
||||
hardwareProfile: [
|
||||
this.data.organizationalUnit?.networkSettings?.hardwareProfile?.['@id'] || null
|
||||
],
|
||||
hardwareProfile: [null],
|
||||
ogLive: [null],
|
||||
repository: [null],
|
||||
menu: [null]
|
||||
|
@ -84,8 +82,19 @@ export class CreateClientComponent implements OnInit {
|
|||
this.parentUnitsWithPaths = this.parentUnits.map(unit => ({
|
||||
id: unit['@id'],
|
||||
name: unit.name,
|
||||
path: this.dataService.getOrganizationalUnitPath(unit, this.parentUnits)
|
||||
path: this.dataService.getOrganizationalUnitPath(unit, this.parentUnits),
|
||||
repository: unit.networkSettings?.repository?.['@id'],
|
||||
hardwareProfile: unit.networkSettings?.hardwareProfile?.['@id'],
|
||||
ogLive: unit.networkSettings?.ogLive?.['@id'],
|
||||
menu: unit.networkSettings?.menu?.['@id']
|
||||
}));
|
||||
|
||||
// 🚀 Ahora que los datos están listos, aplicamos la configuración inicial
|
||||
const initialUnitId = this.clientForm.get('organizationalUnit')?.value;
|
||||
if (initialUnitId) {
|
||||
this.setOrganizationalUnitDefaults(initialUnitId);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
|
@ -165,6 +174,22 @@ export class CreateClientComponent implements OnInit {
|
|||
);
|
||||
}
|
||||
|
||||
onParentChange(event: any): void {
|
||||
this.setOrganizationalUnitDefaults(event.value);
|
||||
}
|
||||
|
||||
setOrganizationalUnitDefaults(unitId: string): void {
|
||||
const selectedUnit: any = this.parentUnitsWithPaths.find(unit => unit.id === unitId);
|
||||
if (selectedUnit) {
|
||||
this.clientForm.patchValue({
|
||||
repository: selectedUnit.repository || null,
|
||||
hardwareProfile: selectedUnit.hardwareProfile || null,
|
||||
ogLive: selectedUnit.ogLive || null,
|
||||
menu: selectedUnit.menu || null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.clientForm.valid) {
|
||||
const formData = this.clientForm.value;
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<mat-label>{{ 'ogLiveLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="ogLive">
|
||||
<mat-option *ngFor="let ogLive of ogLives" [value]="ogLive['@id']">
|
||||
{{ ogLive.filename }}
|
||||
{{ ogLive.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
h1 {
|
||||
text-align: center;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 400;
|
||||
color: #3f51b5;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.network-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
text-transform: none;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mat-slide-toggle {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
mat-slide-toggle{
|
||||
margin-left: 10px;
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
<h1 mat-dialog-title>{{ 'addOrgUnitTitle' | translate }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<!-- Paso 1: General -->
|
||||
<form [formGroup]="generalFormGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'typeLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="type" required>
|
||||
<mat-option *ngFor="let type of filteredTypes" [value]="type">
|
||||
{{ typeTranslations[type] }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'nameLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'createOrgUnitparentLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="parent">
|
||||
<mat-select-trigger>
|
||||
{{ getSelectedParentName() }}
|
||||
</mat-select-trigger>
|
||||
<mat-option *ngFor="let unit of parentUnitsWithPaths" [value]="unit.id">
|
||||
<div>{{ unit.name }}</div>
|
||||
<div style="font-size: smaller; color: gray;">{{ unit.path }}</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'descriptionLabel' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="description"></textarea>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
|
||||
<!-- Paso 2: Información del Aula -->
|
||||
<form *ngIf="generalFormGroup.value.type === 'classroom'" [formGroup]="classroomInfoFormGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'locationLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="location">
|
||||
</mat-form-field>
|
||||
<mat-slide-toggle formControlName="projector">{{ 'projectorToggle' | translate }}</mat-slide-toggle>
|
||||
<mat-slide-toggle formControlName="board">{{ 'boardToggle' | translate }}</mat-slide-toggle>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'capacityLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="capacity" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field" appearance="fill">
|
||||
<mat-label>{{ 'associatedCalendarLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="remoteCalendar" (selectionChange)="onCalendarChange($event)">
|
||||
<mat-option *ngFor="let calendar of calendars" [value]="calendar['@id']">
|
||||
{{ calendar.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
|
||||
<!-- Paso 3: Información Adicional -->
|
||||
<form [formGroup]="additionalInfoFormGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'commentsLabel' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="comments"></textarea>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
|
||||
<!-- Paso 4: Configuración de Red -->
|
||||
<form *ngIf="generalFormGroup.value.type === 'classroom' || generalFormGroup.value.type === 'clients-group'" [formGroup]="networkSettingsFormGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'ogLiveLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="oglive" (selectionChange)="onOgLiveChange($event)">
|
||||
<mat-option *ngFor="let oglive of ogLives" [value]="oglive['@id']">
|
||||
{{ oglive.filename }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'repositoryLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="repository" (selectionChange)="onRepositoryChange($event)">
|
||||
<mat-option *ngFor="let repository of repositories" [value]="repository['@id']">
|
||||
{{ repository.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'nextServerLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="nextServer">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'bootFileNameLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="bootFileName">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'proxyUrlLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="proxy">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'dnsIpLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="dns">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'netmaskLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="netmask">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'routerLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="router">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'ntpIpLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="ntp">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'p2pModeLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="p2pMode">
|
||||
<mat-option *ngFor="let option of p2pModeOptions" [value]="option.value">
|
||||
{{ option.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'p2pTimeLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="p2pTime" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'mcastIpLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="mcastIp">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'mcastSpeedLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="mcastSpeed" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'mcastPortLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="mcastPort" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'mcastModeLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="mcastMode">
|
||||
<mat-option *ngFor="let option of multicastModeOptions" [value]="option.value">
|
||||
{{ option.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'menuUrlLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="menu" type="url">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'hardwareProfileLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="hardwareProfile">
|
||||
<mat-option *ngFor="let unit of hardwareProfiles" [value]="unit['@id']">{{ unit.description }}</mat-option>
|
||||
</mat-select>
|
||||
<mat-error>{{ 'urlFormatError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-button (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
||||
<button mat-button (click)="onSubmit()" [disabled]="!networkSettingsFormGroup.valid">{{ 'addOUSubmitButton' | translate }}</button>
|
||||
</div>
|
|
@ -1,240 +0,0 @@
|
|||
import { Component, OnInit, Output, EventEmitter, Inject } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { DataService } from '../../../services/data.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-organizational-unit',
|
||||
templateUrl: './create-organizational-unit.component.html',
|
||||
styleUrls: ['./create-organizational-unit.component.css']
|
||||
})
|
||||
export class CreateOrganizationalUnitComponent implements OnInit {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
isLinear = true;
|
||||
generalFormGroup: FormGroup;
|
||||
additionalInfoFormGroup: FormGroup;
|
||||
networkSettingsFormGroup: FormGroup;
|
||||
classroomInfoFormGroup: FormGroup;
|
||||
types: string[] = ['organizational-unit', 'classrooms-group', 'classroom', 'clients-group'];
|
||||
typeTranslations: { [key: string]: string } = {
|
||||
'organizational-unit': 'Centro',
|
||||
'classrooms-group': 'Grupo de aulas',
|
||||
'classroom': 'Aula',
|
||||
'clients-group': 'Grupo de clientes'
|
||||
};
|
||||
protected p2pModeOptions = [
|
||||
{ name: 'Leecher', value: 'leecher' },
|
||||
{ name: 'Peer', value: 'peer' },
|
||||
{ name: 'Seeder', value: 'seeder' },
|
||||
];
|
||||
protected multicastModeOptions = [
|
||||
{"name": 'Half duplex', "value": "half"},
|
||||
{"name": 'Full duplex', "value": "full"},
|
||||
];
|
||||
parentUnits: any[] = [];
|
||||
hardwareProfiles: any[] = [];
|
||||
calendars: any[] = [];
|
||||
ogLives: any[] = [];
|
||||
repositories: any[] = [];
|
||||
selectedCalendarUuid: string | null = null;
|
||||
parentUnitsWithPaths: { id: string, name: string, path: string }[] = [];
|
||||
|
||||
@Output() unitAdded = new EventEmitter<{ uuid: string; name: string }>();
|
||||
|
||||
constructor(
|
||||
private _formBuilder: FormBuilder,
|
||||
private dialogRef: MatDialogRef<CreateOrganizationalUnitComponent>,
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService,
|
||||
private dataService: DataService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any
|
||||
) {
|
||||
this.generalFormGroup = this._formBuilder.group({
|
||||
name: ['', Validators.required],
|
||||
parent: [this.data.parent ? this.data.parent['@id'] : null],
|
||||
description: [''],
|
||||
type: ['', Validators.required]
|
||||
});
|
||||
this.additionalInfoFormGroup = this._formBuilder.group({
|
||||
comments: [''],
|
||||
});
|
||||
this.networkSettingsFormGroup = this._formBuilder.group({
|
||||
ogLive: [null],
|
||||
ogRepository: [null],
|
||||
nextServer: [''],
|
||||
bootFileName: [''],
|
||||
proxy: [''],
|
||||
dns: [''],
|
||||
netmask: [''],
|
||||
router: [''],
|
||||
ntp: [''],
|
||||
p2pMode: [''],
|
||||
p2pTime: [0, Validators.min(0)],
|
||||
mcastIp: [''],
|
||||
mcastSpeed: [0, Validators.min(0)],
|
||||
mcastPort: [0, Validators.min(0)],
|
||||
mcastMode: [''],
|
||||
menu: [''],
|
||||
hardwareProfile: [''],
|
||||
validation: [false]
|
||||
});
|
||||
this.classroomInfoFormGroup = this._formBuilder.group({
|
||||
location: [''],
|
||||
projector: [false],
|
||||
board: [false],
|
||||
capacity: [0, Validators.min(0)],
|
||||
remoteCalendar: ['']
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadParentUnits();
|
||||
this.loadHardwareProfiles();
|
||||
this.loadCalendars();
|
||||
this.loadOgLives();
|
||||
this.loadRepositories();
|
||||
}
|
||||
|
||||
get filteredTypes(): string[] {
|
||||
return this.generalFormGroup.get('parent')?.value ? this.types.filter(type => type !== 'organizational-unit') : this.types;
|
||||
}
|
||||
|
||||
loadParentUnits() {
|
||||
const url = `${this.baseUrl}/organizational-units?page=1&itemsPerPage=1000`;
|
||||
this.http.get<any>(url).subscribe(
|
||||
response => {
|
||||
this.parentUnits = response['hydra:member'];
|
||||
this.parentUnitsWithPaths = this.parentUnits.map(unit => ({
|
||||
id: unit['@id'],
|
||||
name: unit.name,
|
||||
path: this.dataService.getOrganizationalUnitPath(unit, this.parentUnits)
|
||||
}));
|
||||
},
|
||||
error => console.error('Error fetching parent units:', error)
|
||||
);
|
||||
}
|
||||
|
||||
getSelectedParentName(): string | undefined {
|
||||
const parentId = this.generalFormGroup.get('parent')?.value;
|
||||
return this.parentUnitsWithPaths.find(unit => unit.id === parentId)?.name;
|
||||
}
|
||||
|
||||
loadHardwareProfiles(): void {
|
||||
this.dataService.getHardwareProfiles().subscribe(
|
||||
(data: any[]) => this.hardwareProfiles = data,
|
||||
error => console.error('Error fetching hardware profiles', error)
|
||||
);
|
||||
}
|
||||
|
||||
loadOgLives() {
|
||||
this.dataService.getOgLives().subscribe(
|
||||
(data: any[]) => {
|
||||
this.ogLives = data
|
||||
},
|
||||
error => console.error('Error fetching ogLives', error)
|
||||
);
|
||||
}
|
||||
|
||||
loadRepositories() {
|
||||
this.dataService.getRepositories().subscribe(
|
||||
(data: any[]) => this.repositories = data,
|
||||
error => console.error('Error fetching repositories', error)
|
||||
);
|
||||
}
|
||||
|
||||
loadCalendars() {
|
||||
const apiUrl = `${this.baseUrl}/remote-calendars?page=1&itemsPerPage=30`;
|
||||
this.http.get<any>(apiUrl).subscribe(
|
||||
response => this.calendars = response['hydra:member'],
|
||||
error => {
|
||||
console.error('Error loading calendars', error);
|
||||
this.openSnackBar(true, 'Error loading calendars');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private cleanFormValues(formGroup: FormGroup): any {
|
||||
const cleanedValues: any = {};
|
||||
Object.keys(formGroup.controls).forEach(key => {
|
||||
const value = formGroup.get(key)?.value;
|
||||
if (value !== '' && value !== 0) {
|
||||
cleanedValues[key] = value;
|
||||
}
|
||||
});
|
||||
return cleanedValues;
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.isFormValid()) {
|
||||
const formData: any = this.buildPayload();
|
||||
const postUrl = `${this.baseUrl}/organizational-units`;
|
||||
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
|
||||
|
||||
this.http.post<any>(postUrl, formData, { headers }).subscribe(
|
||||
response => {
|
||||
this.unitAdded.emit(response);
|
||||
this.dialogRef.close(response);
|
||||
this.toastService.success('Unidad creada exitosamente', 'Éxito');
|
||||
this.openSnackBar(false, 'Unidad creada exitosamente');
|
||||
},
|
||||
error => {
|
||||
console.error('Error al realizar POST:', error);
|
||||
this.toastService.error('Ha ocurrido un error');
|
||||
this.openSnackBar(true, 'Error al crear la unidad organizativa: ' + error.error['hydra:description']);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private isFormValid(): boolean {
|
||||
return this.generalFormGroup.valid &&
|
||||
this.additionalInfoFormGroup.valid &&
|
||||
this.networkSettingsFormGroup.valid &&
|
||||
(this.generalFormGroup.value.type !== 'classroom' || this.classroomInfoFormGroup.valid);
|
||||
}
|
||||
|
||||
private buildPayload(): any {
|
||||
const generalFormValues = this.cleanFormValues(this.generalFormGroup);
|
||||
const additionalInfoFormValues = this.cleanFormValues(this.additionalInfoFormGroup);
|
||||
const networkSettingsFormValues = this.cleanFormValues(this.networkSettingsFormGroup);
|
||||
const classroomInfoFormValues = this.cleanFormValues(this.classroomInfoFormGroup);
|
||||
|
||||
return {
|
||||
...generalFormValues,
|
||||
...classroomInfoFormValues,
|
||||
comments: additionalInfoFormValues.comments,
|
||||
networkSettings: { ...networkSettingsFormValues },
|
||||
menu: networkSettingsFormValues.menu || null,
|
||||
ogLive: networkSettingsFormValues.ogLive || null,
|
||||
ogRepository: networkSettingsFormValues.ogRepository || null,
|
||||
hardwareProfile: networkSettingsFormValues.hardwareProfile || null,
|
||||
};
|
||||
}
|
||||
|
||||
onCalendarChange(event: any) {
|
||||
this.generalFormGroup.value.remoteCalendar = event.value;
|
||||
this.selectedCalendarUuid = event.value;
|
||||
}
|
||||
|
||||
onOgLiveChange(event: any) {
|
||||
this.networkSettingsFormGroup.value.ogLive = event.value;
|
||||
}
|
||||
|
||||
onRepositoryChange(event: any) {
|
||||
this.networkSettingsFormGroup.value.ogRepository = event
|
||||
}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
openSnackBar(isError: boolean, message: string) {
|
||||
if (isError) {
|
||||
this.toastService.error('Error al crear la unidad: ' + message, 'Error');
|
||||
} else {
|
||||
this.toastService.success('Unidad creada exitosamente', 'Éxito');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
h1 {
|
||||
text-align: center;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 400;
|
||||
color: #3f51b5;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.network-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
text-transform: none;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mat-slide-toggle {
|
||||
margin-top: 20px;
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
<h1 mat-dialog-title>{{ 'editOrgUnitTitle' | translate }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<!-- Paso 1: General -->
|
||||
<form [formGroup]="generalFormGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'typeLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="type" required>
|
||||
<mat-option *ngFor="let type of filteredTypes" [value]="type">
|
||||
{{ typeTranslations[type] }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'nameLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'editOrgUnitParentLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="parent">
|
||||
<mat-select-trigger>
|
||||
{{ getSelectedParentName() }}
|
||||
</mat-select-trigger>
|
||||
<mat-option *ngFor="let unit of parentUnitsWithPaths" [value]="unit.id">
|
||||
<div>{{ unit.name }}</div>
|
||||
<div style="font-size: smaller; color: gray;">{{ unit.path }}</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'descriptionLabel' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="description"></textarea>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
|
||||
<!-- Paso 2: Información del Aula -->
|
||||
<form *ngIf="generalFormGroup.value.type === 'classroom'" [formGroup]="classroomInfoFormGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'locationLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="location">
|
||||
</mat-form-field>
|
||||
<mat-slide-toggle formControlName="projector">{{ 'projectorToggle' | translate }}</mat-slide-toggle>
|
||||
<mat-slide-toggle formControlName="board">{{ 'boardToggle' | translate }}</mat-slide-toggle>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'capacityLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="capacity" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field" appearance="fill">
|
||||
<mat-label>{{ 'associatedCalendarLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="remoteCalendar" (selectionChange)="onCalendarChange($event)">
|
||||
<mat-option *ngFor="let calendar of calendars" [value]="calendar['@id']">
|
||||
{{ calendar.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
|
||||
<!-- Paso 3: Información Adicional -->
|
||||
<form [formGroup]="additionalInfoFormGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'commentsLabel' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="comments"></textarea>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
|
||||
<!-- Paso 4: Configuración de Red -->
|
||||
<form *ngIf="generalFormGroup.value.type === 'classroom' || generalFormGroup.value.type === 'clients-group'" [formGroup]="networkSettingsFormGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'ogLiveLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="ogLive" (selectionChange)="onOgLiveChange($event)">
|
||||
<mat-option *ngFor="let oglive of ogLives" [value]="oglive['@id']">
|
||||
{{ oglive.filename }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'repositoryLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="repository" (selectionChange)="onRepositoryChange($event)">
|
||||
<mat-option *ngFor="let repository of repositories" [value]="repository['@id']">
|
||||
{{ repository.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'proxyUrlLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="proxy">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'dnsIpLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="dns">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'netmaskLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="netmask">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'routerLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="router">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'ntpIpLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="ntp">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'p2pModeLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="p2pMode">
|
||||
<mat-option *ngFor="let option of p2pModeOptions" [value]="option.value">{{ option.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'p2pTimeLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="p2pTime" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'mcastIpLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="mcastIp">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'mcastSpeedLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="mcastSpeed" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'mcastPortLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="mcastPort" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'mcastModeLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="mcastMode">
|
||||
<mat-option *ngFor="let option of multicastModeOptions" [value]="option.value">{{ option.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'menuUrlLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="menu" type="url">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>{{ 'hardwareProfileLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="hardwareProfile">
|
||||
<mat-option *ngFor="let unit of hardwareProfiles" [value]="unit['@id']">{{ unit.description }}</mat-option>
|
||||
</mat-select>
|
||||
<mat-error>{{ 'urlFormatError' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-slide-toggle formControlName="validation">{{ 'validationToggle' | translate }}</mat-slide-toggle>
|
||||
</form>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-button (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
||||
<button mat-button (click)="onSubmit()" [disabled]="!networkSettingsFormGroup.valid">{{ 'editOUSubmitButton' | translate }}</button>
|
||||
</div>
|
|
@ -0,0 +1,64 @@
|
|||
h1 {
|
||||
text-align: center;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 400;
|
||||
color: #3f51b5;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.description-form-field {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
padding: 0px 40px 15px 50px;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1em;
|
||||
padding: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
button {
|
||||
text-transform: none;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.grid-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
column-gap: 20px;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
color: #3f51b5;
|
||||
margin: 40px 0 15px 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.projector-board-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
grid-column: span 2;
|
||||
gap: 2em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.validation-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
grid-column: span 2;
|
||||
margin-bottom: 10px;
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
<h1 mat-dialog-title>{{ isEditMode ? 'Editar' : 'Crear' }} Unidad Organizativa</h1>
|
||||
<div class="mat-dialog-content">
|
||||
<!-- Paso 1: General -->
|
||||
<span class="step-title">General</span>
|
||||
<form [formGroup]="generalFormGroup" class="grid-form">
|
||||
<mat-form-field class="form-field" appearance="fill">
|
||||
<mat-label>Tipo</mat-label>
|
||||
<mat-select formControlName="type" required>
|
||||
<mat-option *ngFor="let type of filteredTypes" [value]="type">
|
||||
{{ typeTranslations[type] }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field" appearance="fill">
|
||||
<mat-label>Nombre</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field" appearance="fill">
|
||||
<mat-label>Padre</mat-label>
|
||||
<mat-select formControlName="parent">
|
||||
<mat-select-trigger>
|
||||
{{ getSelectedParentName() }}
|
||||
</mat-select-trigger>
|
||||
<mat-option *ngFor="let unit of parentUnitsWithPaths" [value]="unit.id">
|
||||
<div>{{ unit.name }}</div>
|
||||
<div style="font-size: smaller; color: gray;">{{ unit.path }}</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field description-form-field" appearance="fill">
|
||||
<mat-label>Descripción</mat-label>
|
||||
<textarea matInput formControlName="description"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-checkbox formControlName="excludeParentChanges">
|
||||
{{ 'excludeParentChanges' | translate }}
|
||||
</mat-checkbox>
|
||||
</form>
|
||||
|
||||
<!-- Paso 2: Información del Aula -->
|
||||
<span *ngIf="generalFormGroup.value.type === 'classroom'" class="step-title">Información del aula</span>
|
||||
<form *ngIf="generalFormGroup.value.type === 'classroom'" class="grid-form" [formGroup]="classroomInfoFormGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Localización</mat-label>
|
||||
<input matInput formControlName="location">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Aforo</mat-label>
|
||||
<input matInput formControlName="capacity" type="number" min="0">
|
||||
<mat-error *ngIf="classroomInfoFormGroup.get('capacity')?.hasError('min')">
|
||||
El aforo no puede ser negativo
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field" appearance="fill" style="grid-column: span 1;">
|
||||
<mat-label>Calendario Asociado</mat-label>
|
||||
<mat-select formControlName="remoteCalendar" (selectionChange)="onCalendarChange($event)">
|
||||
<mat-option *ngFor="let calendar of calendars" [value]="calendar['@id']">
|
||||
{{ calendar.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div class="projector-board-field">
|
||||
<mat-slide-toggle formControlName="projector">Proyector</mat-slide-toggle>
|
||||
<mat-slide-toggle formControlName="board">Pizarra</mat-slide-toggle>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Paso 3: Configuración de Red -->
|
||||
<span class="step-title">Configuración de Red</span>
|
||||
<form [formGroup]="networkSettingsFormGroup" class="grid-form">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>OgLive</mat-label>
|
||||
<mat-select formControlName="ogLive" (selectionChange)="onOgLiveChange($event)">
|
||||
<mat-option *ngFor="let oglive of ogLives" [value]="oglive['@id']">
|
||||
{{ oglive.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Repositorio</mat-label>
|
||||
<mat-select formControlName="repository" (selectionChange)="onRepositoryChange($event)">
|
||||
<mat-option *ngFor="let repository of repositories" [value]="repository['@id']">
|
||||
{{ repository.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Proxy</mat-label>
|
||||
<input matInput formControlName="proxy">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>DNS</mat-label>
|
||||
<input matInput formControlName="dns">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Máscara de Red</mat-label>
|
||||
<input matInput formControlName="netmask">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Router</mat-label>
|
||||
<input matInput formControlName="router">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>NTP</mat-label>
|
||||
<input matInput formControlName="ntp">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Modo P2P</mat-label>
|
||||
<mat-select formControlName="p2pMode">
|
||||
<mat-option *ngFor="let option of p2pModeOptions" [value]="option.value">
|
||||
{{ option.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Tiempo P2P</mat-label>
|
||||
<input matInput formControlName="p2pTime" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Mcast IP</mat-label>
|
||||
<input matInput formControlName="mcastIp">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Mcast Speed</mat-label>
|
||||
<input matInput formControlName="mcastSpeed" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Mcast Port</mat-label>
|
||||
<input matInput formControlName="mcastPort" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Mcast Mode</mat-label>
|
||||
<mat-select formControlName="mcastMode">
|
||||
<mat-option *ngFor="let option of multicastModeOptions" [value]="option.value">
|
||||
{{ option.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Menu</mat-label>
|
||||
<mat-select formControlName="menu">
|
||||
<mat-option *ngFor="let menu of menus" [value]="menu['@id']">
|
||||
{{ menu.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Perfil de Hardware</mat-label>
|
||||
<mat-select formControlName="hardwareProfile">
|
||||
<mat-option *ngFor="let unit of hardwareProfiles" [value]="unit['@id']">{{ unit.description
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
<mat-error>Formato de URL incorrecto</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
|
||||
<!-- Paso 4: Información Adicional -->
|
||||
<span class="step-title">Información Adicional</span>
|
||||
<form [formGroup]="additionalInfoFormGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Comentarios</mat-label>
|
||||
<textarea matInput formControlName="comments"></textarea>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
<div class="mat-dialog-actions">
|
||||
<button mat-button (click)="onNoClick()">Cancelar</button>
|
||||
<button mat-button (click)="onSubmit()"
|
||||
[disabled]="!generalFormGroup.valid || !additionalInfoFormGroup.valid || !networkSettingsFormGroup.valid">{{
|
||||
isEditMode ? 'Editar' : 'Crear' }}</button>
|
||||
</div>
|
|
@ -0,0 +1,49 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ManageOrganizationalUnitComponent } from './manage-organizational-unit.component';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
describe('ManageOrganizationalUnitComponent', () => {
|
||||
let component: ManageOrganizationalUnitComponent;
|
||||
let fixture: ComponentFixture<ManageOrganizationalUnitComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ManageOrganizationalUnitComponent],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
ReactiveFormsModule,
|
||||
ToastrModule.forRoot(),
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatSlideToggleModule,
|
||||
MatCheckboxModule,
|
||||
TranslateModule.forRoot(),
|
||||
BrowserAnimationsModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: {} }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ManageOrganizationalUnitComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,17 +1,16 @@
|
|||
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
||||
import {Component, EventEmitter, Inject, OnInit, Output} from '@angular/core';
|
||||
import {FormBuilder, FormGroup} from '@angular/forms';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
|
||||
import {CreateOrganizationalUnitComponent} from '../create-organizational-unit/create-organizational-unit.component';
|
||||
import {DataService} from "../../../services/data.service";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DataService } from "../../../services/data.service";
|
||||
import { ToastrService } from "ngx-toastr";
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-organizational-unit',
|
||||
templateUrl: './edit-organizational-unit.component.html',
|
||||
styleUrl: './edit-organizational-unit.component.css'
|
||||
selector: 'app-manage-organizational-unit',
|
||||
templateUrl: './manage-organizational-unit.component.html',
|
||||
styleUrls: ['./manage-organizational-unit.component.css']
|
||||
})
|
||||
export class EditOrganizationalUnitComponent implements OnInit {
|
||||
export class ManageOrganizationalUnitComponent implements OnInit {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
isLinear = true;
|
||||
generalFormGroup: FormGroup;
|
||||
|
@ -30,23 +29,24 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
isEditMode: boolean;
|
||||
currentCalendar: any = [];
|
||||
ogLives: any[] = [];
|
||||
menus: any[] = [];
|
||||
repositories: any[] = [];
|
||||
parentUnitsWithPaths: { id: string, name: string, path: string }[] = [];
|
||||
protected p2pModeOptions = [
|
||||
{"name": 'Leecher', "value": "leecher"},
|
||||
{"name": 'Peer', "value": "peer"},
|
||||
{"name": 'Seeder', "value": "seeder"},
|
||||
{ "name": 'Leecher', "value": "leecher" },
|
||||
{ "name": 'Peer', "value": "peer" },
|
||||
{ "name": 'Seeder', "value": "seeder" },
|
||||
];
|
||||
protected multicastModeOptions = [
|
||||
{"name": 'Half duplex', "value": "half"},
|
||||
{"name": 'Full duplex', "value": "full"},
|
||||
{ "name": 'Half duplex', "value": "half" },
|
||||
{ "name": 'Full duplex', "value": "full" },
|
||||
];
|
||||
@Output() unitAdded = new EventEmitter();
|
||||
calendars: any;
|
||||
|
||||
constructor(
|
||||
private _formBuilder: FormBuilder,
|
||||
private dialogRef: MatDialogRef<CreateOrganizationalUnitComponent>,
|
||||
private dialogRef: MatDialogRef<ManageOrganizationalUnitComponent>,
|
||||
private http: HttpClient,
|
||||
private dataService: DataService,
|
||||
private toastService: ToastrService,
|
||||
|
@ -55,10 +55,11 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
this.isEditMode = !!data?.uuid;
|
||||
|
||||
this.generalFormGroup = this._formBuilder.group({
|
||||
name: [null],
|
||||
parent: [null],
|
||||
name: [null, Validators.required],
|
||||
parent: [data?.parent ? data.parent['@id'] : null],
|
||||
description: [null],
|
||||
type: [null]
|
||||
type: [null, Validators.required],
|
||||
excludeParentChanges: [false],
|
||||
});
|
||||
|
||||
this.additionalInfoFormGroup = this._formBuilder.group({
|
||||
|
@ -80,15 +81,14 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
mcastPort: [null],
|
||||
mcastMode: [null],
|
||||
menu: [null],
|
||||
hardwareProfile: [null],
|
||||
validation: [false]
|
||||
hardwareProfile: [null]
|
||||
});
|
||||
|
||||
this.classroomInfoFormGroup = this._formBuilder.group({
|
||||
location: [null],
|
||||
projector: [false],
|
||||
board: [false],
|
||||
capacity: [null],
|
||||
capacity: [null, [Validators.required, Validators.min(0)]],
|
||||
remoteCalendar: [null]
|
||||
});
|
||||
|
||||
|
@ -103,6 +103,7 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
this.loadCalendars();
|
||||
this.loadOgLives();
|
||||
this.loadRepositories();
|
||||
this.loadMenus()
|
||||
}
|
||||
|
||||
get filteredTypes(): string[] {
|
||||
|
@ -140,9 +141,25 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
);
|
||||
}
|
||||
|
||||
loadMenus(): void {
|
||||
const url = `${this.baseUrl}/menus?page=1&itemsPerPage=10000`;
|
||||
|
||||
this.http.get<any>(url).subscribe(
|
||||
response => {
|
||||
this.menus = response['hydra:member'];
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching menus:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
loadOgLives() {
|
||||
this.dataService.getOgLives().subscribe(
|
||||
(data: any[]) => this.ogLives = data,
|
||||
(data: any[]) => {
|
||||
this.ogLives = data
|
||||
},
|
||||
error => console.error('Error fetching ogLives', error)
|
||||
);
|
||||
}
|
||||
|
@ -173,7 +190,8 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
console.error('Error loading current calendar', error);
|
||||
this.toastService.error('Error loading current calendar');
|
||||
}
|
||||
);}
|
||||
);
|
||||
}
|
||||
|
||||
onCalendarChange(event: any) {
|
||||
this.generalFormGroup.value.remoteCalendar = event.value;
|
||||
|
@ -184,7 +202,7 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
}
|
||||
|
||||
onRepositoryChange(event: any) {
|
||||
this.networkSettingsFormGroup.value.repository = event.value
|
||||
this.networkSettingsFormGroup.value.repository = event.value;
|
||||
}
|
||||
|
||||
loadData(uuid: string) {
|
||||
|
@ -196,7 +214,8 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
name: data.name,
|
||||
parent: data.parent ? data.parent['@id'] : '',
|
||||
description: data.description,
|
||||
type: data.type
|
||||
type: data.type,
|
||||
excludeParentChanges: data.excludeParentChanges
|
||||
});
|
||||
this.additionalInfoFormGroup.patchValue({
|
||||
comments: data.comments
|
||||
|
@ -216,8 +235,7 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
menu: data.networkSettings.menu ? data.networkSettings.menu['@id'] : null,
|
||||
hardwareProfile: data.networkSettings.hardwareProfile ? data.networkSettings.hardwareProfile['@id'] : null,
|
||||
ogLive: data.networkSettings.ogLive ? data.networkSettings.ogLive['@id'] : null,
|
||||
repository: data.networkSettings.repository ? data.networkSettings.repository['@id'] : null,
|
||||
validation: data.networkSettings.validation
|
||||
repository: data.networkSettings.repository ? data.networkSettings.repository['@id'] : null
|
||||
});
|
||||
this.classroomInfoFormGroup.patchValue({
|
||||
location: data.location,
|
||||
|
@ -231,10 +249,9 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
error => {
|
||||
console.error('Error fetching data for edit:', error);
|
||||
this.toastService.error('Error fetching data');
|
||||
this.onNoClick()
|
||||
this.onNoClick();
|
||||
}
|
||||
);
|
||||
console.log(this.classroomInfoFormGroup.value);
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
|
@ -242,23 +259,25 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
const parentValue = this.generalFormGroup.value.parent;
|
||||
const formData = {
|
||||
name: this.generalFormGroup.value.name,
|
||||
excludeParentChanges: this.generalFormGroup.value.excludeParentChanges,
|
||||
parent: parentValue || null,
|
||||
description: this.generalFormGroup.value.description,
|
||||
comments: this.additionalInfoFormGroup.value.comments,
|
||||
remoteCalendar: this.generalFormGroup.value.remoteCalendar,
|
||||
type: this.generalFormGroup.value.type,
|
||||
networkSettings: this.networkSettingsFormGroup.value
|
||||
networkSettings: this.networkSettingsFormGroup.value,
|
||||
location: this.classroomInfoFormGroup.value.location,
|
||||
projector: this.classroomInfoFormGroup.value.projector,
|
||||
board: this.classroomInfoFormGroup.value.board,
|
||||
capacity: this.classroomInfoFormGroup.value.capacity,
|
||||
};
|
||||
|
||||
|
||||
if (this.isEditMode) {
|
||||
const putUrl = `${this.baseUrl}/organizational-units/${this.data.uuid}`;
|
||||
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
|
||||
|
||||
console.log('PUT URLLLLL:', formData);
|
||||
this.http.put<any>(putUrl, formData, { headers }).subscribe(
|
||||
response => {
|
||||
console.log('PUT successful:', response);
|
||||
this.unitAdded.emit();
|
||||
this.dialogRef.close();
|
||||
this.toastService.success('Editado exitosamente', 'Éxito');
|
||||
|
@ -274,13 +293,13 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
|
||||
this.http.post<any>(postUrl, formData, { headers }).subscribe(
|
||||
response => {
|
||||
this.unitAdded.emit();
|
||||
this.dialogRef.close();
|
||||
this.toastService.success('Editado exitosamente', 'Éxito');
|
||||
this.unitAdded.emit(response);
|
||||
this.dialogRef.close(response);
|
||||
this.toastService.success('Creado exitosamente', 'Éxito');
|
||||
},
|
||||
error => {
|
||||
console.error('Error al realizar POST:', error);
|
||||
this.toastService.error('Error al editar:', error.error['hydra:description']);
|
||||
this.toastService.error('Error al crear:', error.error['hydra:description']);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -290,6 +309,4 @@ export class EditOrganizationalUnitComponent implements OnInit {
|
|||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -14,13 +14,23 @@ table {
|
|||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.button-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.custom-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.property-column {
|
||||
font-weight: bold;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.true-icon {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.false-icon {
|
||||
color: red;
|
||||
}
|
||||
|
|
|
@ -2,28 +2,42 @@
|
|||
<div mat-dialog-content>
|
||||
<mat-tab-group dynamicHeight>
|
||||
<mat-tab label="{{ 'generalDataTab' | translate }}">
|
||||
<table mat-table [dataSource]="generalData" class="mat-elevation-z8">
|
||||
<table mat-table [dataSource]="generalData" class="mat-elevation-z8 custom-table">
|
||||
<ng-container matColumnDef="property">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'propertyHeader' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let element"> {{ element.property }} </td>
|
||||
<td mat-cell *matCellDef="let element" class="property-column"> {{ element.property }} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="value">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'valueHeader' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let element"> {{ element.value }} </td>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="isBoolean(element.value); else normalValue">
|
||||
<mat-icon [ngClass]="{'true-icon': element.value, 'false-icon': !element.value}">
|
||||
{{ element.value ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
</ng-container>
|
||||
<ng-template #normalValue>{{ element.value }}</ng-template>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</mat-tab>
|
||||
<mat-tab [disabled]="data.data.type !== 'classroom'" label="{{ 'classroomNetworkPropertiesTab' | translate }}">
|
||||
<table mat-table [dataSource]="networkData" class="mat-elevation-z8">
|
||||
<mat-tab label="{{ 'classroomNetworkPropertiesTab' | translate }}">
|
||||
<table mat-table [dataSource]="networkData" class="mat-elevation-z8 custom-table">
|
||||
<ng-container matColumnDef="property">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'propertyHeader' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let element"> {{ element.property }} </td>
|
||||
<td mat-cell *matCellDef="let element" class="property-column"> {{ element.property }} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="value">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'valueHeader' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let element"> {{ element.value }} </td>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="isBoolean(element.value); else normalValue">
|
||||
<mat-icon [ngClass]="{'true-icon': element.value, 'false-icon': !element.value}">
|
||||
{{ element.value ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
</ng-container>
|
||||
<ng-template #normalValue>{{ element.value }}</ng-template>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
|
@ -31,3 +45,6 @@
|
|||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-button (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
||||
</div>
|
||||
|
|
|
@ -1,22 +1,31 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import {Component, Inject} from '@angular/core';
|
||||
import {MAT_DIALOG_DATA} from "@angular/material/dialog";
|
||||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {DatePipe} from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: 'app-show-organizational-unit',
|
||||
templateUrl: './show-organizational-unit.component.html',
|
||||
styleUrl: './show-organizational-unit.component.css'
|
||||
})
|
||||
export class ShowOrganizationalUnitComponent {
|
||||
export class ShowOrganizationalUnitComponent implements OnInit {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
displayedColumns: string[] = ['property', 'value'];
|
||||
currentCalendar: any;
|
||||
ou: any;
|
||||
generalData: any[] = [];
|
||||
networkData: any[] = [];
|
||||
datePipe: DatePipe = new DatePipe('es-ES');
|
||||
typeTranslations: { [key: string]: string } = {
|
||||
'organizational-unit': 'Centro',
|
||||
'classrooms-group': 'Grupo de aulas',
|
||||
'classroom': 'Aula',
|
||||
'clients-group': 'Grupo de clientes'
|
||||
};
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
private dialogRef: MatDialogRef<ShowOrganizationalUnitComponent>,
|
||||
private http: HttpClient
|
||||
) {
|
||||
}
|
||||
|
@ -25,8 +34,11 @@ export class ShowOrganizationalUnitComponent {
|
|||
this.loadOrganizationalUnit(this.data.data['@id']);
|
||||
}
|
||||
|
||||
isBoolean(value: any): boolean {
|
||||
return typeof value === 'boolean';
|
||||
}
|
||||
|
||||
loadOrganizationalUnit(uuid: string): void {
|
||||
console.log(this.data['@id'])
|
||||
const apiUrl = `${this.baseUrl}${uuid}`;
|
||||
this.http.get<any>(apiUrl).subscribe(
|
||||
response => {
|
||||
|
@ -60,24 +72,38 @@ export class ShowOrganizationalUnitComponent {
|
|||
{ property: 'Uuid', value: this.ou.uuid },
|
||||
{ property: 'Descripción', value: this.ou.description },
|
||||
{ property: 'Comentarios', value: this.ou.comments },
|
||||
{ property: 'Tipo', value: this.ou.type },
|
||||
{ property: 'Tipo', value: this.typeTranslations[this.ou.type] },
|
||||
{ property: 'Unidad organizativa superior', value: this.ou.parent ? this.ou.parent.name : '-' },
|
||||
{ property: 'Creado el', value: this.ou.createdAt },
|
||||
];
|
||||
|
||||
this.networkData = [
|
||||
{ property: 'Creado el', value: this.datePipe.transform(this.ou.createdAt, 'dd/MM/yyyy hh:mm:ss') },
|
||||
{ property: 'Calendario asociado', value: this.ou.remoteCalendar ? this.currentCalendar : '-' },
|
||||
{ property: 'Aforo', value: this.ou.capacity },
|
||||
{ property: 'Localización', value: this.ou.location },
|
||||
{ property: 'Calendario', value: this.ou.calendar ? this.ou.calendar.name : '-' },
|
||||
{ property: 'Proyector', value: this.ou.projector },
|
||||
{ property: 'Pizarra', value: this.ou.board },
|
||||
];
|
||||
|
||||
this.networkData = [
|
||||
{ property: 'Proxy', value: this.ou.networkSettings.proxy },
|
||||
{ property: 'DNS', value: this.ou.networkSettings.dns },
|
||||
{ property: 'Router', value: this.ou.networkSettings.router },
|
||||
{ property: 'NTP', value: this.ou.networkSettings.ntp },
|
||||
{ property: 'Modo P2P', value: this.ou.networkSettings.p2pMode },
|
||||
{ property: 'Tiempo P2P', value: this.ou.networkSettings.p2pTime },
|
||||
{ property: 'Mcast IP', value: this.ou.networkSettings.mcastIp },
|
||||
{ property: 'Mcast Speed', value: this.ou.networkSettings.mcastSpeed },
|
||||
{ property: 'Mcast Port', value: this.ou.networkSettings.mcastPort },
|
||||
{ property: 'Mcast Mode', value: this.ou.networkSettings.mcastMode },
|
||||
{ property: 'Menú', value: this.ou.networkSettings.menu ? this.ou.networkSettings.menu.name : '-' },
|
||||
{ property: 'OgLive', value: this.ou.networkSettings.ogLive ? this.ou.networkSettings.ogLive.name : '-' },
|
||||
{ property: 'Repositorio', value: this.ou.networkSettings.repository ? this.ou.networkSettings.repository.name : '-' },
|
||||
{ property: 'OgLog', value: this.ou.networkSettings.oglog },
|
||||
{ property: 'OgShare', value: this.ou.networkSettings.ogshare },
|
||||
{ property: 'Perfil de hardware', value: this.ou.networkSettings.hardwareProfile ? this.ou.networkSettings.hardwareProfile.name : '-' },
|
||||
{ property: 'Máscara de red', value: this.ou.networkSettings.netmask },
|
||||
]
|
||||
}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,22 @@ button {
|
|||
margin-left: 8px; /* Espacio entre los botones */
|
||||
}
|
||||
|
||||
.warning-card {
|
||||
background-color: #ffebee; /* Rojo claro */
|
||||
color: #d32f2f; /* Rojo oscuro */
|
||||
padding: 16px;
|
||||
border-left: 5px solid #d32f2f;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.warning-card mat-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
|
||||
/* Responsividad para pantallas pequeñas */
|
||||
@media (max-width: 600px) {
|
||||
.form-field {
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
<mat-dialog-content class="dialog-content">
|
||||
<form [formGroup]="imageForm" (ngSubmit)="saveImage()" class="image-form">
|
||||
<mat-card *ngIf="showWarning" class="warning-card">
|
||||
<mat-card-content>
|
||||
<mat-icon color="warn">warning</mat-icon>
|
||||
Ha marcado la casilla <strong>"Imagen Global"</strong>. Se transferirá la imagen al resto de repositorios en el caso de que no exista previamente.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>{{ 'imageNameLabel' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
|
@ -9,8 +15,8 @@
|
|||
|
||||
<mat-form-field appearance="fill" class="form-field">
|
||||
<mat-label>{{ 'repositoryLabel' | translate }}</mat-label>
|
||||
<mat-select formControlName="imageRepository" required>
|
||||
<mat-option *ngFor="let imageRepository of repositories" [value]="imageRepository['@id']">
|
||||
<mat-select formControlName="imageRepositories" required multiple>
|
||||
<mat-option *ngFor="let imageRepository of repositories" [value]="imageRepository['@id']" [disabled]="imageId !== null">
|
||||
{{ imageRepository.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
|
@ -37,14 +43,13 @@
|
|||
|
||||
<mat-checkbox
|
||||
formControlName="remotePc"
|
||||
class="example-margin"
|
||||
>
|
||||
{{ 'remotePcLabel' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
<mat-checkbox
|
||||
formControlName="isGlobal"
|
||||
class="example-margin"
|
||||
(click)="changeIsGlobal()"
|
||||
>
|
||||
{{ 'globalImageLabel' | translate }}
|
||||
</mat-checkbox>
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {ToastrService} from 'ngx-toastr';
|
||||
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
|
||||
import {DataService} from "../data.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-image',
|
||||
templateUrl: './create-image.component.html',
|
||||
|
@ -16,6 +17,7 @@ export class CreateImageComponent implements OnInit {
|
|||
softwareProfiles: any[] = [];
|
||||
repositories: any[] = [];
|
||||
partitionInfo: { [key: string]: string } = {};
|
||||
showWarning: boolean = false; // Nueva variable para mostrar la advertencia
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
|
@ -32,7 +34,7 @@ export class CreateImageComponent implements OnInit {
|
|||
remotePc: [false],
|
||||
isGlobal: [false],
|
||||
softwareProfile: [''],
|
||||
imageRepository: ['', Validators.required],
|
||||
imageRepositories: [[], Validators.required],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -42,6 +44,7 @@ export class CreateImageComponent implements OnInit {
|
|||
}
|
||||
this.fetchSoftwareProfiles();
|
||||
this.fetchRepositories();
|
||||
|
||||
}
|
||||
|
||||
load(): void {
|
||||
|
@ -54,7 +57,7 @@ export class CreateImageComponent implements OnInit {
|
|||
remotePc: [response.remotePc],
|
||||
isGlobal: [response.isGlobal],
|
||||
softwareProfile: [response.softwareProfile ? response.softwareProfile['@id'] : null, Validators.required],
|
||||
imageRepository: [response.imageRepository ? response.imageRepository['@id'] : null, Validators.required],
|
||||
imageRepositories: [response.imageRepositories ? response.imageRepositories.map((r: any) => r.imageRepository['@id']) : [], Validators.required],
|
||||
});
|
||||
this.imageId = response['@id'];
|
||||
this.partitionInfo = response.partitionInfo;
|
||||
|
@ -65,6 +68,10 @@ export class CreateImageComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
changeIsGlobal() {
|
||||
this.showWarning = this.imageForm.get('isGlobal')?.value;
|
||||
}
|
||||
|
||||
fetchSoftwareProfiles() {
|
||||
const url = `${this.baseUrl}/software-profiles`;
|
||||
this.http.get(url).subscribe({
|
||||
|
@ -98,7 +105,7 @@ export class CreateImageComponent implements OnInit {
|
|||
comments: this.imageForm.value.comments,
|
||||
remotePc: this.imageForm.value.remotePc,
|
||||
isGlobal: this.imageForm.value.isGlobal,
|
||||
imageRepository: this.imageForm.value.imageRepository,
|
||||
imageRepositories: this.imageForm.value.imageRepositories,
|
||||
...(this.imageForm.value.softwareProfile ? { softwareProfile: this.imageForm.value.softwareProfile } : {}),
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<h2 mat-dialog-title>Exportar imagen {{data.image?.name}}</h2>
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<h2 mat-dialog-title>Transferir imagen {{data.image?.name}}</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<mat-form-field appearance="fill" class="full-width">
|
||||
<mat-label>Seleccione repositorio destino</mat-label>
|
||||
<mat-select [(value)]="selectedRepository">
|
||||
<mat-label>Seleccione nuevo repositorio destino</mat-label>
|
||||
<mat-select [(value)]="selectedRepositories" multiple>
|
||||
<mat-option *ngFor="let repository of repositories" [value]="repository['@id']">{{ repository.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
|
|
@ -12,6 +12,8 @@ import {MatButtonModule} from "@angular/material/button";
|
|||
import {MatSelectModule} from "@angular/material/select";
|
||||
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
|
||||
import {TranslateModule} from "@ngx-translate/core";
|
||||
import {LoadingComponent} from "../../../shared/loading/loading.component";
|
||||
import {MatProgressSpinner} from "@angular/material/progress-spinner";
|
||||
|
||||
describe('ExportImageComponent', () => {
|
||||
let component: ExportImageComponent;
|
||||
|
@ -19,7 +21,7 @@ describe('ExportImageComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ExportImageComponent],
|
||||
declarations: [ExportImageComponent, LoadingComponent],
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatDialogModule,
|
||||
|
@ -27,6 +29,7 @@ describe('ExportImageComponent', () => {
|
|||
MatInputModule,
|
||||
MatButtonModule,
|
||||
MatSelectModule,
|
||||
MatProgressSpinner,
|
||||
BrowserAnimationsModule,
|
||||
ToastrModule.forRoot(),
|
||||
TranslateModule.forRoot()
|
||||
|
|
|
@ -13,14 +13,14 @@ export class ExportImageComponent implements OnInit {
|
|||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
loading: boolean = true;
|
||||
repositories: any[] = [];
|
||||
selectedRepository: string = '';
|
||||
selectedRepositories: any[] = [];
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
public dialogRef: MatDialogRef<ExportImageComponent>,
|
||||
private toastService: ToastrService,
|
||||
private router: Router,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { image: any }
|
||||
@Inject(MAT_DIALOG_DATA) public data: { image: any, imageImageRepository: any }
|
||||
) {
|
||||
|
||||
}
|
||||
|
@ -31,27 +31,41 @@ export class ExportImageComponent implements OnInit {
|
|||
}
|
||||
|
||||
loadRepositories() {
|
||||
this.http.get<any>(`${this.baseUrl}/image-repositories?id[ne]=1&page=1&itemsPerPage=50`).subscribe(
|
||||
response => {
|
||||
this.repositories = response['hydra:member'];
|
||||
this.loading = false;
|
||||
},
|
||||
error => console.error('Error fetching organizational units:', error)
|
||||
);
|
||||
const excludedIds = this.data?.image?.imageRepositories?.map((repository: any) => repository.imageRepository?.id) || null;
|
||||
|
||||
let url = `${this.baseUrl}/image-repositories?page=1&itemsPerPage=50`;
|
||||
|
||||
if (excludedIds) {
|
||||
url += `&id[neq]=${excludedIds.join(',')}`;
|
||||
}
|
||||
|
||||
this.http.get<any>(url)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.repositories = response['hydra:member'];
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching repositories:', error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
save() {
|
||||
this.http.post<any>(`${this.baseUrl}${this.selectedRepository}/export-image`, {
|
||||
images: [this.data.image['@id']]
|
||||
this.loading = true;
|
||||
this.http.post<any>(`${this.baseUrl}${this.data.imageImageRepository['@id']}/transfer-image`, {
|
||||
repositories: this.selectedRepositories
|
||||
}).subscribe({
|
||||
next: (response) => {
|
||||
this.toastService.success('Imagen exportada correctamente');
|
||||
this.toastService.success('Petición de exportación de imagen realizada correctamente');
|
||||
this.dialogRef.close();
|
||||
this.loading = false;
|
||||
this.router.navigate(['/commands-logs']);
|
||||
},
|
||||
error: error => {
|
||||
console.error('Error al exportar imagen:', error);
|
||||
this.toastService.error('Error al exportar imagen');
|
||||
this.loading = false;
|
||||
this.toastService.error('Error en la petición de exportación de imagen');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.images-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
|
@ -22,7 +26,7 @@ table {
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -36,13 +40,6 @@ table {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
@ -95,10 +92,3 @@ table {
|
|||
background-color: #f5a623 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
|
@ -15,18 +16,19 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<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-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)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()"
|
||||
i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="imagesTable" text="Esta tabla muestra las imágenes disponibles.">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="imagesTable"
|
||||
text="Esta tabla muestra las imágenes 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">
|
||||
|
@ -35,23 +37,20 @@
|
|||
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'imageRepositories'">
|
||||
<button mat-button [matMenuTriggerFor]="menu">Ver repositorios</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item *ngFor="let repository of image.imageRepositories">
|
||||
{{ repository.imageRepository.name }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="column.columnDef === 'isGlobal'">
|
||||
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
|
||||
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
</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>
|
||||
<ng-container *ngIf="column.columnDef !== 'remotePc' && column.columnDef !== 'status' && column.columnDef !== 'isGlobal'">
|
||||
<ng-container *ngIf="column.columnDef !== 'remotePc' && column.columnDef !== 'status' && column.columnDef !== 'isGlobal' && column.columnDef !== 'imageRepositories'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
|
@ -60,33 +59,15 @@
|
|||
<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="info" (click)="showImageInfo($event, image)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="editImage($event, image)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="deleteImage($event, image)">
|
||||
<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, 'get-aux')">Obtener ficheros auxiliares</button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'" (click)="toggleAction(image, 'delete-trash')">Eliminar imagen temporalmente</button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'" (click)="toggleAction(image, 'delete-permanent')">Eliminar imagen</button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'trash'" (click)="toggleAction(image, 'recover')">Recuperar imagen de la papelera</button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'" (click)="toggleAction(image, 'export')">Exportar imagen</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="[5, 10, 20, 40, 100]"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="[5, 10, 20, 40, 100]"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
|
|
@ -38,9 +38,9 @@ export class ImagesComponent implements OnInit {
|
|||
cell: (image: any) => `${image.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'imageRepository',
|
||||
header: 'Repositorio',
|
||||
cell: (image: any) => `${image.imageRepository?.name}`
|
||||
columnDef: 'imageRepositories',
|
||||
header: 'Repositorios',
|
||||
cell: (image: any) => `${image.imageRepositories}`
|
||||
},
|
||||
{
|
||||
columnDef: 'remotePc',
|
||||
|
@ -52,16 +52,6 @@ export class ImagesComponent implements OnInit {
|
|||
header: 'Imagen Global',
|
||||
cell: (image: any) => `${image.isGlobal}`
|
||||
},
|
||||
{
|
||||
columnDef: 'status',
|
||||
header: 'Estado',
|
||||
cell: (image: any) => `${image.status}`
|
||||
},
|
||||
{
|
||||
columnDef: 'imageFullsum',
|
||||
header: 'Fullsum',
|
||||
cell: (image: any) => `${image.imageFullsum}`
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
header: 'Fecha de creación',
|
||||
|
@ -101,25 +91,6 @@ export class ImagesComponent implements OnInit {
|
|||
)
|
||||
}
|
||||
|
||||
getStatusLabel(status: string): string {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 'Pendiente';
|
||||
case 'in-progress':
|
||||
return 'En progreso';
|
||||
case 'aux-file-pending':
|
||||
return 'Archivos auxiliares pendientes';
|
||||
case 'success':
|
||||
return 'Creado con éxito';
|
||||
case 'trash':
|
||||
return 'Papelera temporal';
|
||||
case 'failed':
|
||||
return 'Fallido';
|
||||
default:
|
||||
return 'Estado desconocido';
|
||||
}
|
||||
}
|
||||
|
||||
addImage(): void {
|
||||
const dialogRef = this.dialog.open(CreateImageComponent, {
|
||||
width: '800px'
|
||||
|
@ -132,7 +103,7 @@ export class ImagesComponent implements OnInit {
|
|||
|
||||
search(): void {
|
||||
this.loading = true;
|
||||
this.http.get<any>(`${this.apiUrl}?page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}&repository.id=${this.repositoryId}`, { params: this.filters }).subscribe(
|
||||
this.http.get<any>(`${this.apiUrl}?page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}&repositoryId=${this.repositoryId}`, { params: this.filters }).subscribe(
|
||||
data => {
|
||||
this.dataSource.data = data['hydra:member'];
|
||||
this.length = data['hydra:totalItems'];
|
||||
|
@ -153,26 +124,6 @@ export class ImagesComponent implements OnInit {
|
|||
}).afterClosed().subscribe(() => this.search());
|
||||
}
|
||||
|
||||
deleteImage(event: MouseEvent,command: any): void {
|
||||
event.stopPropagation();
|
||||
this.dialog.open(DeleteModalComponent, {
|
||||
width: '300px',
|
||||
data: { name: command.name },
|
||||
}).afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
this.http.delete(`${this.apiUrl}/${command.uuid}`).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Imagen eliminada con éxito');
|
||||
this.search();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error al eliminar la imagen:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onPageChange(event: any): void {
|
||||
this.page = event.pageIndex;
|
||||
this.itemsPerPage = event.pageSize;
|
||||
|
@ -180,88 +131,6 @@ export class ImagesComponent implements OnInit {
|
|||
this.search();
|
||||
}
|
||||
|
||||
loadImageAlert(image: any): Observable<any> {
|
||||
return this.http.get<any>(`${this.apiUrl}/server/${image.uuid}/get`, {});
|
||||
}
|
||||
|
||||
showImageInfo(event: MouseEvent, image:any) {
|
||||
event.stopPropagation();
|
||||
this.loadImageAlert(image).subscribe(
|
||||
response => {
|
||||
this.alertMessage = response.output;
|
||||
|
||||
this.dialog.open(ServerInfoDialogComponent, {
|
||||
width: '600px',
|
||||
data: {
|
||||
message: this.alertMessage
|
||||
}
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
toggleAction(image: any, action:string): void {
|
||||
switch (action) {
|
||||
case 'get-aux':
|
||||
this.http.post(`${this.baseUrl}/images/server/${image.uuid}/create-aux-files`, {}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Petición de creación de archivos auxiliares enviada');
|
||||
this.search()
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'delete-trash':
|
||||
this.http.post(`${this.baseUrl}/images/server/${image.uuid}/delete-trash`, {}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Petición de eliminación de la papelera temporal enviada');
|
||||
this.search()
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'delete-permanent':
|
||||
this.http.post(`${this.baseUrl}/images/server/${image.uuid}/delete-permanent`, {}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Petición de eliminación de la papelera temporal enviada');
|
||||
this.search()
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'recover':
|
||||
this.http.post(`${this.baseUrl}/images/server/${image.uuid}/recover`, {}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Petición de recuperación de la imagen enviada');
|
||||
this.search()
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'export':
|
||||
this.dialog.open(ExportImageComponent, {
|
||||
width: '600px',
|
||||
data: {
|
||||
image: image
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.error('Acción no soportada:', action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.images-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.lists-container {
|
||||
|
@ -37,21 +42,20 @@
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.image-name{
|
||||
.image-name {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -65,19 +69,12 @@ table {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
|
@ -2,17 +2,21 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" joyrideStep="titleStep" text="Desde esta pantalla podrás ver y administrar los menus exitentes.">Administrar menús</h2>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="titleStep" text="Desde esta pantalla podrás ver y administrar los menus exitentes.">
|
||||
Administrar menús</h2>
|
||||
</div>
|
||||
<div class="images-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addMenu()" joyrideStep="addStep" text="Utiliza este botón para añadir un nuevo menu.">Añadir menú</button>
|
||||
<button mat-flat-button color="primary" (click)="addMenu()" joyrideStep="addStep"
|
||||
text="Utiliza este botón para añadir un nuevo menu.">Añadir menú</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>Buscar nombre de menú</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()"
|
||||
i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
|
@ -21,7 +25,7 @@
|
|||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let menu" >
|
||||
<td mat-cell *matCellDef="let menu">
|
||||
<ng-container *ngIf="column.columnDef !== 'isDefault'">
|
||||
{{ column.cell(menu) }}
|
||||
</ng-container>
|
||||
|
@ -36,7 +40,8 @@
|
|||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let menu" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="editMenu($event, menu)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="editMenu($event, menu)" i18n="@@editImage">
|
||||
<mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="deleteMenu($event, menu)">
|
||||
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
|
||||
</button>
|
||||
|
@ -47,10 +52,7 @@
|
|||
</table>
|
||||
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="[5, 10, 20, 40, 100]"
|
||||
(page)="onPageChange($event)">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="[5, 10, 20, 40, 100]"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
|
@ -2,95 +2,105 @@
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 10px;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.disk-usage-info{
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-left: 15px;
|
||||
}.dashboard {
|
||||
padding: 20px;
|
||||
}
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.disk-usage-container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.disk-usage-info {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.disk-usage {
|
||||
flex: 2;
|
||||
margin-right: 20px;
|
||||
}
|
||||
p {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.services-status {
|
||||
flex: 1;
|
||||
}
|
||||
.dashboard {
|
||||
padding: 0.5rem 0.5rem 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.services-status ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
.disk-usage-container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.services-status li {
|
||||
margin: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.disk-usage {
|
||||
flex: 2;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.status-led {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.services-status {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.status-led.active {
|
||||
background-color: green;
|
||||
}
|
||||
.services-status ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.status-led.inactive {
|
||||
background-color: red;
|
||||
}
|
||||
.services-status li {
|
||||
margin: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.installed-oglives {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.status-led {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.status-led.active {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
.status-led.inactive {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.installed-oglives {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px; /* Espacio entre botones */
|
||||
margin-top: 50px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
/* Espacio entre botones */
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.btn:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.btn:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.btn:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
.btn:last-child {
|
||||
margin-right: 0;
|
||||
}
|
|
@ -1,25 +1,23 @@
|
|||
<div class="dashboard">
|
||||
<div class="header-container">
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="titleStep" text="Esta sección muestra el estado general del servidor OgBoot.">
|
||||
{{ 'ogBootServerStatus' | translate }}
|
||||
</h2>
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard">
|
||||
<div class="disk-usage-container">
|
||||
<!-- Disk Usage Section -->
|
||||
<div class="disk-usage" joyrideStep="diskUsageStep" text="{{ 'diskUsageDescription' | translate }}">
|
||||
<h3>{{ 'diskUsageTitle' | translate }}</h3>
|
||||
<ngx-charts-pie-chart
|
||||
[view]="view"
|
||||
[scheme]="colorScheme"
|
||||
[results]="diskUsageChartData"
|
||||
[gradient]="gradient"
|
||||
[doughnut]="isDoughnut"
|
||||
[labels]="showLabels"
|
||||
[legend]="showLegend">
|
||||
<ngx-charts-pie-chart [view]="view" [scheme]="colorScheme" [results]="diskUsageChartData" [gradient]="gradient"
|
||||
[doughnut]="isDoughnut" [labels]="showLabels" [legend]="showLegend">
|
||||
</ngx-charts-pie-chart>
|
||||
<div class="disk-usage-info">
|
||||
<p>{{ 'totalLabel' | translate }}: {{ formatBytes(diskUsage.total) }}</p>
|
||||
|
@ -34,10 +32,8 @@
|
|||
<h3>{{ 'servicesTitle' | translate }}</h3>
|
||||
<ul>
|
||||
<li *ngFor="let service of getServices()">
|
||||
<span
|
||||
class="status-led"
|
||||
[ngClass]="{ 'active': service.status === 'active', 'inactive': service.status !== 'active' }"
|
||||
></span>
|
||||
<span class="status-led"
|
||||
[ngClass]="{ 'active': service.status === 'active', 'inactive': service.status !== 'active' }"></span>
|
||||
{{ service.name }}: {{ service.status | translate }}
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -66,4 +62,4 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -18,13 +18,14 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import {LoadingComponent} from "../../../shared/loading/loading.component";
|
||||
describe('OgbootStatusComponent', () => {
|
||||
let component: OgbootStatusComponent;
|
||||
let fixture: ComponentFixture<OgbootStatusComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [OgbootStatusComponent],
|
||||
declarations: [OgbootStatusComponent, LoadingComponent],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
ToastrModule.forRoot(),
|
||||
|
@ -39,7 +40,7 @@ describe('OgbootStatusComponent', () => {
|
|||
MatTooltipModule,
|
||||
FormsModule,
|
||||
MatProgressSpinner,
|
||||
MatProgressSpinnerModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatDialogModule,
|
||||
ReactiveFormsModule,
|
||||
MatSelectModule,
|
||||
|
@ -48,8 +49,8 @@ describe('OgbootStatusComponent', () => {
|
|||
JoyrideModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: {} }
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: {} }
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { JoyrideService } from 'ngx-joyride';
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
||||
@Component({
|
||||
selector: 'app-ogboot-status',
|
||||
|
@ -13,6 +14,7 @@ export class OgbootStatusComponent implements OnInit {
|
|||
servicesStatus: any = {};
|
||||
installedOglives: any[] = [];
|
||||
diskUsageChartData: any[] = [];
|
||||
loading: boolean = false;
|
||||
|
||||
view: [number, number] = [1100, 500];
|
||||
|
||||
|
@ -24,13 +26,18 @@ export class OgbootStatusComponent implements OnInit {
|
|||
domain: ['#FF6384', '#3f51b5']
|
||||
};
|
||||
|
||||
constructor(private http: HttpClient, private joyrideService: JoyrideService) {}
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private joyrideService: JoyrideService,
|
||||
private toastService: ToastrService,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadStatus();
|
||||
}
|
||||
|
||||
loadStatus(): void {
|
||||
this.loading = true;
|
||||
this.http.get<any>(`${this.baseUrl}/og-boot/status`).subscribe(data => {
|
||||
this.diskUsage = data.message.disk_usage;
|
||||
this.servicesStatus = data.message.services_status;
|
||||
|
@ -46,8 +53,11 @@ export class OgbootStatusComponent implements OnInit {
|
|||
value: parseFloat(this.diskUsage.available)
|
||||
}
|
||||
];
|
||||
this.loading = false;
|
||||
|
||||
}, error => {
|
||||
console.error('Error fetching status', error);
|
||||
this.toastService.error('Error al sincronizar con el el servicio de og-boot');
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -82,5 +92,5 @@ export class OgbootStatusComponent implements OnInit {
|
|||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,31 +1,33 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 0rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.global-selectors {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 0 5px;
|
||||
justify-content: start;
|
||||
padding-left: 5px;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.selected-global {
|
||||
|
@ -42,25 +44,12 @@ table {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.save-button{
|
||||
.save-button {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.example-headers-align .mat-expansion-panel-header-description {
|
||||
|
@ -68,7 +57,7 @@ table {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field {
|
||||
.example-headers-align .mat-mdc-form-field+.mat-mdc-form-field {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
|
@ -79,5 +68,4 @@ table {
|
|||
|
||||
.example-button-row .mat-mdc-button-base {
|
||||
margin: 8px 8px 8px 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,27 +1,28 @@
|
|||
<div class="header-container">
|
||||
<h2 class="title" joyrideStep="titleStep" text="{{ 'advancedNetbootDescription' | translate }}">
|
||||
{{ 'advancedNetbootTitle' | translate }}
|
||||
</h2>
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div [formGroup]="taskForm" class="search-container">
|
||||
<div [formGroup]="taskForm" class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="selectClassStep" text="{{ 'selectClassDescription' | translate }}">
|
||||
<mat-label>{{ 'selectClassLabel' | translate }}</mat-label>
|
||||
<mat-select (selectionChange)="loadChildUnits($event)">
|
||||
<mat-option *ngFor="let unit of units" [value]="unit">{{ unit.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="titleStep" text="{{ 'advancedNetbootDescription' | translate }}">
|
||||
{{ 'advancedNetbootTitle' | translate }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
<div [formGroup]="taskForm" class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="selectClassStep"
|
||||
text="{{ 'selectClassDescription' | translate }}">
|
||||
<mat-label>{{ 'selectClassLabel' | translate }}</mat-label>
|
||||
<mat-select (selectionChange)="loadChildUnits($event)">
|
||||
<mat-option *ngFor="let unit of units" [value]="unit">{{ unit.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="global-selectors">
|
||||
<mat-form-field appearance="fill" class="selected-global" joyrideStep="applyToAllStep" text="{{ 'applyToAllDescription' | translate }}">
|
||||
<mat-form-field appearance="fill" class="selected-global" joyrideStep="applyToAllStep"
|
||||
text="{{ 'applyToAllDescription' | translate }}">
|
||||
<mat-label>{{ 'applyToAllLabel' | translate }}</mat-label>
|
||||
<mat-select [(value)]="globalOgLive" (selectionChange)="applyToAll()">
|
||||
<mat-option *ngFor="let option of ogLiveOptions" [value]="option['@id']">
|
||||
|
@ -30,18 +31,14 @@
|
|||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
[disabled]="units.length === 0"
|
||||
(click)="saveOgLiveTemplates()"
|
||||
joyrideStep="saveButtonStep"
|
||||
text="{{ 'saveButtonDescription' | translate }}">
|
||||
<button mat-flat-button color="primary" [disabled]="units.length === 0" (click)="saveOgLiveTemplates()"
|
||||
joyrideStep="saveButtonStep" text="{{ 'saveButtonDescription' | translate }}">
|
||||
{{ 'saveButtonLabel' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableDescription' | translate }}">
|
||||
<mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
text="{{ 'tableDescription' | translate }}">
|
||||
<ng-container matColumnDef="id">
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'idColumnHeader' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let element"> {{ element.id }} </mat-cell>
|
||||
|
@ -65,7 +62,8 @@
|
|||
<ng-container matColumnDef="ogLive">
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'templateColumnHeader' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let client">
|
||||
<mat-form-field appearance="fill" joyrideStep="selectTemplateStep" text="{{ 'selectTemplateDescription' | translate }}">
|
||||
<mat-form-field appearance="fill" joyrideStep="selectTemplateStep"
|
||||
text="{{ 'selectTemplateDescription' | translate }}">
|
||||
<mat-label>{{ 'selectTemplateLabel' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="client.ogLive" [name]="'ogLive' + client.id">
|
||||
<mat-option [value]="null">{{ 'noTemplateOption' | translate }}</mat-option>
|
||||
|
@ -79,4 +77,4 @@
|
|||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||
</mat-table>
|
||||
</mat-table>
|
|
@ -1,6 +1,6 @@
|
|||
<h2 mat-dialog-title>{{ isEditMode ? 'Editar' : 'Añadir' }} imagen ogLive</h2>
|
||||
<mat-dialog-content>
|
||||
<mat-spinner class="spinner" *ngIf="loading"></mat-spinner>
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<mat-form-field *ngIf="!loading" appearance="fill" class="full-width">
|
||||
<mat-label>Seleccionar ISO</mat-label>
|
||||
|
|
|
@ -1,28 +1,32 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.images-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -36,33 +40,12 @@ table {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.images-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
mat-spinner {
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,39 +2,46 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" joyrideStep="titleStep" [text]="'adminImagesDescription' | translate">
|
||||
{{ 'adminImagesTitle' | translate }}
|
||||
</h2>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="titleStep" [text]="'adminImagesDescription' | translate">
|
||||
{{ 'adminImagesTitle' | translate }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="images-button-row">
|
||||
<button mat-flat-button color="accent" (click)="openSubnetInfoDialog()">{{ 'viewInfoButton' | translate }}</button>
|
||||
<button mat-flat-button color="primary" (click)="addImage()" joyrideStep="addImageStep" [text]="'addImageButtonDescription' | translate">
|
||||
<button mat-flat-button color="primary" (click)="addImage()" joyrideStep="addImageStep"
|
||||
[text]="'addImageButtonDescription' | translate">
|
||||
{{ 'addImageButton' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchNameStep" [text]="'searchNameDescription' | translate">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchNameStep"
|
||||
[text]="'searchNameDescription' | translate">
|
||||
<mat-label>{{ 'searchLabel' | translate }}</mat-label>
|
||||
<input matInput [placeholder]="'searchPlaceholder' | translate" [(ngModel)]="filters['name']" (keyup.enter)="search()">
|
||||
<input matInput [placeholder]="'searchPlaceholder' | translate" [(ngModel)]="filters['name']"
|
||||
(keyup.enter)="search()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="searchDefaultImageStep" [text]="'searchDefaultDescription' | translate">
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="searchDefaultImageStep"
|
||||
[text]="'searchDefaultDescription' | translate">
|
||||
<mat-label>{{ 'searchDefaultLabel' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="filters['isDefault']" (selectionChange)="search()" [placeholder]="'selectOptionPlaceholder' | translate">
|
||||
<mat-select [(ngModel)]="filters['isDefault']" (selectionChange)="search()"
|
||||
[placeholder]="'selectOptionPlaceholder' | translate">
|
||||
<mat-option [value]="''">{{ 'allOption' | translate }}</mat-option>
|
||||
<mat-option [value]="true">{{ 'yesOption' | translate }}</mat-option>
|
||||
<mat-option [value]="false">{{ 'noOption' | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="searchInstalledStep" [text]="'searchInstalledDescription' | translate">
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="searchInstalledStep"
|
||||
[text]="'searchInstalledDescription' | translate">
|
||||
<mat-label>{{ 'searchInstalledLabel' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="filters['installed']" (selectionChange)="search()" [placeholder]="'selectOptionPlaceholder' | translate">
|
||||
<mat-select [(ngModel)]="filters['installed']" (selectionChange)="search()"
|
||||
[placeholder]="'selectOptionPlaceholder' | translate">
|
||||
<mat-option [value]="''">{{ 'allOption' | translate }}</mat-option>
|
||||
<mat-option [value]="true">{{ 'yesOption' | translate }}</mat-option>
|
||||
<mat-option [value]="false">{{ 'noOption' | translate }}</mat-option>
|
||||
|
@ -42,8 +49,9 @@
|
|||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<mat-spinner *ngIf="loading"></mat-spinner>
|
||||
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" [text]="'tableDescription' | translate">
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
[text]="'tableDescription' | translate">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ column.header }}</th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
|
@ -66,7 +74,7 @@
|
|||
|
||||
<ng-container *ngIf="column.columnDef === 'name'">
|
||||
<span matTooltip="{{ image.name }}">
|
||||
{{ image.name }}
|
||||
{{ image.name }}
|
||||
</span>
|
||||
</ng-container>
|
||||
|
||||
|
@ -76,7 +84,8 @@
|
|||
</mat-chip>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl' && column.columnDef !== 'status' && column.columnDef !== 'name'">
|
||||
<ng-container
|
||||
*ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl' && column.columnDef !== 'status' && column.columnDef !== 'name'">
|
||||
{{ column.cell(image) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
|
@ -84,7 +93,8 @@
|
|||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'actionsColumnHeader' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let image" style="text-align: center;" joyrideStep="actionsStep" [text]="'actionsDescription' | translate">
|
||||
<td mat-cell *matCellDef="let image" style="text-align: center;" joyrideStep="actionsStep"
|
||||
[text]="'actionsDescription' | translate">
|
||||
<button mat-icon-button color="info" (click)="showOgLive($event, image)">
|
||||
<mat-icon>{{ 'viewIcon' | translate }}</mat-icon>
|
||||
</button>
|
||||
|
@ -98,7 +108,8 @@
|
|||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item [disabled]="image.installed" (click)="toggleAction(image, 'install')">{{ 'installOption' | translate }}</button>
|
||||
<button mat-menu-item [disabled]="image.installed" (click)="toggleAction(image, 'install')">{{ 'installOption' |
|
||||
translate }}</button>
|
||||
<button mat-menu-item [disabled]="!image.installed" (click)="toggleAction(image, 'uninstall')">
|
||||
{{ 'uninstallOption' | translate }}
|
||||
</button>
|
||||
|
@ -114,10 +125,7 @@
|
|||
</table>
|
||||
|
||||
<div class="paginator-container" joyrideStep="paginationStep" [text]="'paginationDescription' | translate">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
|
@ -15,6 +15,7 @@ import { MatInputModule } from '@angular/material/input';
|
|||
import { MatTableModule } from '@angular/material/table';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import { LoadingComponent } from '../../../shared/loading/loading.component';
|
||||
|
||||
describe('PXEimagesComponent', () => {
|
||||
let component: PXEimagesComponent;
|
||||
|
@ -26,7 +27,7 @@ describe('PXEimagesComponent', () => {
|
|||
|
||||
mockToastrService = jasmine.createSpyObj('ToastrService', ['success', 'error']);
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [PXEimagesComponent],
|
||||
declarations: [PXEimagesComponent, LoadingComponent],
|
||||
imports: [HttpClientModule,
|
||||
MatAccordion,
|
||||
MatExpansionPanel,
|
||||
|
|
|
@ -40,9 +40,9 @@ export class PXEimagesComponent implements OnInit {
|
|||
cell: (user: any) => `${user.id}`
|
||||
},
|
||||
{
|
||||
columnDef: 'filename',
|
||||
columnDef: 'name',
|
||||
header: 'Og Live',
|
||||
cell: (user: any) => `${user.filename}`
|
||||
cell: (user: any) => `${user.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'isDefault',
|
||||
|
|
|
@ -1,22 +1,13 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -34,29 +25,27 @@ table {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 10px;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.template-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
mat-spinner {
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,25 +2,31 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" joyrideStep="titleStep" text="{{ 'adminPxeDescription' | translate }}">{{ 'adminPxeTitle' | translate }}</h2>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="titleStep" text="{{ 'adminPxeDescription' | translate }}">{{ 'adminPxeTitle' |
|
||||
translate }}</h2>
|
||||
</div>
|
||||
<div class="template-button-row">
|
||||
<button mat-flat-button color="accent" (click)="openInfoDialog()">{{ 'viewInfoButton' | translate }}</button>
|
||||
<button mat-flat-button color="primary" (click)="addPxeTemplate()" joyrideStep="addTemplateStep" text="{{ 'addTemplateButtonDescription' | translate }}">{{ 'addTemplateButton' | translate }}</button>
|
||||
<button mat-flat-button color="primary" (click)="addPxeTemplate()" joyrideStep="addTemplateStep"
|
||||
text="{{ 'addTemplateButtonDescription' | translate }}">{{ 'addTemplateButton' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchNameStep" text="{{ 'searchNameDescription' | translate }}">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchNameStep"
|
||||
text="{{ 'searchNameDescription' | translate }}">
|
||||
<mat-label>{{ 'searchLabel' | translate }}</mat-label>
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()">
|
||||
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
|
||||
(keyup.enter)="search()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="searchSyncStep" text="{{ 'searchSyncDescription' | translate }}">
|
||||
<mat-form-field appearance="fill" class="search-boolean" joyrideStep="searchSyncStep"
|
||||
text="{{ 'searchSyncDescription' | translate }}">
|
||||
<mat-label>{{ 'createdInOgbootLabel' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="filters['synchronized']" (selectionChange)="search()" placeholder="{{ 'selectOptionPlaceholder' | translate }}">
|
||||
<mat-select [(ngModel)]="filters['synchronized']" (selectionChange)="search()"
|
||||
placeholder="{{ 'selectOptionPlaceholder' | translate }}">
|
||||
<mat-option [value]="''">{{ 'allOption' | translate }}</mat-option>
|
||||
<mat-option [value]="true">{{ 'yesOption' | translate }}</mat-option>
|
||||
<mat-option [value]="false">{{ 'noOption' | translate }}</mat-option>
|
||||
|
@ -28,8 +34,9 @@
|
|||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<mat-spinner *ngIf="loading"></mat-spinner>
|
||||
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableDescription' | translate }}">
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
text="{{ 'tableDescription' | translate }}">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ column.header }}</th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
|
@ -64,10 +71,7 @@
|
|||
</table>
|
||||
|
||||
<div class="paginator-container" joyrideStep="paginationStep" text="{{ 'paginationDescription' | translate }}">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
|
@ -23,13 +23,14 @@ import { MatInputModule } from '@angular/material/input';
|
|||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import { LoadingComponent } from '../../../shared/loading/loading.component';
|
||||
describe('PxeComponent', () => {
|
||||
let component: PxeComponent;
|
||||
let fixture: ComponentFixture<PxeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [PxeComponent],
|
||||
declarations: [PxeComponent, LoadingComponent],
|
||||
imports: [
|
||||
FormsModule,
|
||||
MatInputModule,
|
||||
|
|
|
@ -1,29 +1,27 @@
|
|||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.lists-container {
|
||||
padding: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -34,21 +32,10 @@
|
|||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-container h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
|
@ -64,7 +51,5 @@ mat-spinner {
|
|||
|
||||
.subnets-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 20px;
|
||||
gap: 10px;
|
||||
}
|
||||
gap: 15px;
|
||||
}
|
|
@ -2,35 +2,42 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" i18n="@@subnetsTitle" joyrideStep="titleStep" text="Desde aquí puedes gestionar las subredes configuradas en el servidor OgDHCP.">Administrar Subredes</h2>
|
||||
<div class="header-container-title">
|
||||
<h2 class="title" i18n="@@subnetsTitle" joyrideStep="titleStep"
|
||||
text="Desde aquí puedes gestionar las subredes configuradas en el servidor OgDHCP.">Administrar Subredes</h2>
|
||||
</div>
|
||||
<div class="subnets-button-row">
|
||||
<button mat-flat-button color="accent" (click)="openSubnetInfoDialog()" joyrideStep="viewInfoStep" text="Haz clic para ver información detallada de las subredes en el servidor.">Ver Información</button>
|
||||
<button mat-flat-button color="primary" (click)="addSubnet()" joyrideStep="addSubnetStep" text="Haz clic para añadir una nueva subred.">Añadir Subred</button>
|
||||
<button mat-flat-button color="accent" (click)="openSubnetInfoDialog()" joyrideStep="viewInfoStep"
|
||||
text="Haz clic para ver información detallada de las subredes en el servidor.">Ver Información</button>
|
||||
<button mat-flat-button color="primary" (click)="addSubnet()" joyrideStep="addSubnetStep"
|
||||
text="Haz clic para añadir una nueva subred.">Añadir Subred</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchNameStep" text="Busca subredes por nombre para localizar una subred específica rápidamente.">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchNameStep"
|
||||
text="Busca subredes por nombre para localizar una subred específica rápidamente.">
|
||||
<mat-label i18n="@@searchLabel">Buscar nombre de la subred</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchNetmaskStep" text="Busca subredes usando una netmask específica.">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchNetmaskStep"
|
||||
text="Busca subredes usando una netmask específica.">
|
||||
<mat-label i18n="@@searchLabel">Buscar netmask</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['netmask']" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchIpStep" text="Busca subredes por dirección IP.">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchIpStep"
|
||||
text="Busca subredes por dirección IP.">
|
||||
<mat-label i18n="@@searchLabel">Buscar IP</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['ip']" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchBootFileStep" text="Busca subredes según el nombre del archivo de arranque.">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchBootFileStep"
|
||||
text="Busca subredes según el nombre del archivo de arranque.">
|
||||
<mat-label i18n="@@searchLabel">Buscar Boot file name</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['bootFileName']" i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
|
@ -38,8 +45,9 @@
|
|||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<mat-spinner *ngIf="loading"></mat-spinner>
|
||||
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="Visualiza y administra las subredes listadas según los filtros aplicados.">
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
|
||||
text="Visualiza y administra las subredes listadas según los filtros aplicados.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let subnet">
|
||||
|
@ -66,11 +74,13 @@
|
|||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let subnet" style="text-align: center;" joyrideStep="actionsStep" text="Gestiona cada subred con opciones para editar, eliminar y más.">
|
||||
<td mat-cell *matCellDef="let subnet" style="text-align: center;" joyrideStep="actionsStep"
|
||||
text="Gestiona cada subred con opciones para editar, eliminar y más.">
|
||||
<button mat-icon-button color="primary" (click)="editSubnet(subnet)" i18n="@@editSubnet">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="primary" (click)="addClientsToSubnet(subnet)"><mat-icon>computer</mat-icon></button>
|
||||
<button mat-icon-button color="primary"
|
||||
(click)="addClientsToSubnet(subnet)"><mat-icon>computer</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="toggleAction(subnet, 'delete')"><mat-icon>delete</mat-icon></button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
@ -79,7 +89,9 @@
|
|||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
|
||||
<div class="paginator-container" joyrideStep="paginationStep" text="Navega entre las páginas de subredes usando el paginador.">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions" (page)="onPageChange($event)">
|
||||
<div class="paginator-container" joyrideStep="paginationStep"
|
||||
text="Navega entre las páginas de subredes usando el paginador.">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
|
@ -3,19 +3,20 @@ import { OgDhcpSubnetsComponent, Subnet } from './og-dhcp-subnets.component';
|
|||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { of } from 'rxjs';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { of } from 'rxjs';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import { LoadingComponent } from '../../../shared/loading/loading.component';
|
||||
|
||||
describe('OgDhcpSubnetsComponent', () => {
|
||||
let component: OgDhcpSubnetsComponent;
|
||||
|
@ -29,10 +30,10 @@ describe('OgDhcpSubnetsComponent', () => {
|
|||
mockHttpClient = jasmine.createSpyObj('HttpClient', ['get', 'post', 'put', 'delete']);
|
||||
mockToastrService = jasmine.createSpyObj('ToastrService', ['success', 'error']);
|
||||
mockHttpClient.get.and.returnValue(of({ 'hydra:member': [], 'hydra:totalItems': 0 }));
|
||||
mockHttpClient.post.and.returnValue(of({}));
|
||||
mockHttpClient.post.and.returnValue(of({}));
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [OgDhcpSubnetsComponent],
|
||||
declarations: [OgDhcpSubnetsComponent, LoadingComponent],
|
||||
imports: [
|
||||
MatExpansionModule,
|
||||
MatIconModule,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
.disk-usage-info{
|
||||
.disk-usage-info {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
margin-top: 10px;
|
||||
|
@ -7,9 +6,11 @@
|
|||
|
||||
p {
|
||||
margin-left: 15px;
|
||||
}.dashboard {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
padding: 0.5rem 0.5rem 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.disk-usage-container {
|
||||
display: flex;
|
||||
|
@ -66,7 +67,8 @@ table {
|
|||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
@ -78,7 +80,7 @@ th {
|
|||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 10px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
|
@ -86,8 +88,14 @@ th {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 10px;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.btn:first-child {
|
||||
|
@ -96,4 +104,4 @@ th {
|
|||
|
||||
.btn:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
|
@ -1,22 +1,21 @@
|
|||
<div class="dashboard">
|
||||
<div class="header-container" >
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<h2 joyrideStep="titleStep" text="Esta sección muestra el estado general del servidor OgDhcp.">OgDhcp server Status</h2>
|
||||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="titleStep" text="Esta sección muestra el estado general del servidor OgDhcp.">
|
||||
OgDhcp server Status
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard">
|
||||
<div class="disk-usage-container">
|
||||
<div class="disk-usage" joyrideStep="diskUsageStep" text="Visualiza el uso del disco del servidor.">
|
||||
<h3>Uso de disco</h3>
|
||||
<ngx-charts-pie-chart
|
||||
[view]="view"
|
||||
[scheme]="colorScheme"
|
||||
[results]="diskUsageChartData"
|
||||
[gradient]="gradient"
|
||||
[doughnut]="isDoughnut"
|
||||
[labels]="showLabels"
|
||||
[legend]="showLegend">
|
||||
<ngx-charts-pie-chart [view]="view" [scheme]="colorScheme" [results]="diskUsageChartData" [gradient]="gradient"
|
||||
[doughnut]="isDoughnut" [labels]="showLabels" [legend]="showLegend">
|
||||
</ngx-charts-pie-chart>
|
||||
<div class="disk-usage-info">
|
||||
<p>Total: {{ diskUsage.total }}</p>
|
||||
|
@ -26,21 +25,21 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="services-status" joyrideStep="servicesStatusStep" text="Aquí puedes ver el estado de los servicios importantes del servidor.">
|
||||
<div class="services-status" joyrideStep="servicesStatusStep"
|
||||
text="Aquí puedes ver el estado de los servicios importantes del servidor.">
|
||||
<h3>Servicios</h3>
|
||||
<ul>
|
||||
<li *ngFor="let service of getServices()">
|
||||
<span
|
||||
class="status-led"
|
||||
[ngClass]="{ 'active': service.status === 'active', 'inactive': service.status !== 'active' }"
|
||||
></span>
|
||||
<span class="status-led"
|
||||
[ngClass]="{ 'active': service.status === 'active', 'inactive': service.status !== 'active' }"></span>
|
||||
{{ service.name }}: {{ service.status }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="installed-oglives" joyrideStep="subnetsStep" text="Consulta la información de las subredes configuradas en el servidor.">
|
||||
<div class="installed-oglives" joyrideStep="subnetsStep"
|
||||
text="Consulta la información de las subredes configuradas en el servidor.">
|
||||
<h3>Subredes</h3>
|
||||
<table>
|
||||
<thead>
|
||||
|
@ -63,4 +62,4 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -20,6 +20,7 @@ import { ToastrModule } from 'ngx-toastr';
|
|||
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import {LoadingComponent} from "../../../../shared/loading/loading.component";
|
||||
|
||||
describe('StatusComponent', () => {
|
||||
let component: StatusComponent;
|
||||
|
@ -27,7 +28,7 @@ describe('StatusComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [StatusComponent],
|
||||
declarations: [StatusComponent, LoadingComponent],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
ToastrModule.forRoot(),
|
||||
|
@ -51,8 +52,8 @@ describe('StatusComponent', () => {
|
|||
JoyrideModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: {} }
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: {} }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
|
|
@ -13,6 +13,7 @@ export class StatusComponent {
|
|||
servicesStatus: any = {};
|
||||
subnets: any[] = [];
|
||||
diskUsageChartData: any[] = [];
|
||||
loading: boolean = false;
|
||||
|
||||
view: [number, number] = [1100, 500];
|
||||
|
||||
|
@ -24,13 +25,14 @@ export class StatusComponent {
|
|||
domain: ['#FF6384', '#3f51b5']
|
||||
};
|
||||
|
||||
constructor(private http: HttpClient,
|
||||
constructor(private http: HttpClient,
|
||||
private joyrideService: JoyrideService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadStatus();
|
||||
}
|
||||
loadStatus(): void {
|
||||
this.loading = true;
|
||||
this.http.get<any>(`${this.baseUrl}/og-dhcp/status`).subscribe(data => {
|
||||
this.diskUsage = data.message.disk_usage;
|
||||
this.servicesStatus = data.message.services_status;
|
||||
|
@ -46,7 +48,9 @@ export class StatusComponent {
|
|||
value: parseFloat(this.diskUsage.available)
|
||||
}
|
||||
];
|
||||
this.loading = false;
|
||||
}, error => {
|
||||
this.loading = false;
|
||||
console.error('Error fetching status', error);
|
||||
});
|
||||
}
|
||||
|
@ -61,14 +65,14 @@ export class StatusComponent {
|
|||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'titleStep',
|
||||
'diskUsageStep',
|
||||
'servicesStatusStep',
|
||||
'subnetsStep'
|
||||
'titleStep',
|
||||
'diskUsageStep',
|
||||
'servicesStatusStep',
|
||||
'subnetsStep'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.calendar-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.lists-container {
|
||||
|
@ -23,15 +28,14 @@
|
|||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -45,19 +49,12 @@ table {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
|
@ -2,26 +2,31 @@
|
|||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<h2 class="title" i18n="@@adminImagesTitle" joyrideStep="osTitleStep" text="En esta pantalla, puedes gestionar los sistemas operativos disponibles.">
|
||||
Administrar sistemas operativos
|
||||
</h2>
|
||||
<div class="header-container-title">
|
||||
<h2 i18n="@@adminImagesTitle" joyrideStep="osTitleStep"
|
||||
text="En esta pantalla, puedes gestionar los sistemas operativos disponibles.">
|
||||
Administrar sistemas operativos
|
||||
</h2>
|
||||
</div>
|
||||
<div class="calendar-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addSoftware()" joyrideStep="addOsButton" text="Añade un nuevo sistema operativo a la lista.">Añadir sistema operativo</button>
|
||||
<button mat-flat-button color="primary" (click)="addSoftware()" joyrideStep="addOsButton"
|
||||
text="Añade un nuevo sistema operativo a la lista.">Añadir sistema operativo</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchField" text="Busca un sistema operativo por nombre. Pulsa 'enter' para iniciar la búsqueda.">
|
||||
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchField"
|
||||
text="Busca un sistema operativo por nombre. Pulsa 'enter' para iniciar la búsqueda.">
|
||||
<mat-label i18n="@@searchLabel">Buscar nombre de sistema operativo</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()"
|
||||
i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="table" text="Esta tabla muestra los sistemas operativos existentes.">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="table"
|
||||
text="Esta tabla muestra los sistemas operativos existentes.">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let image">
|
||||
|
@ -32,12 +37,15 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;" joyrideStep="actionsHeader" text="Acciones disponibles para cada sistema operativo.">Acciones</th>
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;" joyrideStep="actionsHeader"
|
||||
text="Acciones disponibles para cada sistema operativo.">Acciones</th>
|
||||
<td mat-cell *matCellDef="let calendar" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="editSoftware(calendar)" i18n="@@editImage" joyrideStep="editButton" text="Editar el sistema operativo seleccionado.">
|
||||
<button mat-icon-button color="primary" (click)="editSoftware(calendar)" i18n="@@editImage"
|
||||
joyrideStep="editButton" text="Editar el sistema operativo seleccionado.">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="deleteSoftware(calendar)" i18n="@@buttonDelete" joyrideStep="deleteButton" text="Eliminar el sistema operativo seleccionado.">
|
||||
<button mat-icon-button color="warn" (click)="deleteSoftware(calendar)" i18n="@@buttonDelete"
|
||||
joyrideStep="deleteButton" text="Eliminar el sistema operativo seleccionado.">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
|
@ -48,10 +56,7 @@
|
|||
</table>
|
||||
|
||||
<div class="paginator-container" joyrideStep="pagination" text="Navega entre las páginas de sistemas operativos.">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
|
@ -1,3 +1,16 @@
|
|||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.client-header {
|
||||
display: flex;
|
||||
|
@ -36,20 +49,24 @@
|
|||
.charts-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between; /* Distribuye el espacio entre los gráficos */
|
||||
gap: 20px; /* Añade espacio entre los gráficos */
|
||||
justify-content: space-between;
|
||||
/* Distribuye el espacio entre los gráficos */
|
||||
gap: 20px;
|
||||
/* Añade espacio entre los gráficos */
|
||||
}
|
||||
|
||||
.disk-usage {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
min-width: 200px; /* Ajusta este valor según el tamaño mínimo deseado para cada gráfico */
|
||||
min-width: 200px;
|
||||
/* Ajusta este valor según el tamaño mínimo deseado para cada gráfico */
|
||||
}
|
||||
|
||||
.circular-chart {
|
||||
max-width: 150px;
|
||||
max-height: 150px;
|
||||
margin: 0 auto; /* Centra el gráfico dentro del contenedor */
|
||||
margin: 0 auto;
|
||||
/* Centra el gráfico dentro del contenedor */
|
||||
}
|
||||
|
||||
.chart {
|
||||
|
@ -89,7 +106,7 @@
|
|||
gap: 15px;
|
||||
}
|
||||
|
||||
.save-button{
|
||||
.save-button {
|
||||
align-self: flex-start;
|
||||
width: 100px !important;
|
||||
margin-top: 20px;
|
||||
|
@ -110,7 +127,7 @@
|
|||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.table-row {
|
||||
|
@ -135,10 +152,6 @@
|
|||
min-height: 400px;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.mat-tab-body-wrapper {
|
||||
min-height: inherit;
|
||||
}
|
||||
|
@ -170,7 +183,7 @@
|
|||
}
|
||||
|
||||
|
||||
.disk-usage-info{
|
||||
.disk-usage-info {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
margin-top: 10px;
|
||||
|
@ -178,9 +191,7 @@
|
|||
|
||||
p {
|
||||
margin-left: 15px;
|
||||
}.dashboard {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.disk-usage-container {
|
||||
display: flex;
|
||||
|
@ -190,8 +201,7 @@ p {
|
|||
|
||||
.header-button-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.disk-usage {
|
||||
|
@ -231,7 +241,8 @@ table {
|
|||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px; /* Espacio entre botones */
|
||||
gap: 10px;
|
||||
/* Espacio entre botones */
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
|
@ -262,7 +273,7 @@ table {
|
|||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
@ -281,17 +292,8 @@ table {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
|
@ -300,8 +302,7 @@ table {
|
|||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
|
||||
.dashboard {
|
||||
.server-status {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -381,5 +382,4 @@ table {
|
|||
.top-row .card {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,18 +1,13 @@
|
|||
<mat-tab-group class="main-container" dynamicHeight>
|
||||
<mat-tab-group dynamicHeight>
|
||||
<mat-tab label="Estado servidor">
|
||||
<div class="dashboard">
|
||||
<div class="server-status">
|
||||
<h2>OgRepository Server Status</h2>
|
||||
|
||||
<div class="row top-row">
|
||||
<div class="card">
|
||||
<h3>Uso de Disco</h3>
|
||||
<ngx-charts-pie-chart
|
||||
[view]="view"
|
||||
[scheme]="colorScheme"
|
||||
[results]="diskUsageChartData"
|
||||
[gradient]="gradient"
|
||||
[doughnut]="isDoughnut"
|
||||
[labels]="showLabels" >
|
||||
<ngx-charts-pie-chart [view]="view" [scheme]="colorScheme" [results]="diskUsageChartData"
|
||||
[gradient]="gradient" [doughnut]="isDoughnut" [labels]="showLabels">
|
||||
</ngx-charts-pie-chart>
|
||||
<div class="info">
|
||||
<p>Total: {{ diskUsage.total }}</p>
|
||||
|
@ -24,13 +19,8 @@
|
|||
|
||||
<div class="card">
|
||||
<h3>Uso de RAM</h3>
|
||||
<ngx-charts-pie-chart
|
||||
[view]="view"
|
||||
[scheme]="colorScheme"
|
||||
[results]="ramUsageChartData"
|
||||
[gradient]="gradient"
|
||||
[doughnut]="isDoughnut"
|
||||
[labels]="showLabels">
|
||||
<ngx-charts-pie-chart [view]="view" [scheme]="colorScheme" [results]="ramUsageChartData" [gradient]="gradient"
|
||||
[doughnut]="isDoughnut" [labels]="showLabels">
|
||||
</ngx-charts-pie-chart>
|
||||
<div class="info">
|
||||
<p>Total: {{ ramUsage.total }}</p>
|
||||
|
@ -45,7 +35,8 @@
|
|||
<div class="card">
|
||||
<h3>Uso de CPU</h3>
|
||||
<div class="cpu-usage-bar">
|
||||
<div class="cpu-bar" [style.width]="cpuUsage.percentage" [ngClass]="{'high': cpuUsage.percentage > '80%'}"></div>
|
||||
<div class="cpu-bar" [style.width]="cpuUsage.percentage" [ngClass]="{'high': cpuUsage.percentage > '80%'}">
|
||||
</div>
|
||||
</div>
|
||||
<p>Usado: {{ cpuUsage.percentage }}</p>
|
||||
</div>
|
||||
|
@ -54,17 +45,17 @@
|
|||
<h3>Servicios</h3>
|
||||
<ul>
|
||||
<li *ngFor="let service of getServices()">
|
||||
<span class="status-led" [ngClass]="{
|
||||
<span class="status-led" [ngClass]="{
|
||||
'active': service.status === 'active',
|
||||
'inactive': service.status === 'stopped' || service.status === 'status not accesible'
|
||||
}"></span>
|
||||
{{ service.name }}:
|
||||
<span [ngSwitch]="service.status">
|
||||
<span *ngSwitchCase="'active'">Activo</span>
|
||||
<span *ngSwitchCase="'stopped'">Detenido</span>
|
||||
<span *ngSwitchCase="'status not accesible'">No accesible</span>
|
||||
<span *ngSwitchDefault>{{ service.status }}</span>
|
||||
</span>
|
||||
<span *ngSwitchCase="'active'">Activo</span>
|
||||
<span *ngSwitchCase="'stopped'">Detenido</span>
|
||||
<span *ngSwitchCase="'status not accesible'">No accesible</span>
|
||||
<span *ngSwitchDefault>{{ service.status }}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -73,7 +64,7 @@
|
|||
<h3>Procesos</h3>
|
||||
<ul>
|
||||
<li *ngFor="let process of getProcesses()">
|
||||
<span class="status-led" [ngClass]="{
|
||||
<span class="status-led" [ngClass]="{
|
||||
'active': process.status === 'running',
|
||||
'inactive': process.status === 'stopped'
|
||||
}"></span>
|
||||
|
@ -85,18 +76,19 @@
|
|||
</div>
|
||||
</mat-tab>
|
||||
|
||||
|
||||
<mat-tab label="Datos generales">
|
||||
<div class="dashboard">
|
||||
<div class="header-button-container">
|
||||
<button mat-flat-button color="primary" (click)="syncRepository()">Sincronizar base de datos</button>
|
||||
<button mat-flat-button color="accent" (click)="openImageInfoDialog()">Ver Información</button>
|
||||
<div class="general-data">
|
||||
<div class="header-container">
|
||||
<div class="header-container-title">
|
||||
<h2>Editar datos repositorio</h2>
|
||||
</div>
|
||||
<div class="header-button-container">
|
||||
<button mat-flat-button color="primary" (click)="syncRepository()">Sincronizar base de datos</button>
|
||||
<button mat-flat-button color="accent" (click)="openImageInfoDialog()">Ver Información</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Editar datos repositorio</h2>
|
||||
<div *ngIf="loading" class="loading-container">
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div *ngIf="!loading" class="client-info form-container">
|
||||
<form [formGroup]="repositoryForm" (ngSubmit)="save()" class="repository-form">
|
||||
|
@ -116,13 +108,13 @@
|
|||
<input matInput formControlName="comments" name="comments">
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-flat-button color="primary" class="save-button"(click)="save()">Guardar</button>
|
||||
<button mat-flat-button color="primary" class="save-button" (click)="save()">Guardar</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Listado de imágenes">
|
||||
<app-images [repositoryUuid]="repositoryId"></app-images>
|
||||
<app-repository-images [repositoryUuid]="repositoryId"></app-repository-images>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-tab-group>
|
|
@ -16,6 +16,7 @@ import { DataService } from '../../calendar/data.service';
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { LoadingComponent } from '../../../shared/loading/loading.component';
|
||||
|
||||
describe('MainRepositoryViewComponent', () => {
|
||||
let component: MainRepositoryViewComponent;
|
||||
|
@ -23,7 +24,7 @@ describe('MainRepositoryViewComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [MainRepositoryViewComponent],
|
||||
declarations: [MainRepositoryViewComponent, LoadingComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
|
|
|
@ -119,7 +119,6 @@ export class MainRepositoryViewComponent implements OnInit {
|
|||
comments: [response.comments],
|
||||
});
|
||||
this.loading = false;
|
||||
this.searchImages();
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error al cargar los datos del cliente:', error);
|
||||
|
@ -207,27 +206,12 @@ export class MainRepositoryViewComponent implements OnInit {
|
|||
return this.processesStatus ? this.processesStatus : [];
|
||||
}
|
||||
|
||||
searchImages(): void {
|
||||
this.loading = true;
|
||||
this.http.get<any>(`${this.apiUrl}?repository.id=${this.repositoryData.id}&page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
|
||||
data => {
|
||||
this.dataSource.data = data['hydra:member'];
|
||||
this.length = data['hydra:totalItems'];
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching images', error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadAlert(): Observable<any> {
|
||||
return this.http.get<any>(`${this.baseUrl}/image-repositories/server/get-collection`);
|
||||
return this.http.post<any>(`${this.baseUrl}/image-repositories/server/${this.repositoryId}/get-collection`, {});
|
||||
}
|
||||
|
||||
syncRepository() {
|
||||
this.http.get(`${this.baseUrl}/image-repositories/server/sync`, {})
|
||||
this.http.post(`${this.baseUrl}/image-repositories/server/${this.repositoryId}/sync`, {})
|
||||
.subscribe(response => {
|
||||
this.toastService.success('Sincronización completada');
|
||||
this.load()
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
.title {
|
||||
font-size: 24px;
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.images-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 20px 0;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.lists-container {
|
||||
|
@ -37,21 +42,20 @@
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.image-name{
|
||||
.image-name {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -65,15 +69,8 @@ table {
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.paginator-container {
|
||||
|
@ -87,7 +84,7 @@ table {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field {
|
||||
.example-headers-align .mat-mdc-form-field+.mat-mdc-form-field {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
|
@ -98,6 +95,7 @@ table {
|
|||
|
||||
.example-button-row .mat-mdc-button-base {
|
||||
margin: 8px 8px 8px 0;
|
||||
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
|
@ -106,4 +104,12 @@ table {
|
|||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.chip-success {
|
||||
background-color: #63d165 !important;
|
||||
color: white ;
|
||||
}
|
||||
|
||||
.chip-failed {
|
||||
background-color: #ff6b63 !important;
|
||||
color: white;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
|
@ -8,7 +10,8 @@
|
|||
</h2>
|
||||
</div>
|
||||
<div class="images-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addImage()" joyrideStep="addStep" text="Utiliza este botón para añadir un nuevo repositorio.">Añadir repositorio</button>
|
||||
<button mat-flat-button color="primary" (click)="addImage()" joyrideStep="addStep"
|
||||
text="Utiliza este botón para añadir un nuevo repositorio.">Añadir repositorio</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
|
@ -16,13 +19,15 @@
|
|||
<div class="search-container">
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>Buscar nombre de repositorio</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()"
|
||||
i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="search-string">
|
||||
<mat-label>Buscar IP de repositorio</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['ip']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['ip']" (keyup.enter)="search()"
|
||||
i18n-placeholder="@@searchPlaceholder">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>Pulsar 'enter' para buscar</mat-hint>
|
||||
</mat-form-field>
|
||||
|
@ -31,9 +36,20 @@
|
|||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
|
||||
<td mat-cell *matCellDef="let repository" >
|
||||
<ng-container>
|
||||
{{ column.cell(repository) }}
|
||||
<td mat-cell *matCellDef="let repository">
|
||||
<ng-container [ngSwitch]="column.columnDef">
|
||||
<ng-container *ngSwitchCase="'status'">
|
||||
<mat-chip [ngClass]="{
|
||||
'chip-success': repository.status === true ,
|
||||
'chip-failed': repository.status === false
|
||||
}">
|
||||
{{ repository.status === true ? 'Conectado' : 'Fallido' }}
|
||||
</mat-chip>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchDefault>
|
||||
{{ column.cell(repository) }}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
@ -41,8 +57,8 @@
|
|||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
|
||||
<td mat-cell *matCellDef="let repository" style="text-align: center;">
|
||||
<button mat-icon-button color="primary" (click)="importImage($event, repository)" i18n="@@editImage"> <mat-icon>move_to_inbox</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="editRepository($event, repository)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="primary" (click)="editRepository($event, repository)" i18n="@@editImage">
|
||||
<mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button color="warn" (click)="deleteRepository($event, repository)">
|
||||
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
|
||||
</button>
|
||||
|
@ -53,10 +69,7 @@
|
|||
</table>
|
||||
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="[5, 10, 20, 40, 100]"
|
||||
(page)="onPageChange($event)">
|
||||
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="[5, 10, 20, 40, 100]"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
|
|
@ -19,6 +19,8 @@ import { ToastrModule, ToastrService } from 'ngx-toastr';
|
|||
import { DataService } from '../calendar/data.service';
|
||||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {LoadingComponent} from "../../shared/loading/loading.component";
|
||||
import {MatProgressSpinner} from "@angular/material/progress-spinner";
|
||||
|
||||
describe('RepositoriesComponent', () => {
|
||||
let component: RepositoriesComponent;
|
||||
|
@ -26,7 +28,7 @@ describe('RepositoriesComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [RepositoriesComponent],
|
||||
declarations: [RepositoriesComponent, LoadingComponent],
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
|
@ -40,6 +42,7 @@ describe('RepositoriesComponent', () => {
|
|||
MatDividerModule,
|
||||
MatIconModule,
|
||||
BrowserAnimationsModule,
|
||||
MatProgressSpinner,
|
||||
ToastrModule.forRoot(),
|
||||
TranslateModule.forRoot(),
|
||||
JoyrideModule.forRoot(),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component } from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {MatTableDataSource} from "@angular/material/table";
|
||||
import {DatePipe} from "@angular/common";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
|
@ -8,14 +8,13 @@ import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delet
|
|||
import { JoyrideService } from 'ngx-joyride';
|
||||
import {CreateRepositoryComponent} from "./create-repository/create-repository.component";
|
||||
import { Router } from '@angular/router';
|
||||
import {ImportImageComponent} from "./import-image/import-image.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-repositories',
|
||||
templateUrl: './repositories.component.html',
|
||||
styleUrl: './repositories.component.css'
|
||||
})
|
||||
export class RepositoriesComponent {
|
||||
export class RepositoriesComponent implements OnInit {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
length: number = 0;
|
||||
|
@ -40,6 +39,11 @@ export class RepositoriesComponent {
|
|||
header: 'Ip',
|
||||
cell: (repository: any) => `${repository.ip}`
|
||||
},
|
||||
{
|
||||
columnDef: 'status',
|
||||
header: 'Estado',
|
||||
cell: (repository: any) => `${repository.status}`
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
header: 'Fecha de creación',
|
||||
|
@ -92,16 +96,6 @@ export class RepositoriesComponent {
|
|||
this.router.navigate(['repository', repository.uuid]);
|
||||
}
|
||||
|
||||
importImage(event: MouseEvent, repository: any): void {
|
||||
event.stopPropagation();
|
||||
this.dialog.open(ImportImageComponent, {
|
||||
width: '600px',
|
||||
data: { repository }
|
||||
}).afterClosed().subscribe(() => {
|
||||
this.search();
|
||||
});
|
||||
}
|
||||
|
||||
deleteRepository(event: MouseEvent,command: any): void {
|
||||
event.stopPropagation();
|
||||
this.dialog.open(DeleteModalComponent, {
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
.images-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin: 1.5rem 0rem 1.5rem 0rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-string {
|
||||
flex: 2;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.search-boolean {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.example-headers-align .mat-expansion-panel-header-description {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: table-cell;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.button-row .mat-mdc-button-base {
|
||||
margin: 8px 8px 8px 0;
|
||||
}
|
||||
|
||||
.chip-failed {
|
||||
background-color: #e87979 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chip-success {
|
||||
background-color: #46c446 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chip-pending {
|
||||
background-color: lightgrey !important;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.chip-in-progress {
|
||||
background-color: #f5a623 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chip-transferring {
|
||||
background-color: #f5a623 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-container-title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<app-loading [isLoading]="loading"></app-loading>
|
||||
|
||||
<div class="header-container">
|
||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
<div class="header-container-title">
|
||||
<h2 joyrideStep="groupsTitleStepText" text="{{ 'groupsTitleStepText' | translate }}">
|
||||
{{ 'imagesTitle' | translate }} en {{ repository?.name }}
|
||||
</h2>
|
||||
</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)="search()" 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)="search()" 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>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="imagesTable" text="Esta tabla muestra las imágenes 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 === 'remotePc'">
|
||||
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
|
||||
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
|
||||
</mat-icon>
|
||||
</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>
|
||||
<ng-container *ngIf="column.columnDef !== 'remotePc' && column.columnDef !== 'status' && column.columnDef !== 'isGlobal'">
|
||||
{{ column.cell(image) }}
|
||||
</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="info" (click)="showImageInfo($event, image)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
|
||||
<button *ngIf="repositoryUuid" 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, 'get-aux')">Obtener ficheros auxiliares</button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'" (click)="toggleAction(image, 'delete-permanent')">Eliminar imagen</button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'trash'" (click)="toggleAction(image, 'recover')">Recuperar imagen de la papelera</button>
|
||||
<button mat-menu-item [disabled]="!image.imageFullsum || image.status !== 'success'" (click)="toggleAction(image, 'transfer')">Transferir imagen</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
|
||||
<div class="paginator-container">
|
||||
<mat-paginator [length]="length"
|
||||
[pageSize]="itemsPerPage"
|
||||
[pageIndex]="page"
|
||||
[pageSizeOptions]="[5, 10, 20, 40, 100]"
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { ReactiveFormsModule, FormsModule, FormBuilder } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ToastrModule, ToastrService } from 'ngx-toastr';
|
||||
import { JoyrideModule } from 'ngx-joyride';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {MatProgressSpinner} from "@angular/material/progress-spinner";
|
||||
import {RepositoryImagesComponent} from "./repository-images.component";
|
||||
import {LoadingComponent} from "../../../shared/loading/loading.component";
|
||||
import {MatOptionModule} from "@angular/material/core";
|
||||
import {MatSelectModule} from "@angular/material/select";
|
||||
|
||||
describe('RepositoriesComponent', () => {
|
||||
let component: RepositoryImagesComponent;
|
||||
let fixture: ComponentFixture<RepositoryImagesComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [RepositoryImagesComponent, LoadingComponent],
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatCheckboxModule,
|
||||
MatButtonModule,
|
||||
MatTableModule,
|
||||
MatPaginatorModule,
|
||||
MatDividerModule,
|
||||
MatIconModule,
|
||||
MatOptionModule,
|
||||
BrowserAnimationsModule,
|
||||
MatProgressSpinner,
|
||||
MatSelectModule ,
|
||||
ToastrModule.forRoot(),
|
||||
TranslateModule.forRoot(),
|
||||
JoyrideModule.forRoot(),
|
||||
CommonModule
|
||||
],
|
||||
providers: [
|
||||
FormBuilder,
|
||||
ToastrService,
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
{
|
||||
provide: MatDialogRef,
|
||||
useValue: {}
|
||||
},
|
||||
{
|
||||
provide: MAT_DIALOG_DATA,
|
||||
useValue: {}
|
||||
}
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(RepositoryImagesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,270 @@
|
|||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {MatTableDataSource} from "@angular/material/table";
|
||||
import {DatePipe} from "@angular/common";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {JoyrideService} from "ngx-joyride";
|
||||
import {CreateImageComponent} from "../../images/create-image/create-image.component";
|
||||
import {Observable} from "rxjs";
|
||||
import {ServerInfoDialogComponent} from "../../ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component";
|
||||
import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/delete-modal.component";
|
||||
import {ExportImageComponent} from "../../images/export-image/export-image.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-repository-images',
|
||||
templateUrl: './repository-images.component.html',
|
||||
styleUrl: './repository-images.component.css'
|
||||
})
|
||||
export class RepositoryImagesComponent implements OnInit {
|
||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
length: number = 0;
|
||||
itemsPerPage: number = 10;
|
||||
page: number = 0;
|
||||
loading: boolean = false;
|
||||
filters: { [key: string]: string } = {};
|
||||
alertMessage: string | null = null;
|
||||
repository: any = {};
|
||||
datePipe: DatePipe = new DatePipe('es-ES');
|
||||
columns = [
|
||||
{
|
||||
columnDef: 'id',
|
||||
header: 'Id',
|
||||
cell: (image: any) => `${image.id}`
|
||||
},
|
||||
{
|
||||
columnDef: 'image.image.name',
|
||||
header: 'Nombre de imagen',
|
||||
cell: (image: any) => `${image.image.name}`
|
||||
},
|
||||
{
|
||||
columnDef: 'remotePc',
|
||||
header: 'Remote Pc',
|
||||
cell: (image: any) => `${image.remotePc}`
|
||||
},
|
||||
{
|
||||
columnDef: 'status',
|
||||
header: 'Estado',
|
||||
cell: (image: any) => `${image.status}`
|
||||
},
|
||||
{
|
||||
columnDef: 'imageFullsum',
|
||||
header: 'Fullsum',
|
||||
cell: (image: any) => `${image.imageFullsum}`
|
||||
},
|
||||
{
|
||||
columnDef: 'createdAt',
|
||||
header: 'Fecha de creación',
|
||||
cell: (image: any) => `${this.datePipe.transform(image.createdAt, 'dd/MM/yyyy hh:mm:ss')}`
|
||||
}
|
||||
];
|
||||
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
|
||||
|
||||
private apiUrl = `${this.baseUrl}/image-image-repositories`;
|
||||
@Input() repositoryUuid: any
|
||||
private repositoryId: any;
|
||||
|
||||
constructor(
|
||||
public dialog: MatDialog,
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService,
|
||||
private joyrideService: JoyrideService,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.repositoryUuid) {
|
||||
this.loadRepository()
|
||||
} else {
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
|
||||
loadRepository(): void {
|
||||
this.http.get<any>(`${this.baseUrl}/image-repositories/${this.repositoryUuid}`, {}).subscribe(
|
||||
data => {
|
||||
this.repositoryId = data.id;
|
||||
this.repository = data
|
||||
this.search();
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching image repositories', error);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
search(): void {
|
||||
this.loading = true;
|
||||
this.http.get<any>(`${this.apiUrl}?page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}&repository.id=${this.repositoryId}`, { params: this.filters }).subscribe(
|
||||
data => {
|
||||
this.dataSource.data = data['hydra:member'];
|
||||
this.length = data['hydra:totalItems'];
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching images', error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onPageChange(event: any): void {
|
||||
this.page = event.pageIndex;
|
||||
this.itemsPerPage = event.pageSize;
|
||||
this.length = event.length;
|
||||
this.search();
|
||||
}
|
||||
|
||||
loadImageAlert(image: any): Observable<any> {
|
||||
return this.http.get<any>(`${this.apiUrl}/server/${image.uuid}/get`, {});
|
||||
}
|
||||
|
||||
showImageInfo(event: MouseEvent, image:any) {
|
||||
event.stopPropagation();
|
||||
this.loading = true;
|
||||
this.loadImageAlert(image).subscribe(
|
||||
response => {
|
||||
this.alertMessage = response;
|
||||
|
||||
this.dialog.open(ServerInfoDialogComponent, {
|
||||
width: '800px',
|
||||
data: {
|
||||
message: this.alertMessage
|
||||
}
|
||||
});
|
||||
this.loading = false;
|
||||
},
|
||||
error => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
toggleAction(image: any, action:string): void {
|
||||
switch (action) {
|
||||
case 'get-aux':
|
||||
this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/create-aux-files`, {}).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Petición de creación de archivos auxiliares enviada');
|
||||
this.search()
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
break;
|
||||
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.search()
|
||||
},
|
||||
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.repositoryUuid}` })
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Petición de eliminación de la papelera temporal enviada');
|
||||
this.search()
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case 'delete-permanent':
|
||||
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.search()
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.error(error.error['hydra:description']);
|
||||
}
|
||||
});
|
||||
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.search()
|
||||
},
|
||||
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;
|
||||
default:
|
||||
console.error('Acción no soportada:', action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
iniciarTour(): void {
|
||||
this.joyrideService.startTour({
|
||||
steps: [
|
||||
'imagesTitleStep',
|
||||
'addImageButton',
|
||||
'searchImagesField',
|
||||
'imagesTable',
|
||||
'actionsHeader',
|
||||
'editImageButton',
|
||||
'deleteImageButton',
|
||||
'imagesPagination'
|
||||
],
|
||||
showPrevButton: true,
|
||||
themeColor: '#3f51b5'
|
||||
});
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue