refs #1320. Splice create client logic.
testing/ogGui-multibranch/pipeline/head There was a failure building this commit
Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit
Details
parent
d689583b56
commit
b74f129dbe
|
@ -123,6 +123,7 @@ import { EnvVarsComponent } from './components/admin/env-vars/env-vars.component
|
||||||
import { MatSortModule } from '@angular/material/sort';
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
import { MenusComponent } from './components/menus/menus.component';
|
import { MenusComponent } from './components/menus/menus.component';
|
||||||
import { CreateMenuComponent } from './components/menus/create-menu/create-menu.component';
|
import { CreateMenuComponent } from './components/menus/create-menu/create-menu.component';
|
||||||
|
import { CreateMultipleClientComponent } from './components/groups/shared/clients/create-multiple-client/create-multiple-client.component';
|
||||||
export function HttpLoaderFactory(http: HttpClient) {
|
export function HttpLoaderFactory(http: HttpClient) {
|
||||||
return new TranslateHttpLoader(http, './locale/', '.json');
|
return new TranslateHttpLoader(http, './locale/', '.json');
|
||||||
}
|
}
|
||||||
|
@ -204,6 +205,7 @@ export function HttpLoaderFactory(http: HttpClient) {
|
||||||
EnvVarsComponent,
|
EnvVarsComponent,
|
||||||
MenusComponent,
|
MenusComponent,
|
||||||
CreateMenuComponent,
|
CreateMenuComponent,
|
||||||
|
CreateMultipleClientComponent,
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
imports: [BrowserModule,
|
imports: [BrowserModule,
|
||||||
|
|
|
@ -13,9 +13,12 @@
|
||||||
matTooltip="{{ 'newOrganizationalUnitTooltip' | translate }}" matTooltipShowDelay="1000">
|
matTooltip="{{ 'newOrganizationalUnitTooltip' | translate }}" matTooltipShowDelay="1000">
|
||||||
{{ 'newOrganizationalUnitButton' | translate }}
|
{{ 'newOrganizationalUnitButton' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button mat-flat-button color="primary" (click)="addClient($event)" matTooltipShowDelay="1000">
|
<button mat-flat-button color="primary" [matMenuTriggerFor]="menu">{{ 'newClientButton' | translate }}</button>
|
||||||
{{ 'newClientButton' | translate }}
|
<mat-menu #menu="matMenu">
|
||||||
</button>
|
<button mat-menu-item (click)="addClient($event)" >Añadir cliente unitario</button>
|
||||||
|
<button mat-menu-item (click)="addMultipleClients($event)">Añadir clientes masivamente</button>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
<button mat-flat-button (click)="openBottomSheet()" joyrideStep="keyStep" text="{{ 'keyStepText' | translate }}"
|
<button mat-flat-button (click)="openBottomSheet()" joyrideStep="keyStep" text="{{ 'keyStepText' | translate }}"
|
||||||
matTooltipShowDelay="1000">
|
matTooltipShowDelay="1000">
|
||||||
{{ 'legendButton' | translate }}
|
{{ 'legendButton' | translate }}
|
||||||
|
@ -204,11 +207,11 @@
|
||||||
|
|
||||||
<!-- Clients view -->
|
<!-- Clients view -->
|
||||||
<div class="clients-container">
|
<div class="clients-container">
|
||||||
<span class="clients-title-name">{{ 'clients' | translate }}
|
<span class="clients-title-name">{{ 'clients' | translate }}
|
||||||
<strong>{{ selectedNode?.name ? ' ' + selectedNode?.name : ' ' + selectedUnidad?.name }}</strong>
|
<strong>{{ selectedNode?.name ? ' ' + selectedNode?.name : ' ' + selectedUnidad?.name }}</strong>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<mat-divider></mat-divider>
|
<mat-divider style="margin-bottom: 10px;"></mat-divider>
|
||||||
|
|
||||||
<div *ngIf="(selectedClients.data?.length || 0) > 0; else noClientsTemplate">
|
<div *ngIf="(selectedClients.data?.length || 0) > 0; else noClientsTemplate">
|
||||||
<!-- Cards view -->
|
<!-- Cards view -->
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { ClassroomViewDialogComponent } from './shared/classroom-view/classroom-
|
||||||
import { MatSort } from '@angular/material/sort';
|
import { MatSort } from '@angular/material/sort';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { MatPaginator } from '@angular/material/paginator';
|
import { MatPaginator } from '@angular/material/paginator';
|
||||||
|
import {CreateMultipleClientComponent} from "./shared/clients/create-multiple-client/create-multiple-client.component";
|
||||||
|
|
||||||
enum NodeType {
|
enum NodeType {
|
||||||
OrganizationalUnit = 'organizational-unit',
|
OrganizationalUnit = 'organizational-unit',
|
||||||
|
@ -330,6 +331,20 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addMultipleClients(event: MouseEvent, organizationalUnit: TreeNode | null = null): void {
|
||||||
|
event.stopPropagation();
|
||||||
|
const dialogRef = this.dialog.open(CreateMultipleClientComponent, {
|
||||||
|
data: { organizationalUnit },
|
||||||
|
width: '900px',
|
||||||
|
});
|
||||||
|
dialogRef.afterClosed().subscribe(() => {
|
||||||
|
this.refreshOrganizationalUnits();
|
||||||
|
if (organizationalUnit && organizationalUnit['@id']) {
|
||||||
|
this.refreshClientsForNode(organizationalUnit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private refreshOrganizationalUnits(): void {
|
private refreshOrganizationalUnits(): void {
|
||||||
const expandedNodeIds = this.treeControl.dataNodes
|
const expandedNodeIds = this.treeControl.dataNodes
|
||||||
? this.treeControl.dataNodes
|
? this.treeControl.dataNodes
|
||||||
|
|
|
@ -1,161 +1,63 @@
|
||||||
.create-client-container {
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #3f51b5;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 16px;
|
gap: 15px;
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h3, h4 {
|
.form-field {
|
||||||
margin: 0 0 16px;
|
width: 100%;
|
||||||
color: #333;
|
margin-top: 10px;
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputs-container {
|
|
||||||
display: flex;
|
|
||||||
gap: 24px;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-dialog-content {
|
.mat-dialog-content {
|
||||||
flex: 1;
|
padding: 50px;
|
||||||
background-color: #f9f9f9;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 16px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
min-width: 600px;
|
|
||||||
max-width: 90vw;
|
|
||||||
width: 800px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-multiple-client-container {
|
button {
|
||||||
flex: 1;
|
text-transform: none;
|
||||||
background-color: #f9f9f9;
|
font-size: 16px;
|
||||||
border-radius: 8px;
|
font-weight: 500;
|
||||||
padding: 16px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-form {
|
.mat-slide-toggle {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-option .unit-name {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-option .unit-path {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-client-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 16px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-field {
|
.form-field {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-form-field {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollable-table {
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
margin-top: 16px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
th, td {
|
|
||||||
text-align: left;
|
|
||||||
padding: 8px;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:hover {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-dialog-actions {
|
|
||||||
margin-top: 16px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.mat-raised-button {
|
|
||||||
text-transform: none;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
margin: 16px auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #007BFF;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-button:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-divider {
|
|
||||||
margin: 0 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="file"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.inputs-container {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-dialog-content, .create-multiple-client-container {
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollable-table {
|
|
||||||
max-height: 150px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,155 +1,111 @@
|
||||||
<div class="create-client-container mat-elevation-z4">
|
<div class="create-client-container">
|
||||||
<h1>{{ 'addClientTitle' | translate }}s</h1>
|
<h1 mat-dialog-title i18n="@@add-client-dialog-title">Añadir Cliente</h1>
|
||||||
<div class="inputs-container">
|
<div class="mat-dialog-content" [ngClass]="{'loading': loading}">
|
||||||
<div class="mat-dialog-content" [ngClass]="{'loading': loading}">
|
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||||
|
<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-option *ngFor="let unit of parentUnits" [value]="unit['@id']">
|
||||||
|
<div class="unit-name">{{ unit.name }}</div>
|
||||||
|
<div class="unit-path">{{ unit.path }}</div>
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<form [formGroup]="clientForm" class="client-form grid-form" *ngIf="!loading">
|
<mat-form-field class="form-field">
|
||||||
<mat-form-field class="form-field">
|
<mat-label i18n="@@name-label">Nombre</mat-label>
|
||||||
<mat-label>{{ 'organizationalUnitLabel' | translate }}</mat-label>
|
<input matInput formControlName="name">
|
||||||
<mat-select formControlName="organizationalUnit">
|
</mat-form-field>
|
||||||
<mat-option *ngFor="let unit of parentUnits" [value]="unit['@id']">
|
|
||||||
<div class="unit-name">{{ unit.name }}</div>
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div *ngIf="!isSingleClientForm; else singleClientForm">
|
<mat-form-field class="form-field">
|
||||||
<h3>Añadir múltiples clientes</h3>
|
<mat-label i18n="@@oglive-label">OgLive</mat-label>
|
||||||
<div class="upload-container">
|
<mat-select formControlName="ogLive">
|
||||||
<button mat-raised-button color="primary" (click)="fileInput.click()">Subir fichero</button>
|
<mat-option *ngFor="let oglive of ogLives" [value]="oglive['@id']">
|
||||||
<input #fileInput type="file" (change)="onFileUpload($event)" accept="*" hidden>
|
{{ oglive.name }}
|
||||||
<p>o añadelos manualmente:</p>
|
</mat-option>
|
||||||
<div *ngIf="showTextarea">
|
</mat-select>
|
||||||
<textarea #textarea matInput placeholder="Ejemplo: host bbaa-it1-11 { hardware ethernet a0:48:1c:8a:f1:5b; fixed-address 172.17.69.11; };" rows="20" cols="100"></textarea>
|
</mat-form-field>
|
||||||
<button mat-raised-button color="primary" (click)="onTextarea(textarea.value)">cargar</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4 *ngIf="uploadedClients.length > 0">Clientes importados:</h4>
|
<mat-form-field class="form-field">
|
||||||
<div class="scrollable-table">
|
<mat-label i18n="@@serial-number-label">Número de Serie</mat-label>
|
||||||
<table mat-table [dataSource]="uploadedClients" class="mat-elevation-z8" *ngIf="uploadedClients.length > 0">
|
<input matInput formControlName="serialNumber">
|
||||||
<ng-container matColumnDef="name">
|
</mat-form-field>
|
||||||
<th mat-header-cell *matHeaderCellDef> Nombre </th>
|
|
||||||
<td mat-cell *matCellDef="let client"> {{ client.name }} </td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="ip">
|
<mat-form-field class="form-field">
|
||||||
<th mat-header-cell *matHeaderCellDef> IP </th>
|
<mat-label i18n="@@netiface-label">Interfaz de red</mat-label>
|
||||||
<td mat-cell *matCellDef="let client"> {{ client.ip }} </td>
|
<mat-select formControlName="netiface">
|
||||||
</ng-container>
|
<mat-option *ngFor="let type of netifaceTypes" [value]="type.value">
|
||||||
|
{{ type.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
<mat-form-field class="form-field">
|
||||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
<mat-label i18n="@@net-driver-label">Controlador de red</mat-label>
|
||||||
</table>
|
<mat-select formControlName="netDriver">
|
||||||
</div>
|
<mat-option *ngFor="let type of netDriverTypes" [value]="type.value">
|
||||||
</div>
|
{{ type.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<!-- Añadir uon cliente -->
|
<mat-form-field class="form-field">
|
||||||
|
<mat-label i18n="@@mac-label">MAC</mat-label>
|
||||||
|
<mat-hint i18n="@@mac-hint">Ejemplo: 00:11:22:33:44:55</mat-hint>
|
||||||
|
<input matInput formControlName="mac">
|
||||||
|
<mat-error i18n="@@mac-error">Formato de MAC inválido. Ejemplo válido: 00:11:22:33:44:55</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<ng-template #singleClientForm>
|
<mat-form-field class="form-field">
|
||||||
<h3>Añadir un cliente</h3>
|
<mat-label i18n="@@ip-label">Dirección IP</mat-label>
|
||||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
<mat-hint i18n="@@ip-hint">Ejemplo: 127.0.0.1</mat-hint>
|
||||||
<form [formGroup]="clientForm" class="client-form grid-form" *ngIf="!loading">
|
<input matInput formControlName="ip">
|
||||||
|
<mat-error i18n="@@ip-error">Formato de dirección IP inválido. Ejemplo válido: 127.0.0.1</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<mat-label>{{ 'nameLabel' | translate }}</mat-label>
|
<mat-label i18n="@@oglive-label">Plantilla PXE</mat-label>
|
||||||
<input matInput formControlName="name">
|
<mat-select formControlName="template">
|
||||||
</mat-form-field>
|
<mat-option *ngFor="let template of templates" [value]="template['@id']">
|
||||||
|
{{ template.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<mat-label>{{ 'ogLiveLabel' | translate }}</mat-label>
|
<mat-label i18n="@@hardware-profile-label">Perfil de Hardware</mat-label>
|
||||||
<mat-select formControlName="ogLive">
|
<mat-select formControlName="hardwareProfile">
|
||||||
<mat-option *ngFor="let oglive of ogLives" [value]="oglive['@id']">
|
<mat-option *ngFor="let unit of hardwareProfiles" [value]="unit['@id']">
|
||||||
{{ oglive.filename }}
|
{{ unit.description }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
<mat-error i18n="@@hardware-profile-error">Formato de URL inválido.</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<mat-label>{{ 'serialNumberLabel' | translate }}</mat-label>
|
<mat-label i18n="@@hardware-profile-label">Repositorio</mat-label>
|
||||||
<input matInput formControlName="serialNumber">
|
<mat-select formControlName="repository">
|
||||||
</mat-form-field>
|
<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-form-field class="form-field">
|
||||||
<mat-label>{{ 'netifaceLabel' | translate }}</mat-label>
|
<mat-label>{{ 'menuLabel' | translate }}</mat-label>
|
||||||
<mat-select formControlName="netiface">
|
<mat-select formControlName="menu">
|
||||||
<mat-option *ngFor="let type of netifaceTypes" [value]="type.value">
|
<mat-option *ngFor="let menu of menus" [value]="menu['@id']">
|
||||||
{{ type.name }}
|
{{ menu.name }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
<mat-error>{{ 'menuError' | translate }}</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
<mat-form-field class="form-field">
|
</form>
|
||||||
<mat-label>{{ 'netDriverLabel' | translate }}</mat-label>
|
|
||||||
<mat-select formControlName="netDriver">
|
|
||||||
<mat-option *ngFor="let type of netDriverTypes" [value]="type.value">
|
|
||||||
{{ type.name }}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
|
||||||
<mat-label>{{ 'macLabel' | translate }}</mat-label>
|
|
||||||
<mat-hint>{{ 'macHint' | translate }}</mat-hint>
|
|
||||||
<input matInput formControlName="mac">
|
|
||||||
<mat-error>{{ 'macError' | translate }}</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
|
||||||
<mat-label>{{ 'ipLabel' | translate }}</mat-label>
|
|
||||||
<mat-hint>{{ 'ipHint' | translate }}</mat-hint>
|
|
||||||
<input matInput formControlName="ip">
|
|
||||||
<mat-error>{{ 'ipError' | translate }}</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
|
||||||
<mat-label>{{ 'templateLabel' | translate }}</mat-label>
|
|
||||||
<mat-select formControlName="template">
|
|
||||||
<mat-option *ngFor="let template of templates" [value]="template['@id']">
|
|
||||||
{{ template.name }}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</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>{{ 'hardwareProfileError' | translate }}</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
|
||||||
<mat-label i18n="@@hardware-profile-label">Repositorio</mat-label>
|
|
||||||
<mat-select formControlName="repository">
|
|
||||||
<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>{{ 'menuLabel' | translate }}</mat-label>
|
|
||||||
<mat-select formControlName="menu">
|
|
||||||
<mat-option *ngFor="let menu of menus" [value]="menu['@id']">
|
|
||||||
{{ menu.name }}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
<mat-error>{{ 'menuError' | translate }}</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
</form>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div mat-dialog-actions align="end">
|
<div mat-dialog-actions align="end">
|
||||||
<button mat-button (click)="toggleClientForm()">
|
<button mat-button (click)="onNoClick()" i18n="@@cancel-button">Cancelar</button>
|
||||||
{{ isSingleClientForm ? 'Añadir múltiples clientes' : 'Añadir un único cliente' }}
|
<button mat-button [disabled]="!clientForm.valid" (click)="onSubmit()" i18n="@@add-button">Añadir</button>
|
||||||
</button>
|
|
||||||
<button mat-button color="warn" (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
|
||||||
<button mat-button color="primary" (click)="onSubmit()">{{ 'addButton' | translate }}</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
.create-client-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h3, h4 {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputs-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-dialog-content {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 90vw;
|
||||||
|
width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-multiple-client-container {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-table {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-top: 16px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-dialog-actions {
|
||||||
|
margin-top: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.mat-raised-button {
|
||||||
|
text-transform: none;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
margin: 16px auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #007BFF;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-button:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-divider {
|
||||||
|
margin: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.inputs-container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-dialog-content, .create-multiple-client-container {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-table {
|
||||||
|
max-height: 150px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
<div class="create-client-container">
|
||||||
|
<h1 mat-dialog-title i18n="@@add-client-dialog-title">Añadir multiples clientes</h1>
|
||||||
|
<div class="inputs-container">
|
||||||
|
<div class="mat-dialog-content" [ngClass]="{'loading': loading}">
|
||||||
|
|
||||||
|
<form [formGroup]="clientForm" class="client-form grid-form" *ngIf="!loading">
|
||||||
|
<mat-form-field class="form-field">
|
||||||
|
<mat-label>{{ 'organizationalUnitLabel' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="organizationalUnit">
|
||||||
|
<mat-option *ngFor="let unit of parentUnits" [value]="unit['@id']">
|
||||||
|
<div class="unit-name">{{ unit.name }}</div>
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="upload-container">
|
||||||
|
<button mat-raised-button color="primary" (click)="fileInput.click()">Subir fichero</button>
|
||||||
|
<input #fileInput type="file" (change)="onFileUpload($event)" accept="*" hidden>
|
||||||
|
<p>o añadelos manualmente:</p>
|
||||||
|
<div *ngIf="showTextarea">
|
||||||
|
<textarea #textarea matInput placeholder="Ejemplo: host bbaa-it1-11 { hardware ethernet a0:48:1c:8a:f1:5b; fixed-address 172.17.69.11; };" rows="20" cols="100"></textarea>
|
||||||
|
<button mat-raised-button color="primary" (click)="onTextarea(textarea.value)">Previsualizar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 *ngIf="uploadedClients.length > 0">Clientes importados:</h4>
|
||||||
|
<div *ngIf="uploadedClients.length > 0" class="scrollable-table">
|
||||||
|
<table mat-table [dataSource]="uploadedClients" class="mat-elevation-z8" *ngIf="uploadedClients.length > 0">
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> Nombre </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.name }} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="ip">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> IP </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.ip }} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="mac">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> Mac </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.mac }} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div mat-dialog-actions align="end">
|
||||||
|
<button mat-button color="warn" (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
||||||
|
<button mat-button color="primary" [disabled]="!clientForm.value.organizationalUnit" (click)="onSubmit()">{{ 'saveButton' | translate }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CreateMultipleClientComponent } from './create-multiple-client.component';
|
||||||
|
|
||||||
|
describe('CreateMultipleClientComponent', () => {
|
||||||
|
let component: CreateMultipleClientComponent;
|
||||||
|
let fixture: ComponentFixture<CreateMultipleClientComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [CreateMultipleClientComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CreateMultipleClientComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,182 @@
|
||||||
|
import {Component, Inject} from '@angular/core';
|
||||||
|
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
|
||||||
|
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||||
|
import {HttpClient} from "@angular/common/http";
|
||||||
|
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||||
|
import {ToastrService} from "ngx-toastr";
|
||||||
|
import {DataService} from "../../../services/data.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-create-multiple-client',
|
||||||
|
templateUrl: './create-multiple-client.component.html',
|
||||||
|
styleUrl: './create-multiple-client.component.css'
|
||||||
|
})
|
||||||
|
export class CreateMultipleClientComponent {
|
||||||
|
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||||
|
clientForm!: FormGroup;
|
||||||
|
parentUnits: any[] = [];
|
||||||
|
hardwareProfiles: any[] = [];
|
||||||
|
ogLives: any[] = [];
|
||||||
|
menus: any[] = [];
|
||||||
|
templates: any[] = [];
|
||||||
|
uploadedClients: any[] = [];
|
||||||
|
repositories: any[] = [];
|
||||||
|
loading: boolean = false;
|
||||||
|
displayedColumns: string[] = ['name', 'ip', 'mac'];
|
||||||
|
showTextarea: boolean = true;
|
||||||
|
protected netifaceTypes = [
|
||||||
|
{ name: 'Eth0', value: 'eth0' },
|
||||||
|
{ name: 'Eth1', value: 'eth1' },
|
||||||
|
{ name: 'Eth2', value: 'eth2' }
|
||||||
|
];
|
||||||
|
protected netDriverTypes = [
|
||||||
|
{ name: 'Generic', value: 'generic' }
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private dialogRef: MatDialogRef<CreateMultipleClientComponent>,
|
||||||
|
private http: HttpClient,
|
||||||
|
private snackBar: MatSnackBar,
|
||||||
|
private toastService: ToastrService,
|
||||||
|
private dataService: DataService,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: any
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.initForm();
|
||||||
|
this.loadParentUnits();
|
||||||
|
}
|
||||||
|
|
||||||
|
initForm(): void {
|
||||||
|
this.clientForm = this.fb.group({
|
||||||
|
organizationalUnit: [
|
||||||
|
this.data.organizationalUnit ? this.data.organizationalUnit['@id'] : null,
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
name: ['', Validators.required],
|
||||||
|
serialNumber: [''],
|
||||||
|
netiface: null,
|
||||||
|
netDriver: null,
|
||||||
|
mac: ['', Validators.required],
|
||||||
|
ip: ['', Validators.required],
|
||||||
|
template: [null],
|
||||||
|
hardwareProfile: [
|
||||||
|
this.data.organizationalUnit?.networkSettings?.hardwareProfile?.['@id'] || null
|
||||||
|
],
|
||||||
|
ogLive: [null],
|
||||||
|
repository: [null],
|
||||||
|
menu: [null]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadParentUnits(): void {
|
||||||
|
this.loading = true;
|
||||||
|
const url = `${this.baseUrl}/organizational-units?page=1&itemsPerPage=10000`;
|
||||||
|
|
||||||
|
this.http.get<any>(url).subscribe(
|
||||||
|
response => {
|
||||||
|
this.parentUnits = response['hydra:member'];
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error('Error fetching parent units:', error);
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileUpload(event: any): void {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (e: any) => {
|
||||||
|
const textData = e.target.result;
|
||||||
|
const regex = /host\s+(\S+)\s+\{\s+hardware\s+ethernet\s+([\da-fA-F:]+);\s+fixed-address\s+([\d.]+);\s+\}/g;
|
||||||
|
let match;
|
||||||
|
const clients = [];
|
||||||
|
|
||||||
|
while ((match = regex.exec(textData)) !== null) {
|
||||||
|
clients.push({
|
||||||
|
name: match[1],
|
||||||
|
mac: match[2],
|
||||||
|
ip: match[3]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clients.length > 0) {
|
||||||
|
this.uploadedClients = clients;
|
||||||
|
this.toastService.success('Archivo cargado correctamente, los datos están listos para enviarse.', 'Éxito');
|
||||||
|
this.showTextarea = false;
|
||||||
|
} else {
|
||||||
|
this.toastService.error('No se encontraron datos válidos', 'Error');
|
||||||
|
this.showTextarea = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextarea(text: string): void {
|
||||||
|
const regex = /host\s+(\S+)\s+\{\s+hardware\s+ethernet\s+([\da-fA-F:]+);\s+fixed-address\s+([\d.]+);\s+\}/g;
|
||||||
|
let match;
|
||||||
|
const clients = [];
|
||||||
|
|
||||||
|
while ((match = regex.exec(text)) !== null) {
|
||||||
|
clients.push({
|
||||||
|
name: match[1],
|
||||||
|
mac: match[2],
|
||||||
|
ip: match[3]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clients.length > 0) {
|
||||||
|
this.uploadedClients = clients;
|
||||||
|
this.toastService.success('Datos cargados correctamente, los datos están listos para enviarse.', 'Éxito');
|
||||||
|
this.showTextarea = false;
|
||||||
|
} else {
|
||||||
|
this.toastService.error('No se encontraron datos válidos', 'Error');
|
||||||
|
this.showTextarea = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.uploadedClients.length > 0) {
|
||||||
|
this.uploadedClients.forEach(client => {
|
||||||
|
const formData = {
|
||||||
|
organizationalUnit: this.clientForm.value.organizationalUnit || null,
|
||||||
|
name: client.name || null,
|
||||||
|
mac: client.mac || null,
|
||||||
|
ip: client.ip || null,
|
||||||
|
template: this.clientForm.value.template || null,
|
||||||
|
hardwareProfile: this.clientForm.value.hardwareProfile || null,
|
||||||
|
ogLive: this.clientForm.value.ogLive || null,
|
||||||
|
repository: this.clientForm.value.repository || null,
|
||||||
|
serialNumber: null,
|
||||||
|
netiface: null,
|
||||||
|
netDriver: null
|
||||||
|
};
|
||||||
|
|
||||||
|
this.http.post(`${this.baseUrl}/clients`, formData).subscribe(
|
||||||
|
response => {
|
||||||
|
this.toastService.success(`Cliente ${client.name} creado exitosamente`, 'Éxito');
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error(`Error al crear el cliente ${client.name}:`, error);
|
||||||
|
this.toastService.error(`Error al crear el cliente ${client.name}`, 'Error');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.uploadedClients = [];
|
||||||
|
this.dialogRef.close();
|
||||||
|
} else {
|
||||||
|
this.toastService.error('No hay clientes cargados para añadir', 'Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNoClick(): void {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue