[ogGui_418] Diseño de la estructura de grupos #4
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MaterialThemeProjectNewConfig">
|
||||
<option name="metadata">
|
||||
<MTProjectMetadataState>
|
||||
<option name="migrated" value="true" />
|
||||
<option name="pristineConfig" value="false" />
|
||||
<option name="userId" value="215863cb:18f708d4624:-7ffe" />
|
||||
</MTProjectMetadataState>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/oggui.iml" filepath="$PROJECT_DIR$/.idea/oggui.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -40,3 +40,4 @@ testem.log
|
|||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
],
|
||||
"styles": [
|
||||
"src/custom-theme.scss",
|
||||
"src/styles.css"
|
||||
"src/styles.css",
|
||||
"node_modules/ngx-toastr/toastr.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
|
@ -102,5 +103,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"analytics": "95fac95c-8936-41a8-8c9c-1fae82fe6912"
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,6 +21,7 @@
|
|||
"@angular/platform-browser-dynamic": "^18.0.0",
|
||||
"@angular/router": "^18.0.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"ngx-toastr": "^19.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "^0.14.6"
|
||||
|
|
|
@ -8,6 +8,7 @@ import { PageNotFoundComponent } from './components/page-not-found/page-not-foun
|
|||
import { AdminComponent } from './components/pages/admin/admin.component';
|
||||
import { UsersComponent } from './components/pages/admin/users/users/users.component';
|
||||
import { RolesComponent } from './components/pages/admin/roles/roles/roles.component';
|
||||
import { GroupsComponent } from './components/groups/groups.component';
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
|
||||
{
|
||||
|
@ -18,6 +19,7 @@ const routes: Routes = [
|
|||
{ path: 'admin', component: AdminComponent },
|
||||
{ path: 'users', component: UsersComponent },
|
||||
{ path: 'user-groups', component: RolesComponent },
|
||||
{ path: 'groups', component: GroupsComponent },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
.sidenav-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
// @ts-ignore
|
||||
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
|
@ -11,8 +13,8 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|||
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||
import { CustomInterceptor } from './services/custom.interceptor';
|
||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||
import {MatToolbarModule} from '@angular/material/toolbar';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import {MatToolbarModule} from '@angular/material/toolbar';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
@ -33,7 +35,38 @@ import { EditUserModalComponent } from './components/pages/admin/users/users/edi
|
|||
import { AddRoleModalComponent } from './components/pages/admin/roles/roles/add-role-modal/add-role-modal.component';
|
||||
import { DeleteRoleModalComponent } from './components/pages/admin/roles/roles/delete-role-modal/delete-role-modal.component';
|
||||
import { ChangePasswordModalComponent } from './components/pages/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/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/clients/create-client/create-client.component';
|
||||
import { DeleteModalComponent } from './components/groups/delete-modal/delete-modal.component';
|
||||
import { EditOrganizationalUnitComponent } from './components/groups/organizational-units/edit-organizational-unit/edit-organizational-unit.component';
|
||||
import { EditClientComponent } from './components/groups/clients/edit-client/edit-client.component';
|
||||
import { ClassroomViewComponent } from './components/groups/classroom-view/classroom-view.component';
|
||||
import {MatProgressSpinner} from "@angular/material/progress-spinner";
|
||||
import {MatMenu, MatMenuItem, MatMenuTrigger} from "@angular/material/menu";
|
||||
import {MatAutocomplete} from "@angular/material/autocomplete";
|
||||
import {MatChip, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule} from "@angular/material/chips";
|
||||
import { ClientViewComponent } from './components/groups/client-view/client-view.component';
|
||||
import {MatTab, MatTabGroup} from "@angular/material/tabs";
|
||||
import {MatTooltip} from "@angular/material/tooltip";
|
||||
import { DeleteGroupsModalComponent } from './components/groups/delete-groups-modal/delete-groups-modal.component';
|
||||
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { ShowOrganizationalUnitComponent } from './components/groups/organizational-units/show-organizational-unit/show-organizational-unit.component';
|
||||
import {MatGridList} from "@angular/material/grid-list";
|
||||
import { TreeViewComponent } from './components/groups/tree-view/tree-view.component';
|
||||
import {
|
||||
MatNestedTreeNode,
|
||||
MatTree,
|
||||
MatTreeNode,
|
||||
MatTreeNodeDef, MatTreeNodeOutlet,
|
||||
MatTreeNodePadding,
|
||||
MatTreeNodeToggle
|
||||
} from "@angular/material/tree";
|
||||
import { LegendComponent } from './components/groups/legend/legend.component';
|
||||
|
||||
@NgModule({ declarations: [
|
||||
AppComponent,
|
||||
|
@ -51,26 +84,55 @@ import { ChangePasswordModalComponent } from './components/pages/admin/users/use
|
|||
EditUserModalComponent,
|
||||
AddRoleModalComponent,
|
||||
DeleteRoleModalComponent,
|
||||
ChangePasswordModalComponent
|
||||
ChangePasswordModalComponent,
|
||||
GroupsComponent,
|
||||
CreateOrganizationalUnitComponent,
|
||||
CreateClientComponent,
|
||||
DeleteModalComponent,
|
||||
EditOrganizationalUnitComponent,
|
||||
EditClientComponent,
|
||||
ClassroomViewComponent,
|
||||
ClientViewComponent,
|
||||
DeleteGroupsModalComponent,
|
||||
ShowOrganizationalUnitComponent,
|
||||
TreeViewComponent,
|
||||
LegendComponent
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [BrowserModule,
|
||||
AppRoutingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatToolbarModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
MatSidenavModule,
|
||||
BrowserAnimationsModule,
|
||||
MatCardModule,
|
||||
MatCheckboxModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatListModule,
|
||||
MatTableModule,
|
||||
MatDialogModule,
|
||||
MatSelectModule,
|
||||
MatDividerModule,
|
||||
MatStepperModule,
|
||||
MatSlideToggleModule, MatMenu, MatMenuTrigger, MatMenuItem, MatAutocomplete, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule, MatChip, MatProgressSpinner, MatTabGroup, MatTab, MatTooltip,
|
||||
ToastrModule.forRoot(
|
||||
{
|
||||
timeOut: 5000,
|
||||
positionClass: 'toast-bottom-right',
|
||||
preventDuplicates: true,
|
||||
progressBar: true,
|
||||
progressAnimation: 'increasing',
|
||||
closeButton: true
|
||||
}
|
||||
), MatGridList, MatTree, MatTreeNode, MatNestedTreeNode, MatTreeNodeToggle, MatTreeNodeDef, MatTreeNodePadding, MatTreeNodeOutlet
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [BrowserModule,
|
||||
AppRoutingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatToolbarModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
MatSidenavModule,
|
||||
BrowserAnimationsModule,
|
||||
MatCardModule,
|
||||
MatCheckboxModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatListModule,
|
||||
MatTableModule,
|
||||
MatDialogModule,
|
||||
MatSelectModule],
|
||||
providers: [
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
|
@ -79,5 +141,5 @@ import { ChangePasswordModalComponent } from './components/pages/admin/users/use
|
|||
},
|
||||
provideAnimationsAsync(),
|
||||
provideHttpClient(withInterceptorsFromDi())
|
||||
] })
|
||||
], })
|
||||
export class AppModule { }
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
.classroom {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
min-height: 43vh;
|
||||
}
|
||||
|
||||
.classroom-group {
|
||||
flex: 1 1 25%;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
mat-card {
|
||||
width: 150px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.client-image-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.client-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.proyector-image {
|
||||
width: auto;
|
||||
height: 100px; /* Ajusta la altura según sea necesario */
|
||||
}
|
||||
|
||||
.client-info {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 5px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: rgba(0, 0, 0, 0); /* Fondo semitransparente para el texto */
|
||||
color: black; /* Color del texto */
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.client-name {
|
||||
font-size: medium;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
mat-chip {
|
||||
margin: 5px;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.client-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.client-container {
|
||||
margin: 5px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.client-box {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
background-color: lightblue;
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: box-shadow 0.3s ease-in-out, transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.client-box:hover {
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.client-box p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.mat-dialog-content.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.client-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.classroom-board {
|
||||
width: 250px;
|
||||
height: 120px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
margin-right: 20px; /* Espacio entre la pizarra y el proyector */
|
||||
}
|
||||
|
||||
.misc-clients {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 25px;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<div class="classroom">
|
||||
<mat-card *ngFor="let group of groupedClients" class="classroom-group">
|
||||
<mat-card-title>Clientes dentro de: {{ group.organizationalUnitName }}</mat-card-title>
|
||||
<div class="misc-clients">
|
||||
<div class="classroom-board">Pizarra digital</div>
|
||||
<img mat-card-image src="assets/images/proyector.png" alt="Proyector" class="proyector-image"/>
|
||||
</div>
|
||||
<div *ngFor="let row of group.clientRows" class="client-row">
|
||||
<div class="client-container" *ngFor="let client of row">
|
||||
<div class="client-box" (click)="handleClientClick(client)">
|
||||
<mat-card appearance="outlined">
|
||||
<div class="client-image-container">
|
||||
<img mat-card-image src="assets/images/client.png" alt="Client" class="client-image"/>
|
||||
<div class="client-info">
|
||||
<div class="client-name">{{ client.name }}</div>
|
||||
<div class="client-details">
|
||||
<span>{{ client.ip }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ClassroomViewComponent } from './classroom-view.component';
|
||||
|
||||
describe('ClassroomViewComponent', () => {
|
||||
let component: ClassroomViewComponent;
|
||||
let fixture: ComponentFixture<ClassroomViewComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ClassroomViewComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ClassroomViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {ClientViewComponent} from "../client-view/client-view.component";
|
||||
|
||||
interface GroupedClients {
|
||||
organizationalUnitName: string;
|
||||
clientRows: any[][];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-classroom-view',
|
||||
templateUrl: './classroom-view.component.html',
|
||||
styleUrls: ['./classroom-view.component.css']
|
||||
})
|
||||
export class ClassroomViewComponent implements OnInit {
|
||||
@Input() clients: any[] = [];
|
||||
@Input() pcInTable: number = 5;
|
||||
groupedClients: GroupedClients[] = [];
|
||||
|
||||
constructor(public dialog: MatDialog) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.groupClientsByOrganizationalUnit();
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.groupClientsByOrganizationalUnit();
|
||||
}
|
||||
|
||||
groupClientsByOrganizationalUnit(): void {
|
||||
const grouped = this.clients.reduce((acc, client) => {
|
||||
const ouName = client.organizationalUnit.name;
|
||||
if (!acc[ouName]) {
|
||||
acc[ouName] = [];
|
||||
}
|
||||
acc[ouName].push(client);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
this.groupedClients = Object.keys(grouped).map(ouName => ({
|
||||
organizationalUnitName: ouName,
|
||||
clientRows: this.chunkArray(grouped[ouName], this.pcInTable)
|
||||
}));
|
||||
}
|
||||
|
||||
chunkArray(arr: any[], chunkSize: number): any[][] {
|
||||
const chunks = [];
|
||||
for (let i = 0; i < arr.length; i += chunkSize) {
|
||||
chunks.push(arr.slice(i, i + chunkSize));
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
handleClientClick(client: any): void {
|
||||
const dialogRef = this.dialog.open(ClientViewComponent, { data: { client }, width: '800px', height:'700px'});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
.mat-dialog-content {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.button-encender {
|
||||
background-color: #3f51b5; /* Azul */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button-apagar {
|
||||
background-color: #e91e63; /* Rosa */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button-resetear {
|
||||
background-color: #f44336; /* Rojo */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button-otros-1 {
|
||||
background-color: #4caf50; /* Verde */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button-otros-2 {
|
||||
background-color: #ff9800; /* Naranja */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button-otros-3 {
|
||||
background-color: #9c27b0; /* Púrpura */
|
||||
color: white;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fixed-column {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 4px 6px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.button-column {
|
||||
display: flex;
|
||||
margin-top: 40px;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button-action {
|
||||
width: 200px;
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<h1 mat-dialog-title>Propiedades cliente</h1>
|
||||
<mat-dialog-content>
|
||||
<mat-tab-group dynamicHeight>
|
||||
<mat-tab label="Datos generales">
|
||||
<table mat-table [dataSource]="generalData" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="property">
|
||||
<th mat-header-cell *matHeaderCellDef class="fixed-column"> Propiedad </th>
|
||||
<td mat-cell *matCellDef="let element" class="fixed-column"> {{element.property}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="value">
|
||||
<th mat-header-cell *matHeaderCellDef> Valor </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.value}} </td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</mat-tab>
|
||||
<mat-tab label="Propiedades de red">
|
||||
<table mat-table [dataSource]="networkData" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="property">
|
||||
<th mat-header-cell *matHeaderCellDef class="fixed-column"> Propiedad </th>
|
||||
<td mat-cell *matCellDef="let element" class="fixed-column"> {{element.property}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="value">
|
||||
<th mat-header-cell *matHeaderCellDef> Valor </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.value}} </td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</mat-tab>
|
||||
<mat-tab label="Propiedades del aula">
|
||||
<table mat-table [dataSource]="classroomData" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="property">
|
||||
<th mat-header-cell *matHeaderCellDef class="fixed-column"> Propiedad </th>
|
||||
<td mat-cell *matCellDef="let element" class="fixed-column"> {{element.property}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="value">
|
||||
<th mat-header-cell *matHeaderCellDef> Valor </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.value}} </td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</mat-tab>
|
||||
<mat-tab label="Acciones">
|
||||
<div class="button-column">
|
||||
<button mat-flat-button color="primary" class="button-action button-encender">Encender</button>
|
||||
<button mat-flat-button color="accent" class="button-action button-apagar">Apagar</button>
|
||||
<button mat-flat-button color="warn" class="button-action button-resetear">Resetear</button>
|
||||
<button mat-flat-button class="button-action button-otros-1">Otras acciones 1</button>
|
||||
<button mat-flat-button class="button-action button-otros-2">Otras acciones 2</button>
|
||||
<button mat-flat-button class="button-action button-otros-3">Otras acciones 3</button>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Particiones">
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close (click)="onNoClick()">Cerrar</button>
|
||||
</mat-dialog-actions>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ClientViewComponent } from './client-view.component';
|
||||
|
||||
describe('ClientViewComponent', () => {
|
||||
let component: ClientViewComponent;
|
||||
let fixture: ComponentFixture<ClientViewComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ClientViewComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ClientViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
import {Component, Inject} from '@angular/core';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
|
||||
@Component({
|
||||
selector: 'app-client-view',
|
||||
templateUrl: './client-view.component.html',
|
||||
styleUrl: './client-view.component.css'
|
||||
})
|
||||
export class ClientViewComponent {
|
||||
|
||||
displayedColumns: string[] = ['property', 'value'];
|
||||
|
||||
generalData = [
|
||||
{property: 'Nombre', value: this.data.client.name},
|
||||
{property: 'Uuid', value: this.data.client.uuid},
|
||||
{property: 'IP', value: this.data.client.ip},
|
||||
{property: 'MAC', value: this.data.client.mac},
|
||||
{property: 'Nº de serie', value: this.data.client.serialNumber},
|
||||
{property: 'Netiface', value: this.data.client.netiface},
|
||||
{property: 'Perfil hardware', value: this.data.client.hardwareProfile ? this.data.client.hardwareProfile.description : ''},
|
||||
{property: 'Menú', value: this.data.client.menu ? this.data.client.menu.description : ''},
|
||||
{property: 'Fecha de creación', value: this.data.client.createdAt},
|
||||
{property: 'Creado por', value: this.data.client.createdBy}
|
||||
];
|
||||
|
||||
networkData = [
|
||||
{property: 'Menú', value: this.data.client.menu ? this.data.client.menu.description : ''},
|
||||
{property: 'Perfil hardware', value: this.data.client.hardwareProfile ? this.data.client.hardwareProfile.description : ''},
|
||||
{property: 'OGlive', value: ''},
|
||||
{property: 'Autoexec', value: ''},
|
||||
{property: 'Repositorio', value: ''},
|
||||
{property: 'Validacion', value: ''},
|
||||
{property: 'Página login', value: ''},
|
||||
{property: 'Página validacion', value: ''},
|
||||
{property: 'Fecha de creación', value: this.data.client.createdAt},
|
||||
{property: 'Creado por', value: this.data.client.createdBy}
|
||||
];
|
||||
|
||||
classroomData = [
|
||||
{property: 'Url servidor proxy', value: this.data.client.organizationalUnit.networkSettings ? this.data.client.organizationalUnit.networkSettings.proxy : ''},
|
||||
{property: 'IP DNS', value: this.data.client.organizationalUnit.networkSettings ? this.data.client.organizationalUnit.networkSettings.dns : ''},
|
||||
{property: 'Máscara de red', value: this.data.client.organizationalUnit.networkSettings ? this.data.client.organizationalUnit.networkSettings.netmask : ''},
|
||||
{property: 'Router', value: this.data.client.organizationalUnit.networkSettings ? this.data.client.organizationalUnit.networkSettings.router : ''},
|
||||
{property: 'NTP', value: this.data.client.organizationalUnit.networkSettings ? this.data.client.organizationalUnit.networkSettings.ntp : ''},
|
||||
{property: 'Modo p2p', value: this.data.client.organizationalUnit.networkSettings ? this.data.client.organizationalUnit.networkSettings.p2pMode : ''},
|
||||
{property: 'Tiempo p2p', value: this.data.client.organizationalUnit.networkSettings ? this.data.client.organizationalUnit.networkSettings.p2pTime : ''},
|
||||
{property: 'IP multicast', value: this.data.client.organizationalUnit.networkSettings ? this.data.client.organizationalUnit.networkSettings.mcastIp : ''},
|
||||
{property: 'Modo multicast', value: this.data.client.organizationalUnit.networkSettings ? this.data.client.organizationalUnit.networkSettings.mcastMode : ''},
|
||||
{property: 'Puerto multicast', value: this.data.client.organizationalUnit.networkSettings ? this.data.client.organizationalUnit.networkSettings.mcastPort : ''},
|
||||
{property: 'Velocidad multicast', value: this.data.client.organizationalUnit.networkSettings ? this.data.client.organizationalUnit.networkSettings.mcastSpeed : ''},
|
||||
{property: 'Perfil hardware', value: this.data.client.organizationalUnit.networkSettings && this.data.client.organizationalUnit.networkSettings.hardwareProfile ? this.data.client.organizationalUnit.networkSettings.hardwareProfile.description : ''},
|
||||
{property: 'Menú', value: this.data.client.organizationalUnit.networkSettings && this.data.client.organizationalUnit.networkSettings.menu ? this.data.client.organizationalUnit.networkSettings.menu.description : ''}
|
||||
];
|
||||
|
||||
constructor(
|
||||
private dialogRef: MatDialogRef<ClientViewComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any // Inject data for edit mode
|
||||
) {
|
||||
}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
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: 50px;
|
||||
}
|
||||
|
||||
.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-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;
|
||||
height: 90vh;
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<div class="create-client-container">
|
||||
<h1 mat-dialog-title>Añadir Cliente</h1>
|
||||
<div class="mat-dialog-content" [ngClass]="{'loading': loading}">
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
<form [formGroup]="clientForm" class="client-form" *ngIf="!loading">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-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>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Nombre</mat-label>
|
||||
<input matInput formControlName="name" >
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Número de Serie</mat-label>
|
||||
<input matInput formControlName="serialNumber" >
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Interfaz de red</mat-label>
|
||||
<mat-select formControlName="netiface" >
|
||||
<mat-option *ngFor="let type of netifaceTypes" value="{{ type.value }}">
|
||||
{{ type.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Controlador de red</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>MAC</mat-label>
|
||||
<mat-hint>Ejemplo: 00:11:22:33:44:55</mat-hint>
|
||||
<input matInput formControlName="mac" >
|
||||
<mat-error>Formato de MAC inválido. Ejemplo válido: 00:11:22:33:44:55</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Dirección IP</mat-label>
|
||||
<mat-hint>Ejemplo: 127.0.0.1</mat-hint>
|
||||
<input matInput formControlName="ip">
|
||||
<mat-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-label>Menú URL</mat-label>
|
||||
<input matInput formControlName="menu" type="url">
|
||||
<mat-error>Formato de URL inválido.</mat-error>
|
||||
</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 inválido.</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onNoClick()">Cancelar</button>
|
||||
<button mat-button [disabled]="!clientForm.valid" (click)="onSubmit()">Añadir</button>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CreateClientComponent } from './create-client.component';
|
||||
|
||||
describe('CreateClientComponent', () => {
|
||||
let component: CreateClientComponent;
|
||||
let fixture: ComponentFixture<CreateClientComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [CreateClientComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CreateClientComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,114 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
|
||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {DataService} from "../../data.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-client',
|
||||
templateUrl: './create-client.component.html',
|
||||
styleUrls: ['./create-client.component.css']
|
||||
})
|
||||
export class CreateClientComponent implements OnInit {
|
||||
clientForm!: FormGroup;
|
||||
parentUnits: any[] = []; // Array to store parent units fetched from API
|
||||
hardwareProfiles: any[] = []; // Array to store hardware profiles fetched from API
|
||||
private errorForm: boolean = false;
|
||||
protected netifaceTypes = [
|
||||
{"name": 'Eth0', "value": "eth0"},
|
||||
{"name": 'Eth1', "value": "eth1"},
|
||||
{"name": 'Eth2', "value": "eth2"},
|
||||
];
|
||||
protected netDriverTypes = [
|
||||
{"name": 'Generic', "value": "generic"},
|
||||
];
|
||||
loading:boolean = false
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private dialogRef: MatDialogRef<CreateClientComponent>,
|
||||
private http: HttpClient,
|
||||
private snackBar: MatSnackBar,
|
||||
private toastService: ToastrService,
|
||||
private dataService: DataService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any // Inject data for edit mode
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log(this.data);
|
||||
this.loadParentUnits(); // Load parent units when component initializes
|
||||
this.loadHardwareProfiles();
|
||||
this.clientForm = this.fb.group({
|
||||
organizationalUnit: [this.data.organizationalUnit ? this.data.organizationalUnit['@id'] : null, Validators.required],
|
||||
name: ['', Validators.required],
|
||||
serialNumber: ['', Validators.required],
|
||||
netiface: null,
|
||||
netDriver: null,
|
||||
mac: ['', Validators.required],
|
||||
ip: ['', Validators.required],
|
||||
menu: [this.data.organizationalUnit && this.data.organizationalUnit.networkSettings && this.data.organizationalUnit.networkSettings.menu ? this.data.organizationalUnit.networkSettings.menu['@id'] : null],
|
||||
hardwareProfile: [this.data.organizationalUnit && this.data.organizationalUnit.networkSettings && this.data.organizationalUnit.networkSettings.hardwareProfile ? this.data.organizationalUnit.networkSettings.hardwareProfile['@id'] : null],
|
||||
});
|
||||
}
|
||||
|
||||
loadHardwareProfiles(): void {
|
||||
this.dataService.getHardwareProfiles().subscribe(
|
||||
(data: any[]) => {
|
||||
this.hardwareProfiles = data;
|
||||
this.loading = false
|
||||
},
|
||||
(error: any) => {
|
||||
console.error('Error fetching hardware profiles', error);
|
||||
this.loading = false
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadParentUnits() {
|
||||
this.loading = true
|
||||
const url = 'http://127.0.0.1:8080/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
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.clientForm.valid) {
|
||||
this.errorForm = false
|
||||
const formData = this.clientForm.value;
|
||||
this.http.post('http://127.0.0.1:8080/clients', formData).subscribe(
|
||||
response => {
|
||||
this.dialogRef.close(response);
|
||||
this.openSnackBar(false, 'Cliente creado exitosamente');
|
||||
},
|
||||
error => {
|
||||
console.error('Error during POST:', error);
|
||||
this.errorForm = true
|
||||
this.openSnackBar(true, 'Error al crear el cliente: ' + error.error['hydra:description']);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
openSnackBar(isError: boolean, message: string) {
|
||||
if (isError) {
|
||||
this.toastService.error(' Error al crear el cliente: ' + message, 'Error');
|
||||
} else
|
||||
this.toastService.success(message, 'Éxito');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
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: 50px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mat-dialog-content.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.client-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<h1 mat-dialog-title>Editar Cliente</h1>
|
||||
<div class="mat-dialog-content" [ngClass]="{'loading': loading}">
|
||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||
<form [formGroup]="clientForm" class="client-form" *ngIf="!loading">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Padre</mat-label>
|
||||
<mat-select formControlName="organizationalUnit">
|
||||
<mat-option *ngFor="let unit of parentUnits" [value]="unit['@id']">{{ unit.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Nombre</mat-label>
|
||||
<input matInput formControlName="name">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Número de Serie</mat-label>
|
||||
<input matInput formControlName="serialNumber">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Interfaz de red</mat-label>
|
||||
<mat-select formControlName="netiface">
|
||||
<mat-option *ngFor="let type of netifaceTypes" [value]="type.value">
|
||||
{{ type.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Controlador de red</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>MAC</mat-label>
|
||||
<input matInput formControlName="mac">
|
||||
<mat-error>Formato de MAC inválido. Ejemplo válido: 00:11:22:33:44:55</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Dirección IP</mat-label>
|
||||
<input matInput formControlName="ip">
|
||||
<mat-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-label>Menú URL</mat-label>
|
||||
<input matInput formControlName="menu" type="url">
|
||||
<mat-error>Formato de URL inválido.</mat-error>
|
||||
</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 inválido.</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onNoClick()">Cancelar</button>
|
||||
<button mat-button [disabled]="!clientForm.valid" (click)="onSubmit()">{{ !isEditMode ? 'Añadir' : 'Guardar' }}</button>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EditClientComponent } from './edit-client.component';
|
||||
|
||||
describe('EditClientComponent', () => {
|
||||
let component: EditClientComponent;
|
||||
let fixture: ComponentFixture<EditClientComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [EditClientComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EditClientComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,156 @@
|
|||
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
||||
import {Component, Inject} from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
|
||||
import { CreateClientComponent } from '../create-client/create-client.component';
|
||||
import {DataService} from "../../data.service";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-client',
|
||||
templateUrl: './edit-client.component.html',
|
||||
styleUrl: './edit-client.component.css'
|
||||
})
|
||||
export class EditClientComponent {
|
||||
clientForm!: FormGroup;
|
||||
parentUnits: any[] = []; // Array to store parent units fetched from API
|
||||
hardwareProfiles: any[] = []; // Array to store hardware profiles fetched from API
|
||||
isEditMode: boolean; // Flag to check if it's edit mode
|
||||
protected netifaceTypes = [
|
||||
{"name": 'Eth0', "value": "eth0"},
|
||||
{"name": 'Eth1', "value": "eth1"},
|
||||
{"name": 'Eth2', "value": "eth2"},
|
||||
];
|
||||
protected netDriverTypes = [
|
||||
{"name": 'Generic', "value": "generic"},
|
||||
];
|
||||
loading:boolean = false
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private dialogRef: MatDialogRef<CreateClientComponent>,
|
||||
private http: HttpClient,
|
||||
private dataService: DataService,
|
||||
private toastService: ToastrService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any // Inject data for edit mode
|
||||
) {
|
||||
this.isEditMode = !!data?.uuid; // Check if uuid is passed to determine edit mode
|
||||
if (this.isEditMode) {
|
||||
this.loadData(data.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadParentUnits(); // Load parent units when component initializes
|
||||
this.loadHardwareProfiles(); // Load hardware profiles when component initializes
|
||||
this.clientForm = this.fb.group({
|
||||
organizationalUnit: [null,Validators.required],
|
||||
name: ['', Validators.required],
|
||||
serialNumber: null,
|
||||
netiface: null,
|
||||
netDriver: null,
|
||||
mac: null,
|
||||
ip: null,
|
||||
menu: null,
|
||||
hardwareProfile: null,
|
||||
});
|
||||
}
|
||||
|
||||
loadParentUnits() {
|
||||
const url = 'http://127.0.0.1:8080/organizational-units?page=1&itemsPerPage=10000';
|
||||
|
||||
this.http.get<any>(url).subscribe(
|
||||
response => {
|
||||
this.parentUnits = response['hydra:member'];
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching parent units:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadHardwareProfiles(): void {
|
||||
this.dataService.getHardwareProfiles().subscribe(
|
||||
(data: any[]) => {
|
||||
this.hardwareProfiles = data;
|
||||
},
|
||||
(error: any) => {
|
||||
console.error('Error fetching hardware profiles', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadData(uuid: string) {
|
||||
this.loading = true
|
||||
const url = `http://127.0.0.1:8080/clients/${uuid}`;
|
||||
|
||||
this.http.get<any>(url).subscribe(
|
||||
data => {
|
||||
this.clientForm.patchValue({
|
||||
name: data.name,
|
||||
ip: data.ip,
|
||||
mac: data.mac,
|
||||
netiface: data.netiface,
|
||||
netDriver: data.netDriver,
|
||||
serialNumber: data.serialNumber,
|
||||
hardwareProfile: data.hardwareProfile ? data.hardwareProfile['@id'] : null,
|
||||
organizationalUnit: data.organizationalUnit ? data.organizationalUnit['@id'] : null
|
||||
});
|
||||
this.loading = false
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
console.log('Form data:', this.clientForm.value);
|
||||
if (this.clientForm.valid) {
|
||||
const formData = this.clientForm.value;
|
||||
|
||||
if (this.isEditMode) {
|
||||
// Edit mode: Send PUT request to update the unit
|
||||
const putUrl = `http://127.0.0.1:8080/clients/${this.data.uuid}`;
|
||||
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
|
||||
|
||||
this.http.patch<any>(putUrl, formData, { headers }).subscribe(
|
||||
response => {
|
||||
console.log('PUT successful:', response);
|
||||
this.dialogRef.close();
|
||||
this.openSnackBar(false, 'Cliente actualizado exitosamente');
|
||||
|
||||
},
|
||||
error => {
|
||||
console.error('Error al realizar PUT:', error);
|
||||
this.openSnackBar(true, 'Error al actualizar el cliente: ' + error.error['hydra:description']);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Create mode: Send POST request to create a new unit
|
||||
const postUrl = 'http://127.0.0.1:8080/clients';
|
||||
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
|
||||
|
||||
this.http.post<any>(postUrl, formData, { headers }).subscribe(
|
||||
response => {
|
||||
console.log('POST successful:', response);
|
||||
this.dialogRef.close();
|
||||
this.openSnackBar(false, 'Cliente creado exitosamente');
|
||||
},
|
||||
error => {
|
||||
console.error('Error al realizar POST:', error);
|
||||
this.openSnackBar(true, 'Error al crear el cliente: ' + error.error['hydra:description']);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
openSnackBar(isError: boolean, message: string) {
|
||||
if (isError) {
|
||||
this.toastService.error(' Error al crear el cliente: ' + message, 'Error');
|
||||
} else
|
||||
this.toastService.success(message, 'Éxito');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { UnidadOrganizativa } from './model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DataService {
|
||||
|
||||
private apiUrl = 'http://127.0.0.1:8080/organizational-units?page=1&itemsPerPage=1000';
|
||||
private clientsUrl = 'http://127.0.0.1:8080/clients?page=1&itemsPerPage=30';
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
getOrganizationalUnits(search: string = ''): Observable<UnidadOrganizativa[]> {
|
||||
let url = `${this.apiUrl}&type=organizational-unit`;
|
||||
if (search) {
|
||||
url += `&name=${encodeURIComponent(search)}`;
|
||||
}
|
||||
|
||||
return this.http.get<any>(url).pipe(
|
||||
map(response => {
|
||||
if (response['hydra:member'] && Array.isArray(response['hydra:member'])) {
|
||||
return response['hydra:member'];
|
||||
} else {
|
||||
throw new Error('Unexpected response format');
|
||||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching unidades organizativas', error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getChildren(id: string): Observable<any[]> {
|
||||
return this.http.get<any>(`${this.apiUrl}&parent.id=${id}`).pipe(
|
||||
map(response => {
|
||||
if (response['hydra:member'] && Array.isArray(response['hydra:member'])) {
|
||||
return response['hydra:member']
|
||||
} else {
|
||||
throw new Error('Unexpected response format');
|
||||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching children', error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getClients(id: string): Observable<any[]> {
|
||||
return this.http.get<any>(`${this.clientsUrl}&organizationalUnit.id=${id}`).pipe(
|
||||
map(response => {
|
||||
if (response['hydra:member'] && Array.isArray(response['hydra:member'])) {
|
||||
return response['hydra:member']
|
||||
} else {
|
||||
throw new Error('Unexpected response format');
|
||||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching clients', error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getHardwareProfiles(): Observable<any[]> {
|
||||
const url = 'http://127.0.0.1:8080/hardware-profiles';
|
||||
return this.http.get<any>(url).pipe(
|
||||
map(response => {
|
||||
if (response['hydra:member'] && Array.isArray(response['hydra:member'])) {
|
||||
return response['hydra:member']
|
||||
} else {
|
||||
throw new Error('Unexpected response format');
|
||||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error fetching clients', error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
deleteElement(uuid: string, type: string): Observable<void> {
|
||||
const url = type === 'client'
|
||||
? `http://127.0.0.1:8080/clients/${uuid}`
|
||||
: `http://127.0.0.1:8080/organizational-units/${uuid}`;
|
||||
return this.http.delete<void>(url).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error deleting element', error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
changeParent(uuid: string): Observable<void> {
|
||||
const url = `http://127.0.0.1:8080/organizational-units/${uuid}/change-parent`;
|
||||
// @ts-ignore
|
||||
return this.http.post<void>(url).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error deleting element', error);
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DeleteGroupsModalComponent } from './delete-groups-modal.component';
|
||||
|
||||
describe('DeleteGroupsModalComponent', () => {
|
||||
let component: DeleteGroupsModalComponent;
|
||||
let fixture: ComponentFixture<DeleteGroupsModalComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [DeleteGroupsModalComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DeleteGroupsModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-delete-confirm-dialog',
|
||||
template: `
|
||||
<h1 mat-dialog-title>Eliminar</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>¿Quiere borrar los clientes situados en {{data.name}} o quiere resituarlos en el nivel superior?</p>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="deleteClick()">Borrar todos los clientes</button>
|
||||
<button mat-button (click)="changeClick()">Resituar </button>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class DeleteGroupsModalComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<DeleteGroupsModalComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { name: string }
|
||||
) {}
|
||||
|
||||
deleteClick(): void {
|
||||
this.dialogRef.close('delete');
|
||||
}
|
||||
|
||||
changeClick(): void {
|
||||
this.dialogRef.close('change');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DeleteModalComponent } from './delete-modal.component';
|
||||
|
||||
describe('DeleteModalComponent', () => {
|
||||
let component: DeleteModalComponent;
|
||||
let fixture: ComponentFixture<DeleteModalComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [DeleteModalComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DeleteModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-delete-confirm-dialog',
|
||||
template: `
|
||||
<h1 mat-dialog-title>Eliminar</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>¿Estás seguro que deseas eliminar {{data.name}}?</p>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onNoClick()">Cancelar</button>
|
||||
<button mat-button (click)="onYesClick()">Eliminar</button>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class DeleteModalComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<DeleteModalComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { name: string }
|
||||
) {}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
onYesClick(): void {
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
.groupLists-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.search-container mat-form-field {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.card {
|
||||
flex-grow: 1;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.unidad-card, .elements-card {
|
||||
flex: 1 1 45%;
|
||||
background-color: #fafafa;
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.element-content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.details-card, .classroom-view {
|
||||
flex: 1 1 25%;
|
||||
}
|
||||
|
||||
mat-card-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.title-with-breadcrumb {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
mat-card-subtitle {
|
||||
font-size: 0.875rem;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
|
||||
mat-card-subtitle a {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
color: #929292;
|
||||
}
|
||||
|
||||
mat-card-subtitle a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.groups-button-row {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.item-content mat-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.clickable-item:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected-item {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.actions mat-icon {
|
||||
cursor: pointer;
|
||||
margin-left: 16px;
|
||||
color: #757575;
|
||||
}
|
||||
|
||||
.actions mat-icon:hover {
|
||||
color: #212121;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
mat-spinner {
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
<div class="header-container">
|
||||
<h2 class="title">Administrar grupos</h2>
|
||||
<div class="groups-button-row">
|
||||
<button mat-flat-button color="primary" (click)="addOU($event)">Nueva Unidad Organizativa</button>
|
||||
<button mat-flat-button color="primary" (click)="addClient($event)">Nuevo Cliente</button>
|
||||
<button mat-raised-button (click)="openBottomSheet()">Leyenda</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-container">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Búsqueda</mat-label>
|
||||
<input matInput placeholder="Búsqueda" [(ngModel)]="searchTerm" (keyup.enter)="search()">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
<mat-hint>Pulsar 'enter' para buscar entre las unidades organizativas</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="groupLists-container">
|
||||
<mat-card class="card unidad-card">
|
||||
<mat-card-title>Unidad organizativa</mat-card-title>
|
||||
<mat-card-content>
|
||||
<mat-spinner *ngIf="loading"></mat-spinner>
|
||||
<mat-list *ngIf="!loading">
|
||||
<mat-list-item *ngFor="let unidad of organizationalUnits"
|
||||
[ngClass]="{'selected-item': unidad === selectedUnidad, 'clickable-item': true}" (click)="onSelectUnidad(unidad)">
|
||||
<div class="item-content">
|
||||
<mat-icon>apartment</mat-icon>
|
||||
{{ unidad.name }}
|
||||
<span class="actions">
|
||||
<mat-icon mat-button [matMenuTriggerFor]="menu" (click)="$event.stopPropagation()">menu</mat-icon>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item (click)="onTreeClick($event, unidad)">
|
||||
<mat-icon
|
||||
class="edit-icon"
|
||||
#tooltip="matTooltip"
|
||||
matTooltip="Visualizar en forma de arbol"
|
||||
matTooltipHideDelay="0">account_tree
|
||||
</mat-icon>
|
||||
Ver organigrama
|
||||
</button>
|
||||
<button mat-menu-item (click)="onEditClick($event, unidad.type, unidad.uuid)">
|
||||
<mat-icon
|
||||
class="edit-icon"
|
||||
#tooltip="matTooltip"
|
||||
matTooltip="Editar unidad organizativa"
|
||||
matTooltipHideDelay="0">edit
|
||||
</mat-icon>
|
||||
Editar
|
||||
</button>
|
||||
<button mat-menu-item (click)="onShowClick($event, unidad)">
|
||||
<mat-icon
|
||||
class="edit-icon"
|
||||
#tooltip="matTooltip"
|
||||
matTooltip="Visualizar unidad organizativa"
|
||||
matTooltipHideDelay="0">visibility
|
||||
</mat-icon>
|
||||
Visualizar datos
|
||||
</button>
|
||||
<button mat-menu-item (click)="addOU($event, unidad)">
|
||||
<mat-icon
|
||||
class="edit-icon"
|
||||
#tooltip="matTooltip"
|
||||
matTooltip="Crear unidad organizativa interna"
|
||||
matTooltipHideDelay="0">add_home_work
|
||||
</mat-icon>
|
||||
Añadir unidad organizativa
|
||||
</button>
|
||||
<button mat-menu-item (click)="addClient($event, unidad)">
|
||||
<mat-icon
|
||||
class="edit-icon"
|
||||
#tooltip="matTooltip"
|
||||
matTooltip="Crear cliente en esta unidad organizativa"
|
||||
matTooltipHideDelay="0">devices
|
||||
</mat-icon>
|
||||
Crear cliente
|
||||
</button>
|
||||
</mat-menu>
|
||||
</span>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<mat-card class="card elements-card">
|
||||
<mat-card-title>
|
||||
<div class="title-with-breadcrumb">
|
||||
<span>Elementos internos</span>
|
||||
<mat-card-subtitle>
|
||||
<ng-container *ngFor="let crumb of breadcrumb; let i = index">
|
||||
<a (click)="navigateToBreadcrumb(i)">{{ crumb }}</a>
|
||||
<span *ngIf="i < breadcrumb.length - 1"> > </span>
|
||||
</ng-container>
|
||||
</mat-card-subtitle>
|
||||
</div>
|
||||
</mat-card-title>
|
||||
<mat-card-content class="element-content">
|
||||
<mat-spinner *ngIf="loadingChildren"></mat-spinner>
|
||||
<mat-list *ngIf="!loadingChildren">
|
||||
<div *ngIf="children.length === 0" class="empty-list">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>No hay elementos internos</span>
|
||||
</div>
|
||||
<mat-list-item *ngFor="let child of children" [ngClass]="{'selected-item': child === selectedUnidad, 'clickable-item': true}" (click)="onSelectChild(child)">
|
||||
<div class="item-content">
|
||||
<mat-icon [ngSwitch]="child.type">
|
||||
<ng-container *ngSwitchCase="'organizational-unit'">apartment</ng-container>
|
||||
<ng-container *ngSwitchCase="'classrooms-group'">meeting_room</ng-container>
|
||||
<ng-container *ngSwitchCase="'classroom'">school</ng-container>
|
||||
<ng-container *ngSwitchCase="'client'">computer</ng-container>
|
||||
<ng-container *ngSwitchCase="'clients-group'">lan</ng-container>
|
||||
<ng-container *ngSwitchDefault>help_outline</ng-container>
|
||||
</mat-icon>
|
||||
{{child.name}}
|
||||
<span class="actions">
|
||||
<mat-icon mat-button [matMenuTriggerFor]="menu" (click)="$event.stopPropagation()">menu</mat-icon>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item (click)="onEditClick($event, child.type, child.uuid)">
|
||||
<mat-icon class="edit-icon" #tooltip="matTooltip" matTooltip="Editar elemento" matTooltipHideDelay="0">edit</mat-icon>
|
||||
Editar
|
||||
</button>
|
||||
<button *ngIf="child.type !== 'client'" mat-menu-item (click)="onShowClick($event, child)">
|
||||
<mat-icon class="edit-icon" #tooltip="matTooltip" matTooltip="Visualizar unidad organizativa" matTooltipHideDelay="0">visibility</mat-icon>
|
||||
Visualizar datos
|
||||
</button>
|
||||
<button *ngIf="child.type !== 'client'" mat-menu-item (click)="addOU($event, child)">
|
||||
<mat-icon class="edit-icon" #tooltip="matTooltip" matTooltip="Crear unidad organizativa interna" matTooltipHideDelay="0">add_home_work</mat-icon>
|
||||
Añadir unidad organizativa
|
||||
</button>
|
||||
<button *ngIf="child.type !== 'client'" mat-menu-item (click)="addClient($event, child)">
|
||||
<mat-icon class="edit-icon" #tooltip="matTooltip" matTooltip="Crear cliente en esta unidad organizativa" matTooltipHideDelay="0">devices</mat-icon>
|
||||
Crear cliente
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDeleteClick($event, child.uuid, child.name, child.type)">
|
||||
<mat-icon class="delete-icon" #tooltip="matTooltip" matTooltip="Borrar elemento" matTooltipHideDelay="0">delete</mat-icon>
|
||||
Borrar elemento
|
||||
</button>
|
||||
</mat-menu>
|
||||
</span>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<app-classroom-view class="card classroom-view" [clients]="clientsData" [pcInTable]="5"></app-classroom-view>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GroupsComponent } from './groups.component';
|
||||
|
||||
describe('GroupsComponent', () => {
|
||||
let component: GroupsComponent;
|
||||
let fixture: ComponentFixture<GroupsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [GroupsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,254 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { DataService } from './data.service';
|
||||
import { ClientCollection, UnidadOrganizativa } from './model';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { CreateOrganizationalUnitComponent } from './organizational-units/create-organizational-unit/create-organizational-unit.component';
|
||||
import { DeleteModalComponent } from './delete-modal/delete-modal.component';
|
||||
import { CreateClientComponent } from './clients/create-client/create-client.component';
|
||||
import { EditOrganizationalUnitComponent } from './organizational-units/edit-organizational-unit/edit-organizational-unit.component';
|
||||
import { EditClientComponent } from './clients/edit-client/edit-client.component';
|
||||
import { DeleteGroupsModalComponent } from "./delete-groups-modal/delete-groups-modal.component";
|
||||
import { ShowOrganizationalUnitComponent} from "./organizational-units/show-organizational-unit/show-organizational-unit.component";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {TreeViewComponent} from "./tree-view/tree-view.component";
|
||||
import {MatBottomSheet} from "@angular/material/bottom-sheet";
|
||||
import {LegendComponent} from "./legend/legend.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-groups',
|
||||
templateUrl: './groups.component.html',
|
||||
styleUrls: ['./groups.component.css']
|
||||
})
|
||||
export class GroupsComponent implements OnInit {
|
||||
organizationalUnits: UnidadOrganizativa[] = [];
|
||||
selectedUnidad: UnidadOrganizativa | null = null;
|
||||
selectedDetail: any | null = null; // Nueva variable para el detalle del elemento seleccionado
|
||||
children: any[] = [];
|
||||
breadcrumb: string[] = [];
|
||||
clientsData: any[] = []; // Nueva variable para almacenar los datos de clients
|
||||
breadcrumbData: any[] = []; // Almacenar datos de breadcrumb para navegar
|
||||
loading:boolean = false;
|
||||
loadingChildren:boolean = false;
|
||||
searchTerm: string = '';
|
||||
constructor(
|
||||
private dataService: DataService,
|
||||
public dialog: MatDialog,
|
||||
private toastService: ToastrService,
|
||||
private _bottomSheet: MatBottomSheet
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.search();
|
||||
}
|
||||
|
||||
search(): void {
|
||||
this.loading = true;
|
||||
this.dataService.getOrganizationalUnits(this.searchTerm).subscribe(
|
||||
data => {
|
||||
this.organizationalUnits = data;
|
||||
this.loading = false; // Desactivar el spinner después de obtener los datos
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching unidades organizativas', error);
|
||||
this.loading = false; // Desactivar el spinner en caso de error
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
onSelectUnidad(unidad: UnidadOrganizativa): void {
|
||||
this.selectedUnidad = unidad;
|
||||
this.selectedDetail = unidad; // Mostrar detalles de la unidad seleccionada
|
||||
this.breadcrumb = [unidad.name];
|
||||
this.breadcrumbData = [unidad];
|
||||
this.loadChildrenAndClients(unidad.id);
|
||||
}
|
||||
|
||||
onSelectChild(child: any): void {
|
||||
this.selectedDetail = child; // Mostrar detalles del niño seleccionado
|
||||
if (child.type !== 'client' && child.uuid && child.id) {
|
||||
this.breadcrumb.push(child.name || child.name);
|
||||
this.breadcrumbData.push(child);
|
||||
this.loadChildrenAndClients(child.id);
|
||||
}
|
||||
}
|
||||
|
||||
navigateToBreadcrumb(index: number): void {
|
||||
this.breadcrumb = this.breadcrumb.slice(0, index + 1);
|
||||
const target = this.breadcrumbData[index];
|
||||
this.breadcrumbData = this.breadcrumbData.slice(0, index + 1);
|
||||
if (target.type === 'client') {
|
||||
this.selectedDetail = target;
|
||||
} else {
|
||||
this.loadChildrenAndClients(target.id);
|
||||
}
|
||||
}
|
||||
|
||||
loadChildrenAndClients(id: string): void {
|
||||
this.loadingChildren = true
|
||||
this.dataService.getChildren(id).subscribe(
|
||||
childrenData => {
|
||||
console.log('Children data:', childrenData);
|
||||
this.dataService.getClients(id).subscribe(
|
||||
clientsData => {
|
||||
this.clientsData = clientsData; // Almacenar clientsData para pasarlo al componente hijo
|
||||
const newChildren = [...childrenData, ...clientsData];
|
||||
|
||||
if (newChildren.length > 0) {
|
||||
this.children = newChildren;
|
||||
} else {
|
||||
this.children = []; // Limpiar card2 cuando no hay elementos
|
||||
}
|
||||
this.loadingChildren = false
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching clients', error);
|
||||
this.clientsData = []; // Limpiar clientsData en caso de error
|
||||
this.children = []; // Limpiar card2 en caso de error
|
||||
this.loadingChildren = false
|
||||
}
|
||||
);
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching children', error);
|
||||
this.children = []; // Limpiar card2 en caso de error
|
||||
this.loadingChildren = false
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
addOU(event: MouseEvent, parent:any = null): void {
|
||||
event.stopPropagation();
|
||||
const dialogRef = this.dialog.open(CreateOrganizationalUnitComponent, { data: { parent }, width: '700px'});
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
this.dataService.getOrganizationalUnits().subscribe(
|
||||
data => {
|
||||
this.organizationalUnits = data
|
||||
this.loadChildrenAndClients(parent.id);
|
||||
},
|
||||
error => console.error('Error fetching unidades organizativas', error)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
addClient(event: MouseEvent, organizationalUnit:any = null): void {
|
||||
event.stopPropagation();
|
||||
|
||||
const dialogRef = this.dialog.open(CreateClientComponent, { data: { organizationalUnit }, width: '700px'});
|
||||
|
||||
// Subscribirse al evento unitAdded del componente de creación después de cerrar el diálogo
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
this.dataService.getOrganizationalUnits().subscribe(
|
||||
data => {
|
||||
this.organizationalUnits = data
|
||||
this.loadChildrenAndClients(organizationalUnit.id);
|
||||
},
|
||||
error => console.error('Error fetching unidades organizativas', error)
|
||||
);
|
||||
});
|
||||
}
|
||||
onDeleteClick(event: MouseEvent, uuid: string, name: string, type: string): void {
|
||||
event.stopPropagation();
|
||||
if (type === 'client') {
|
||||
const dialogRef = this.dialog.open(DeleteModalComponent, {
|
||||
width: '400px',
|
||||
data: { name }
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.dataService.deleteElement(uuid, type).subscribe(
|
||||
() => {
|
||||
this.loadChildrenAndClients(this.selectedUnidad?.id || '');
|
||||
this.dataService.getOrganizationalUnits().subscribe(
|
||||
data => this.organizationalUnits = data,
|
||||
error => console.error('Error fetching unidades organizativas', error)
|
||||
);
|
||||
this.openSnackBar(false, 'Entidad eliminada exitosamente')
|
||||
},
|
||||
error => {
|
||||
console.error('Error deleting element', error)
|
||||
this.openSnackBar(true, error.error['hydra:description'])
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const dialogDeleteGroupRef = this.dialog.open(DeleteGroupsModalComponent, {
|
||||
width: '400px',
|
||||
data: { name }
|
||||
});
|
||||
|
||||
dialogDeleteGroupRef.afterClosed().subscribe(result => {
|
||||
if (result && result === 'delete') {
|
||||
this.dataService.deleteElement(uuid, type).subscribe(
|
||||
() => {
|
||||
this.loadChildrenAndClients(this.selectedUnidad?.id || '');
|
||||
this.dataService.getOrganizationalUnits().subscribe(
|
||||
data => this.organizationalUnits = data,
|
||||
error => console.error('Error fetching unidades organizativas', error)
|
||||
);
|
||||
this.openSnackBar(false, 'Entidad eliminada exitosamente')
|
||||
},
|
||||
error => {
|
||||
console.error('Error deleting element', error)
|
||||
this.openSnackBar(true, error.error['hydra:description'])
|
||||
}
|
||||
);
|
||||
} else if (result && result === 'change') {
|
||||
this.dataService.changeParent(uuid).subscribe(
|
||||
() => {
|
||||
this.loadChildrenAndClients(this.selectedUnidad?.id || '');
|
||||
this.dataService.getOrganizationalUnits().subscribe(
|
||||
data => this.organizationalUnits = data,
|
||||
error => console.error('Error fetching unidades organizativas', error)
|
||||
);
|
||||
this.openSnackBar(false, 'Entidad eliminada exitosamente')
|
||||
},
|
||||
error => {
|
||||
console.error('Error deleting element', error)
|
||||
this.openSnackBar(true, error.error['hydra:description'])
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onEditClick(event: MouseEvent, type: any, uuid: string): void {
|
||||
event.stopPropagation();
|
||||
console.log('Tipo del elemento a editar:', type);
|
||||
console.log('UUID del elemento a editar:', uuid);
|
||||
if (type != "client") {
|
||||
const dialogRef = this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '700px'});
|
||||
} else {
|
||||
console.log('Editar cliente');
|
||||
const dialogRef = this.dialog.open(EditClientComponent, { data: { uuid }, width: '700px' } );
|
||||
}
|
||||
}
|
||||
|
||||
onShowClick(event: MouseEvent, data: any): void {
|
||||
event.stopPropagation();
|
||||
if (data.type != "client") {
|
||||
const dialogRef = this.dialog.open(ShowOrganizationalUnitComponent, { data: { data }, width: '700px'});
|
||||
}
|
||||
}
|
||||
|
||||
onTreeClick(event: MouseEvent, data: any): void {
|
||||
event.stopPropagation();
|
||||
if (data.type != "client") {
|
||||
const dialogRef = this.dialog.open(TreeViewComponent, { data: { data }, width: '800px'});
|
||||
}
|
||||
}
|
||||
|
||||
openSnackBar(isError: boolean, message: string) {
|
||||
if (isError) {
|
||||
this.toastService.error(' Error al eliminar la entidad: ' + message, 'Error');
|
||||
} else
|
||||
this.toastService.success(message, 'Éxito');
|
||||
}
|
||||
|
||||
openBottomSheet(): void {
|
||||
this._bottomSheet.open(LegendComponent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<mat-list>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>apartment</mat-icon>
|
||||
<div matListItemTitle>Unidad organizativa</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>meeting_room</mat-icon>
|
||||
<div matListItemTitle>Grupos de aula</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>school</mat-icon>
|
||||
<div matListItemTitle>Aula</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>lan</mat-icon>
|
||||
<div matListItemTitle>Grupos de clientes</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<mat-icon matListItemIcon>computer</mat-icon>
|
||||
<div matListItemTitle>Cliente</div>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LegendComponent } from './legend.component';
|
||||
|
||||
describe('LegendComponent', () => {
|
||||
let component: LegendComponent;
|
||||
let fixture: ComponentFixture<LegendComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [LegendComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LegendComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
import { Component } from '@angular/core';
|
||||
import {MatBottomSheetRef} from "@angular/material/bottom-sheet";
|
||||
|
||||
@Component({
|
||||
selector: 'app-legend',
|
||||
templateUrl: './legend.component.html',
|
||||
styleUrl: './legend.component.css'
|
||||
})
|
||||
export class LegendComponent {
|
||||
constructor(private _bottomSheetRef: MatBottomSheetRef<LegendComponent>) {}
|
||||
|
||||
openLink(event: MouseEvent): void {
|
||||
this._bottomSheetRef.dismiss();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// model.ts
|
||||
export interface Cliente {
|
||||
nombre: string;
|
||||
}
|
||||
|
||||
export interface Aula {
|
||||
nombre: string;
|
||||
clientes: Cliente[];
|
||||
}
|
||||
|
||||
export interface UnidadOrganizativa {
|
||||
id: string;
|
||||
name: string;
|
||||
uuid: string;
|
||||
type: string;
|
||||
parent: UnidadOrganizativa[];
|
||||
}
|
||||
|
||||
export interface OrganizationalUnit {
|
||||
"@id": string;
|
||||
"@type": string;
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export interface Client {
|
||||
"@id": string;
|
||||
"@type": string;
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
serialNumber: string;
|
||||
netiface: string;
|
||||
organizationalUnit: OrganizationalUnit;
|
||||
partitions: any[];
|
||||
createdAt: string;
|
||||
createdBy: string;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export interface ClientCollection {
|
||||
"@context": string;
|
||||
"@id": string;
|
||||
"@type": string;
|
||||
"hydra:totalItems": number;
|
||||
"hydra:member": Client[];
|
||||
"hydra:view": {
|
||||
"@id": string;
|
||||
"@type": string;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
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;
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
<h1 mat-dialog-title>Añadir Unidad Organizativa</h1>
|
||||
|
||||
<div mat-dialog-content>
|
||||
<mat-stepper orientation="vertical" [linear]="isLinear">
|
||||
<!-- Step 1: General -->
|
||||
<mat-step [stepControl]="generalFormGroup">
|
||||
<form [formGroup]="generalFormGroup">
|
||||
<ng-template matStepLabel>General</ng-template>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Tipo</mat-label>
|
||||
<mat-select formControlName="type" required>
|
||||
<mat-option *ngFor="let type of types" [value]="type">
|
||||
{{ typeTranslations[type] }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Nombre</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Unidad organizativa padre</mat-label>
|
||||
<mat-select formControlName="parent">
|
||||
<mat-option>--</mat-option>
|
||||
<mat-option *ngFor="let unit of parentUnits" [value]="unit['@id']">{{ unit.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Descripción</mat-label>
|
||||
<textarea matInput formControlName="description"></textarea>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperNext>Siguiente</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 2: Classroom Info -->
|
||||
<mat-step *ngIf="generalFormGroup.value.type === 'classroom'" [stepControl]="classroomInfoFormGroup">
|
||||
<form [formGroup]="classroomInfoFormGroup">
|
||||
<ng-template matStepLabel>Información del Aula</ng-template>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Ubicación</mat-label>
|
||||
<input matInput formControlName="location">
|
||||
</mat-form-field>
|
||||
<mat-slide-toggle formControlName="projector">Proyector</mat-slide-toggle>
|
||||
<mat-slide-toggle formControlName="board">Pizarra</mat-slide-toggle>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Aforo</mat-label>
|
||||
<input matInput formControlName="capacity" type="number">
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Atrás</button>
|
||||
<button mat-button matStepperNext>Siguiente</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 3: Información Adicional -->
|
||||
<mat-step [stepControl]="additionalInfoFormGroup">
|
||||
<form [formGroup]="additionalInfoFormGroup">
|
||||
<ng-template matStepLabel>Información Adicional</ng-template>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Comentarios</mat-label>
|
||||
<textarea matInput formControlName="comments"></textarea>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Atrás</button>
|
||||
<button mat-button matStepperNext>Siguiente</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 4: Configuración de Red -->
|
||||
<mat-step [stepControl]="networkSettingsFormGroup">
|
||||
<form [formGroup]="networkSettingsFormGroup">
|
||||
<ng-template matStepLabel>Configuración de Red</ng-template>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Url servidor Proxy</mat-label>
|
||||
<input matInput formControlName="proxy">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>IP servidor 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>IP servidor 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>IP Multicast</mat-label>
|
||||
<input matInput formControlName="mcastIp">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Velocidad Multicast</mat-label>
|
||||
<input matInput formControlName="mcastSpeed" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Puerto Multicast</mat-label>
|
||||
<input matInput formControlName="mcastPort" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Modo Multicast</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>Menú URL</mat-label>
|
||||
<input matInput formControlName="menu" type="url">
|
||||
</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 inválido.</mat-error>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Atrás</button>
|
||||
<button mat-button (click)="onSubmit()" [disabled]="!networkSettingsFormGroup.valid">Añadir</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
</mat-stepper>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onNoClick()">Cancelar</button>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CreateOrganizationalUnitComponent } from './create-organizational-unit.component';
|
||||
|
||||
describe('CreateOrganizationalUnitComponent', () => {
|
||||
let component: CreateOrganizationalUnitComponent;
|
||||
let fixture: ComponentFixture<CreateOrganizationalUnitComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [CreateOrganizationalUnitComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CreateOrganizationalUnitComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,171 @@
|
|||
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 "../../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 {
|
||||
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': 'Unidad organizativa',
|
||||
'classrooms-group': 'Grupo de aulas',
|
||||
'classroom': 'Aula',
|
||||
'clients-group': 'Grupo de clientes'
|
||||
};
|
||||
parentUnits: any[] = [];
|
||||
hardwareProfiles: any[] = [];
|
||||
protected p2pModeOptions = [
|
||||
{"name": 'Leecher', "value": "p2p-mode-leecher"},
|
||||
{"name": 'Peer', "value": "p2p-mode-peer"},
|
||||
{"name": 'Seeder', "value": "p2p-mode-seeder"},
|
||||
];
|
||||
protected multicastModeOptions = [
|
||||
{"name": 'Half duplex', "value": "half-duplex"},
|
||||
{"name": 'Full duplex', "value": "full-duplex"},
|
||||
];
|
||||
@Output() unitAdded = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
private _formBuilder: FormBuilder,
|
||||
private dialogRef: MatDialogRef<CreateOrganizationalUnitComponent>,
|
||||
private http: HttpClient,
|
||||
private toastService: ToastrService,
|
||||
private dataService: DataService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any // Inject data for edit mode
|
||||
) {
|
||||
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({
|
||||
proxy: [''],
|
||||
dns: [''],
|
||||
netmask: [''],
|
||||
router: [''],
|
||||
ntp: [''],
|
||||
p2pMode: [''],
|
||||
p2pTime: [0, Validators.min(0)],
|
||||
mcastIp: [''],
|
||||
mcastSpeed: [0, Validators.min(0)],
|
||||
mcastPort: [0, Validators.min(0)],
|
||||
mcastMode: [''],
|
||||
menu: [null],
|
||||
hardwareProfile: [null],
|
||||
validation: [false]
|
||||
});
|
||||
this.classroomInfoFormGroup = this._formBuilder.group({
|
||||
location: [''],
|
||||
projector: [false],
|
||||
board: [false],
|
||||
capacity: [0, Validators.min(0)]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadParentUnits();
|
||||
this.loadHardwareProfiles();
|
||||
}
|
||||
|
||||
loadParentUnits() {
|
||||
const url = 'http://127.0.0.1:8080/organizational-units?page=1&itemsPerPage=10000';
|
||||
|
||||
this.http.get<any>(url).subscribe(
|
||||
response => {
|
||||
this.parentUnits = response['hydra:member'];
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching parent units:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadHardwareProfiles(): void {
|
||||
this.dataService.getHardwareProfiles().subscribe(
|
||||
(data: any[]) => {
|
||||
this.hardwareProfiles = data;
|
||||
},
|
||||
(error: any) => {
|
||||
console.error('Error fetching hardware profiles', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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.generalFormGroup.valid && this.additionalInfoFormGroup.valid && this.networkSettingsFormGroup.valid && (this.generalFormGroup.value.type !== 'classroom' || this.classroomInfoFormGroup.valid)) {
|
||||
const generalFormValues = this.cleanFormValues(this.generalFormGroup);
|
||||
const additionalInfoFormValues = this.cleanFormValues(this.additionalInfoFormGroup);
|
||||
const networkSettingsFormValues = this.cleanFormValues(this.networkSettingsFormGroup);
|
||||
const classroomInfoFormValues = this.cleanFormValues(this.classroomInfoFormGroup);
|
||||
|
||||
const formData: any = {
|
||||
...generalFormValues,
|
||||
comments: additionalInfoFormValues.comments,
|
||||
networkSettings: { ...networkSettingsFormValues }
|
||||
};
|
||||
|
||||
if (this.generalFormGroup.value.type === 'classroom') {
|
||||
formData.location = classroomInfoFormValues.location;
|
||||
formData.projector = classroomInfoFormValues.projector;
|
||||
formData.board = classroomInfoFormValues.board;
|
||||
if (classroomInfoFormValues.capacity !== undefined) {
|
||||
formData.capacity = classroomInfoFormValues.capacity;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('POST data:', formData);
|
||||
const postUrl = 'http://127.0.0.1:8080/organizational-units';
|
||||
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
|
||||
|
||||
this.http.post<any>(postUrl, formData, { headers }).subscribe(
|
||||
response => {
|
||||
console.log('POST successful:', response);
|
||||
this.unitAdded.emit();
|
||||
this.dialogRef.close();
|
||||
this.openSnackBar(false, 'Cliente creado exitosamente');
|
||||
},
|
||||
error => {
|
||||
console.error('Error al realizar POST:', error);
|
||||
this.openSnackBar(true, 'Error al crear la unidad organizativa: ' + error.error['hydra:description']);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
openSnackBar(isError: boolean, message: string) {
|
||||
if (isError) {
|
||||
this.toastService.error(' Error al crear el cliente: ' + message, 'Error');
|
||||
} else
|
||||
this.toastService.success('Cliente creado exitosamente', 'Éxito');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
<h1 mat-dialog-title>Editar Unidad Organizativa</h1>
|
||||
<div mat-dialog-content>
|
||||
<mat-stepper orientation="vertical" [linear]="isLinear">
|
||||
<!-- Step 1: General -->
|
||||
<mat-step [stepControl]="generalFormGroup">
|
||||
<form [formGroup]="generalFormGroup">
|
||||
<ng-template matStepLabel>General</ng-template>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Tipo</mat-label>
|
||||
<mat-select formControlName="type" required>
|
||||
<mat-option *ngFor="let type of types" [value]="type">{{ type }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Nombre</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Padre</mat-label>
|
||||
<mat-select formControlName="parent">
|
||||
<mat-option *ngFor="let unit of parentUnits" [value]="unit['@id']">{{ unit.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Descripción</mat-label>
|
||||
<textarea matInput formControlName="description"></textarea>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperNext>Siguiente</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 2: Classroom Info -->
|
||||
<mat-step *ngIf="generalFormGroup.value.type === 'classroom'" [stepControl]="classroomInfoFormGroup">
|
||||
<form [formGroup]="classroomInfoFormGroup">
|
||||
<ng-template matStepLabel>Información del Aula</ng-template>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Ubicación</mat-label>
|
||||
<input matInput formControlName="location">
|
||||
</mat-form-field>
|
||||
<mat-slide-toggle formControlName="projector">Proyector</mat-slide-toggle>
|
||||
<mat-slide-toggle formControlName="board">Pizarra</mat-slide-toggle>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Aforo</mat-label>
|
||||
<input matInput formControlName="capacity" type="number">
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Atrás</button>
|
||||
<button mat-button matStepperNext>Siguiente</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 3: Información Adicional -->
|
||||
<mat-step [stepControl]="additionalInfoFormGroup">
|
||||
<form [formGroup]="additionalInfoFormGroup">
|
||||
<ng-template matStepLabel>Información Adicional</ng-template>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Comentarios</mat-label>
|
||||
<textarea matInput formControlName="comments"></textarea>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Atrás</button>
|
||||
<button mat-button matStepperNext>Siguiente</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 4: Configuración de Red -->
|
||||
<mat-step [stepControl]="networkSettingsFormGroup">
|
||||
<form [formGroup]="networkSettingsFormGroup">
|
||||
<ng-template matStepLabel>Configuración de Red</ng-template>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Url servidor Proxy</mat-label>
|
||||
<input matInput formControlName="proxy">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>IP servidor 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>IP servidor 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>IP Multicast</mat-label>
|
||||
<input matInput formControlName="mcastIp">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Velocidad Multicast</mat-label>
|
||||
<input matInput formControlName="mcastSpeed" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Puerto Multicast</mat-label>
|
||||
<input matInput formControlName="mcastPort" type="number">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Modo Multicast</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>Menú URL</mat-label>
|
||||
<input matInput formControlName="menu" type="url">
|
||||
</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 inválido.</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-slide-toggle formControlName="validation">Validación</mat-slide-toggle>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Atrás</button>
|
||||
<button mat-button (click)="onSubmit()" [disabled]="!networkSettingsFormGroup.valid">Añadir</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
</mat-stepper>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onNoClick()">Cancelar</button>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EditOrganizationalUnitComponent } from './edit-organizational-unit.component';
|
||||
|
||||
describe('EditOrganizationalUnitComponent', () => {
|
||||
let component: EditOrganizationalUnitComponent;
|
||||
let fixture: ComponentFixture<EditOrganizationalUnitComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [EditOrganizationalUnitComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EditOrganizationalUnitComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,219 @@
|
|||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { CreateOrganizationalUnitComponent } from '../create-organizational-unit/create-organizational-unit.component';
|
||||
import {DataService} from "../../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'
|
||||
})
|
||||
export class EditOrganizationalUnitComponent implements OnInit {
|
||||
isLinear = true;
|
||||
generalFormGroup: FormGroup;
|
||||
additionalInfoFormGroup: FormGroup;
|
||||
networkSettingsFormGroup: FormGroup;
|
||||
classroomInfoFormGroup: FormGroup;
|
||||
types: string[] = ['organizational-unit', 'classrooms-group', 'classroom', 'clients-group'];
|
||||
parentUnits: any[] = []; // Array to store parent units fetched from API
|
||||
hardwareProfiles: any[] = [];
|
||||
isEditMode: boolean; // Flag to check if it's edit mode
|
||||
protected p2pModeOptions = [
|
||||
{"name": 'Leecher', "value": "p2p-mode-leecher"},
|
||||
{"name": 'Peer', "value": "p2p-mode-peer"},
|
||||
{"name": 'Seeder', "value": "p2p-mode-seeder"},
|
||||
];
|
||||
protected multicastModeOptions = [
|
||||
{"name": 'Half duplex', "value": "half-duplex"},
|
||||
{"name": 'Full duplex', "value": "full-duplex"},
|
||||
];
|
||||
@Output() unitAdded = new EventEmitter(); // Event emitter to notify parent component about unit addition
|
||||
|
||||
constructor(
|
||||
private _formBuilder: FormBuilder,
|
||||
private dialogRef: MatDialogRef<CreateOrganizationalUnitComponent>,
|
||||
private http: HttpClient, // Inject HttpClient for HTTP requests
|
||||
private dataService: DataService,
|
||||
private toastService: ToastrService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any // Inject data for edit mode
|
||||
) {
|
||||
this.isEditMode = !!data?.uuid; // Check if uuid is passed to determine edit mode
|
||||
|
||||
this.generalFormGroup = this._formBuilder.group({
|
||||
name: ['', Validators.required],
|
||||
parent: [''],
|
||||
description: [''],
|
||||
type: ['', Validators.required]
|
||||
});
|
||||
|
||||
this.additionalInfoFormGroup = this._formBuilder.group({
|
||||
comments: [''],
|
||||
});
|
||||
|
||||
this.networkSettingsFormGroup = this._formBuilder.group({
|
||||
proxy: [''],
|
||||
dns: [''],
|
||||
netmask: [''],
|
||||
router: [''],
|
||||
ntp: [''],
|
||||
p2pMode: [''],
|
||||
p2pTime: [0, Validators.min(0)],
|
||||
mcastIp: [''],
|
||||
mcastSpeed: [0, Validators.min(0)],
|
||||
mcastPort: [0, Validators.min(0)],
|
||||
mcastMode: [''],
|
||||
menu: [null],
|
||||
hardwareProfile: [null],
|
||||
validation: [false]
|
||||
});
|
||||
|
||||
this.classroomInfoFormGroup = this._formBuilder.group({
|
||||
location: [''],
|
||||
projector: [false],
|
||||
board: [false],
|
||||
capacity: [0, Validators.min(0)]
|
||||
});
|
||||
|
||||
if (this.isEditMode) {
|
||||
this.loadData(data.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadParentUnits(); // Load parent units when component initializes
|
||||
this.loadHardwareProfiles();
|
||||
}
|
||||
|
||||
loadParentUnits() {
|
||||
const url = 'http://127.0.0.1:8080/organizational-units?page=1&itemsPerPage=10000';
|
||||
|
||||
this.http.get<any>(url).subscribe(
|
||||
response => {
|
||||
this.parentUnits = response['hydra:member'];
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching parent units:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadHardwareProfiles(): void {
|
||||
this.dataService.getHardwareProfiles().subscribe(
|
||||
(data: any[]) => {
|
||||
this.hardwareProfiles = data;
|
||||
},
|
||||
(error: any) => {
|
||||
console.error('Error fetching hardware profiles', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadData(uuid: string) {
|
||||
const url = `http://127.0.0.1:8080/organizational-units/${uuid}`;
|
||||
|
||||
this.http.get<any>(url).subscribe(
|
||||
data => {
|
||||
this.generalFormGroup.patchValue({
|
||||
name: data.name,
|
||||
parent: data.parent ? data.parent['@id'] : '',
|
||||
description: data.description,
|
||||
type: data.type
|
||||
});
|
||||
this.additionalInfoFormGroup.patchValue({
|
||||
comments: data.comments
|
||||
});
|
||||
this.networkSettingsFormGroup.patchValue({
|
||||
proxy: data.networkSettings.proxy,
|
||||
dns: data.networkSettings.dns,
|
||||
netmask: data.networkSettings.netmask,
|
||||
router: data.networkSettings.router,
|
||||
ntp: data.networkSettings.ntp,
|
||||
p2pMode: data.networkSettings.p2pMode,
|
||||
p2pTime: data.networkSettings.p2pTime,
|
||||
mcastIp: data.networkSettings.mcastIp,
|
||||
mcastSpeed: data.networkSettings.mcastSpeed,
|
||||
mcastPort: data.networkSettings.mcastPort,
|
||||
mcastMode: data.networkSettings.mcastMode,
|
||||
menu: data.networkSettings.menu ? data.networkSettings.menu['@id'] : null,
|
||||
hardwareProfile: data.networkSettings.hardwareProfile ? data.networkSettings.hardwareProfile['@id'] : null,
|
||||
validation: data.networkSettings.validation
|
||||
});
|
||||
this.classroomInfoFormGroup.patchValue({
|
||||
location: data.classroomInfo.location,
|
||||
projector: data.classroomInfo.projector,
|
||||
board: data.classroomInfo.board,
|
||||
capacity: data.classroomInfo.capacity
|
||||
});
|
||||
},
|
||||
error => {
|
||||
console.error('Error fetching data for edit:', error);
|
||||
this.openSnackBar(true, 'Error al cargar la unidad organizativa: ' + error.error['hydra:description'])
|
||||
this.onNoClick()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.generalFormGroup.valid && this.additionalInfoFormGroup.valid && this.networkSettingsFormGroup.valid) {
|
||||
const parentValue = this.generalFormGroup.value.parent;
|
||||
|
||||
const formData = {
|
||||
name: this.generalFormGroup.value.name,
|
||||
parent: parentValue || null,
|
||||
description: this.generalFormGroup.value.description,
|
||||
comments: this.additionalInfoFormGroup.value.comments,
|
||||
type: this.generalFormGroup.value.type,
|
||||
networkSettings: this.networkSettingsFormGroup.value
|
||||
};
|
||||
|
||||
|
||||
if (this.isEditMode) {
|
||||
// Edit mode: Send PUT request to update the unit
|
||||
const putUrl = `http://127.0.0.1:8080/organizational-units/${this.data.uuid}`;
|
||||
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
|
||||
|
||||
this.http.put<any>(putUrl, formData, { headers }).subscribe(
|
||||
response => {
|
||||
console.log('PUT successful:', response);
|
||||
this.unitAdded.emit();
|
||||
this.dialogRef.close();
|
||||
},
|
||||
error => {
|
||||
console.error('Error al realizar PUT:', error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Create mode: Send POST request to create a new unit
|
||||
const postUrl = 'http://127.0.0.1:8080/organizational-units';
|
||||
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
|
||||
|
||||
this.http.post<any>(postUrl, formData, { headers }).subscribe(
|
||||
response => {
|
||||
console.log('POST successful:', response);
|
||||
this.unitAdded.emit();
|
||||
this.dialogRef.close();
|
||||
this.openSnackBar(false, 'Cliente creado exitosamente');
|
||||
},
|
||||
error => {
|
||||
console.error('Error al realizar POST:', error);
|
||||
this.openSnackBar(true, 'Error al crear la unidad organizativa: ' + error.error['hydra:description']);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
openSnackBar(isError: boolean, message: string) {
|
||||
if (isError) {
|
||||
this.toastService.error(' Error al crear el cliente: ' + message, 'Error');
|
||||
} else
|
||||
this.toastService.success('Cliente creado exitosamente', 'Éxito');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
mat-dialog-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 80%;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<h1 mat-dialog-title>Propiedades unidad organizativa</h1>
|
||||
<div mat-dialog-content>
|
||||
<mat-tab-group dynamicHeight>
|
||||
<mat-tab label="Datos generales">
|
||||
<table mat-table [dataSource]="generalData" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="property">
|
||||
<th mat-header-cell *matHeaderCellDef> Propiedad </th>
|
||||
<td mat-cell *matCellDef="let element"> {{ element.property }} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="value">
|
||||
<th mat-header-cell *matHeaderCellDef> Valor </th>
|
||||
<td mat-cell *matCellDef="let element"> {{ element.value }} </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="Propiedades aula y de red">
|
||||
<table mat-table [dataSource]="networkData" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="property">
|
||||
<th mat-header-cell *matHeaderCellDef> Propiedad </th>
|
||||
<td mat-cell *matCellDef="let element"> {{ element.property }} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="value">
|
||||
<th mat-header-cell *matHeaderCellDef> Valor </th>
|
||||
<td mat-cell *matCellDef="let element"> {{ element.value }} </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 label="Acciones">
|
||||
<div class="button-column">
|
||||
<button mat-flat-button color="primary" class="button-encender">Encender</button>
|
||||
<button mat-flat-button color="accent" class="button-apagar">Apagar</button>
|
||||
<button mat-flat-button color="warn" class="button-resetear">Resetear</button>
|
||||
<button mat-flat-button class="button-otros-1">Otras acciones 1</button>
|
||||
<button mat-flat-button class="button-otros-2">Otras acciones 2</button>
|
||||
<button mat-flat-button class="button-otros-3">Otras acciones 3</button>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ShowOrganizationalUnitComponent } from './show-organizational-unit.component';
|
||||
|
||||
describe('ShowOrganizationalUnitComponent', () => {
|
||||
let component: ShowOrganizationalUnitComponent;
|
||||
let fixture: ComponentFixture<ShowOrganizationalUnitComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ShowOrganizationalUnitComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ShowOrganizationalUnitComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,49 @@
|
|||
import {Component, Inject} from '@angular/core';
|
||||
import {MAT_DIALOG_DATA} from "@angular/material/dialog";
|
||||
|
||||
@Component({
|
||||
selector: 'app-show-organizational-unit',
|
||||
templateUrl: './show-organizational-unit.component.html',
|
||||
styleUrl: './show-organizational-unit.component.css'
|
||||
})
|
||||
export class ShowOrganizationalUnitComponent {
|
||||
|
||||
displayedColumns: string[] = ['property', 'value'];
|
||||
|
||||
generalData = [
|
||||
{ property: 'Nombre', value: this.data.data.name },
|
||||
{ property: 'Uuid', value: this.data.data.uuid },
|
||||
{ property: 'Descripción', value: this.data.data.description },
|
||||
{ property: 'Comentarios', value: this.data.data.comments },
|
||||
{ property: 'Tipo', value: this.data.data.type },
|
||||
{ property: 'Unidad organizativa superior', value: this.data.data.parent ? this.data.data.parent.name : '-' },
|
||||
{ property: 'Creado por', value: this.data.data.createdBy },
|
||||
{ property: 'Creado el', value: this.data.data.createdAt }
|
||||
];
|
||||
|
||||
networkData = [
|
||||
{ property: 'Localización', value: this.data.data.location },
|
||||
{ property: 'Proyector', value: this.data.data.projector ? 'Sí' : 'No' },
|
||||
{ property: 'Pizzarra', value: this.data.data.board ? 'Sí' : 'No' },
|
||||
{ property: 'Aforo', value: this.data.data.capacity },
|
||||
{ property: 'Url servidor proxy', value: this.data.data.networkSettings ? this.data.data.networkSettings.proxy : '' },
|
||||
{ property: 'IP DNS', value: this.data.data.networkSettings ? this.data.data.networkSettings.dns : '' },
|
||||
{ property: 'Máscara de red', value: this.data.data.networkSettings ? this.data.data.networkSettings.netmask : '' },
|
||||
{ property: 'Router', value: this.data.data.networkSettings ? this.data.data.networkSettings.router : '' },
|
||||
{ property: 'NTP', value: this.data.data.networkSettings ? this.data.data.networkSettings.ntp : '' },
|
||||
{ property: 'Modo p2p', value: this.data.data.networkSettings ? this.data.data.networkSettings.p2pMode : '' },
|
||||
{ property: 'Tiempo p2p', value: this.data.data.networkSettings ? this.data.data.networkSettings.p2pTime : '' },
|
||||
{ property: 'IP multicast', value: this.data.data.networkSettings ? this.data.data.networkSettings.mcastIp : '' },
|
||||
{ property: 'Modo multicast', value: this.data.data.networkSettings ? this.data.data.networkSettings.mcastMode : '' },
|
||||
{ property: 'Puerto multicast', value: this.data.data.networkSettings ? this.data.data.networkSettings.mcastPort : '' },
|
||||
{ property: 'Velocidad multicast', value: this.data.data.networkSettings ? this.data.data.networkSettings.mcastSpeed : '' },
|
||||
{ property: 'Perfil hardware', value: this.data.data.networkSettings && this.data.data.networkSettings.hardwareProfile ? this.data.data.networkSettings.hardwareProfile.description : '' },
|
||||
{ property: 'Menú', value: this.data.data.networkSettings && this.data.data.networkSettings.menu ? this.data.data.networkSettings.menu.description : '' }
|
||||
];
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: any // Inject data for edit mode
|
||||
) {
|
||||
console.log(this.data)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
mat-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.item-content mat-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.tree-invisible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tree ul,
|
||||
.tree li {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* This padding sets alignment of the nested nodes.
|
||||
*/
|
||||
.tree .mat-nested-tree-node div[role=group] {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Padding for leaf nodes.
|
||||
* Leaf nodes need to have padding so as to align with other non-leaf nodes
|
||||
* under the same parent.
|
||||
*/
|
||||
.tree div[role=group] > .mat-tree-node {
|
||||
padding-left: 40px;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<h1 mat-dialog-title>Visualizar arbol unidad Organizativa</h1>
|
||||
|
||||
<mat-dialog-content>
|
||||
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl" class="tree">
|
||||
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
|
||||
<mat-icon [ngSwitch]="node.type">
|
||||
<ng-container *ngSwitchCase="'organizational-unit'">apartment</ng-container>
|
||||
<ng-container *ngSwitchCase="'classrooms-group'">meeting_room</ng-container>
|
||||
<ng-container *ngSwitchCase="'classroom'">school</ng-container>
|
||||
<ng-container *ngSwitchCase="'client'">computer</ng-container>
|
||||
<ng-container *ngSwitchCase="'clients-group'">lan</ng-container>
|
||||
<ng-container *ngSwitchDefault>help_outline</ng-container>
|
||||
</mat-icon>
|
||||
{{node.name}}
|
||||
</mat-tree-node>
|
||||
<!-- This is the tree node template for expandable nodes -->
|
||||
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild">
|
||||
<div class="mat-tree-node">
|
||||
<button mat-icon-button matTreeNodeToggle
|
||||
[attr.aria-label]="'Toggle ' + node.name">
|
||||
<mat-icon class="mat-icon-rtl-mirror">
|
||||
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
|
||||
</mat-icon>
|
||||
</button>
|
||||
<div class="item-content">
|
||||
<mat-icon [ngSwitch]="node.type">
|
||||
<ng-container *ngSwitchCase="'organizational-unit'">apartment</ng-container>
|
||||
<ng-container *ngSwitchCase="'classrooms-group'">meeting_room</ng-container>
|
||||
<ng-container *ngSwitchCase="'classroom'">school</ng-container>
|
||||
<ng-container *ngSwitchCase="'client'">computer</ng-container>
|
||||
<ng-container *ngSwitchCase="'clients-group'">lan</ng-container>
|
||||
<ng-container *ngSwitchDefault>help_outline</ng-container>
|
||||
</mat-icon>
|
||||
{{node.name}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- There is inline padding applied to this div using styles.
|
||||
This padding value depends on the mat-icon-button width. -->
|
||||
<div [class.tree-invisible]="!treeControl.isExpanded(node)"
|
||||
role="group">
|
||||
<ng-container matTreeNodeOutlet></ng-container>
|
||||
<mat-list *ngIf="node.clients">
|
||||
<mat-list-item *ngFor="let client of node.clients">
|
||||
<mat-icon matListItemIcon>computer</mat-icon>
|
||||
<span matListItemTitle>{{ client.name }}</span>
|
||||
<span>{{ client.ip }} | {{ client.mac }}</span>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
||||
</mat-nested-tree-node>
|
||||
</mat-tree>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="close()">Cerrar</button>
|
||||
</mat-dialog-actions>
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TreeViewComponent } from './tree-view.component';
|
||||
|
||||
describe('TreeViewComponent', () => {
|
||||
let component: TreeViewComponent;
|
||||
let fixture: ComponentFixture<TreeViewComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [TreeViewComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TreeViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {NestedTreeControl} from "@angular/cdk/tree";
|
||||
import {MatTreeNestedDataSource} from "@angular/material/tree";
|
||||
|
||||
interface OrganizationalUnit {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
clients?: Client[];
|
||||
children?: OrganizationalUnit[];
|
||||
}
|
||||
|
||||
interface Client {
|
||||
id: number;
|
||||
name: string;
|
||||
ip: string;
|
||||
mac: string;
|
||||
serialNumber: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-tree-view',
|
||||
templateUrl: './tree-view.component.html',
|
||||
styleUrl: './tree-view.component.css'
|
||||
})
|
||||
export class TreeViewComponent implements OnInit {
|
||||
treeControl = new NestedTreeControl<OrganizationalUnit>(node => node.children);
|
||||
dataSource = new MatTreeNestedDataSource<OrganizationalUnit>();
|
||||
|
||||
constructor(
|
||||
private dialogRef: MatDialogRef<TreeViewComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any
|
||||
) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.dataSource.data = [this.mapData(this.data.data)];
|
||||
}
|
||||
|
||||
hasChild = (_: number, node: OrganizationalUnit) => (!!node.children && node.children.length > 0 || !!node.clients && node.clients.length > 0);
|
||||
|
||||
private mapData(data: any): OrganizationalUnit {
|
||||
const mapClients = (clients: any[]): Client[] => {
|
||||
console.log(clients)
|
||||
return clients.map(client => ({
|
||||
id: client.id,
|
||||
name: client.name,
|
||||
ip: client.ip,
|
||||
mac: client.mac,
|
||||
serialNumber: client.serialNumber,
|
||||
}));
|
||||
};
|
||||
|
||||
const mapChildren = (children: any[]): OrganizationalUnit[] => {
|
||||
return children.map(child => this.mapData(child));
|
||||
};
|
||||
|
||||
return {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
type: data.type,
|
||||
clients: data.clients ? mapClients(data.clients) : [],
|
||||
children: data.children ? mapChildren(data.children) : []
|
||||
};
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
|
||||
mat-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
height: 50px;
|
||||
box-shadow: 10px 0 10px -5px rgba(0, 0, 0, 0.5);
|
||||
height: 60px;
|
||||
background-color: #3f51b5;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.admin-button,
|
||||
.user-button{
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.navbar-button-row {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
justify-content: end;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
|
@ -18,6 +21,5 @@ button[mat-flat-button] {
|
|||
}
|
||||
|
||||
.navbar-tittle{
|
||||
padding-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
<mat-toolbar>
|
||||
<button mat-mini-fab color="primary" aria-label="menu" (click)="onToggleSidebar()">
|
||||
<mat-icon>person</mat-icon>
|
||||
</button>
|
||||
<span class="navbar-tittle" routerLink="/dashboard">Opengnsys webconsole</span>
|
||||
<div class="navbar-button-row">
|
||||
<button mat-flat-button color="primary">Aulas</button>
|
||||
<button mat-flat-button color="primary">Acciones</button>
|
||||
<button mat-flat-button color="primary">Imágenes</button>
|
||||
<button mat-flat-button color="primary">Componentes</button>
|
||||
<button mat-flat-button color="primary">Repositiorios</button>
|
||||
<button mat-flat-button color="primary">Menús</button>
|
||||
<button mat-flat-button color="primary">Buscar</button>
|
||||
<button mat-flat-button color="primary">Calendario</button>
|
||||
<button mat-flat-button color="primary">Ayuda</button>
|
||||
<button mat-flat-button color="warn" routerLink="/auth/login">Salir</button>
|
||||
</div>
|
||||
|
||||
</mat-toolbar>
|
||||
<button class="admin-button" *ngIf="isSuperAdmin" mat-button [matMenuTriggerFor]="menu">Administracion</button>
|
||||
<button class="user-button" mat-button *ngIf="!isSuperAdmin" (click)="editUser()">Editar usuario</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item routerLink="/users">Usuarios</button>
|
||||
<button mat-menu-item routerLink="/user-groups">Roles</button>
|
||||
</mat-menu>
|
||||
<button mat-flat-button color="warn" routerLink="/auth/login">Salir</button>
|
||||
</div>
|
||||
</mat-toolbar>
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||
import {jwtDecode} from 'jwt-decode';
|
||||
import {
|
||||
ChangePasswordModalComponent
|
||||
} from "../../pages/admin/users/users/change-password-modal/change-password-modal.component";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
|
@ -7,14 +11,44 @@ import {jwtDecode} from 'jwt-decode';
|
|||
styleUrls: ['./header.component.css'],
|
||||
})
|
||||
export class HeaderComponent implements OnInit {
|
||||
isSuperAdmin: boolean = false;
|
||||
|
||||
@Output() toggleSidebar = new EventEmitter<void>();
|
||||
private decodedToken: any;
|
||||
private username: any;
|
||||
|
||||
onToggleSidebar() {
|
||||
this.toggleSidebar.emit();
|
||||
}
|
||||
|
||||
constructor() { }
|
||||
constructor(public dialog: MatDialog) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
const token = localStorage.getItem('loginToken');
|
||||
if (token) {
|
||||
try {
|
||||
this.decodedToken = jwtDecode(token);
|
||||
this.isSuperAdmin = this.decodedToken.roles.includes('ROLE_SUPER_ADMIN');
|
||||
localStorage.setItem('isSuperAdmin', String(this.isSuperAdmin));
|
||||
console.log('isSuperAdmin:', this.isSuperAdmin);
|
||||
this.username = this.decodedToken.username;
|
||||
} catch (error) {
|
||||
console.error('Error decoding JWT:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck(): void {
|
||||
this.isSuperAdmin = localStorage.getItem('isSuperAdmin') === 'true';
|
||||
}
|
||||
|
||||
editUser() {
|
||||
const dialogRef = this.dialog.open(ChangePasswordModalComponent, {
|
||||
data: { user: this.decodedToken.username, uuid: this.decodedToken.uuid },
|
||||
width: '400px',
|
||||
});
|
||||
/* dialogRef.componentInstance.userEdited.subscribe(() => {
|
||||
console.log("User edited successfully!")
|
||||
}); */
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
.header {
|
||||
height: 10vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: auto;
|
||||
height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.content-wrapper{
|
||||
display: block;
|
||||
grid-area: content;
|
||||
margin: 20px;
|
||||
}
|
|
@ -1,6 +1,11 @@
|
|||
<app-header (toggleSidebar)="toggleSidebar()"></app-header>
|
||||
<div class="content-wrapper">
|
||||
<app-sidebar [isVisible]="isSidebarVisible"></app-sidebar>
|
||||
<div class="content">
|
||||
<app-header class="header" (toggleSidebar)="toggleSidebar()"></app-header>
|
||||
|
||||
<mat-drawer-container class="container" >
|
||||
<mat-drawer class="sidebar" mode="side" opened>
|
||||
<app-sidebar [isVisible]="isSidebarVisible"></app-sidebar>
|
||||
</mat-drawer>
|
||||
|
||||
<mat-drawer-content class="content">
|
||||
<router-outlet />
|
||||
</div>
|
||||
</mat-drawer-content>
|
||||
</mat-drawer-container>
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Component } from '@angular/core';
|
|||
@Component({
|
||||
selector: 'app-main-layout',
|
||||
templateUrl: './main-layout.component.html',
|
||||
styleUrl: './main-layout.component.css'
|
||||
styleUrl: './main-layout.component.css',
|
||||
})
|
||||
export class MainLayoutComponent {
|
||||
isSidebarVisible: boolean = false;
|
||||
|
|
|
@ -1,24 +1,39 @@
|
|||
.sidebar {
|
||||
width: 250px;
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: -260px;
|
||||
height: 100%;
|
||||
background-color: rgb(245, 245, 245);
|
||||
transition: left 0.3s ease-in-out;
|
||||
box-shadow: 10px 0 10px -5px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.sidebar.visible {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.sidebar-content{
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-bottom: 10px;
|
||||
mat-nav-list {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
mat-list-item {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.entry{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding:0.75rem;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.user-logged{
|
||||
align-items: center;
|
||||
height: 70px;
|
||||
gap: 2rem;
|
||||
padding:1rem;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.admin-link {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,58 @@
|
|||
<div class="sidebar" [class.visible]="isVisible">
|
||||
<div class="sidebar-content">
|
||||
<h4>Bienvenido {{username}}</h4>
|
||||
<button mat-flat-button color="primary" (click)="editUser('username')">Editar usuario</button>
|
||||
<button mat-flat-button color="accent" routerLink="/admin" *ngIf="isSuperAdmin">Admin</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-nav-list>
|
||||
<mat-list-item disabled>
|
||||
<span class="user-logged">
|
||||
<span>Bienvenido {{username}}</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<mat-list-item routerLink="/groups">
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">apartment</mat-icon>
|
||||
<span>Grupos</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">chevron_right</mat-icon>
|
||||
<span>Acciones</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">desktop_windows</mat-icon>
|
||||
<span>Imágenes</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">settings_input_component</mat-icon>
|
||||
<span>Componentes</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">warehouse</mat-icon>
|
||||
<span>Repositorios</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">list</mat-icon>
|
||||
<span>Menús</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">search</mat-icon>
|
||||
<span>Buscar</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<span class="entry">
|
||||
<mat-icon class="icon">calendar_month</mat-icon>
|
||||
<span>Calendarios</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
|
|
|
@ -11,7 +11,7 @@ describe('SidebarComponent', () => {
|
|||
imports: [SidebarComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
fixture = TestBed.createComponent(SidebarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
|
|
@ -21,22 +21,13 @@ export class SidebarComponent {
|
|||
if (token) {
|
||||
try {
|
||||
this.decodedToken = jwtDecode(token);
|
||||
console.log('Decoded token:', this.decodedToken);
|
||||
this.isSuperAdmin = this.decodedToken.roles.includes('ROLE_SUPER_ADMIN');
|
||||
localStorage.setItem('isSuperAdmin', String(this.isSuperAdmin));
|
||||
console.log('isSuperAdmin:', this.isSuperAdmin);
|
||||
this.username = this.decodedToken.username;
|
||||
} catch (error) {
|
||||
console.error('Error decoding JWT:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
editUser(user: any) {
|
||||
// Implementar la lógica de edición
|
||||
const dialogRef = this.dialog.open(ChangePasswordModalComponent, {
|
||||
data: { user: this.decodedToken.username, uuid: this.decodedToken.uuid },
|
||||
});
|
||||
/* dialogRef.componentInstance.userEdited.subscribe(() => {
|
||||
console.log("User edited successfully!")
|
||||
}); */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
.login {
|
||||
overflow: hidden;
|
||||
background-color: rgb(255, 255, 255);
|
||||
border: 1px solid grey;
|
||||
padding: 40px 30px 30px 30px;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(128, 128, 128, 0.801);
|
||||
padding: 40px 30px;
|
||||
border-radius: 10px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 400px;
|
||||
width: 300px;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: transform 300ms, box-shadow 300ms;
|
||||
box-shadow: 5px 10px 10px rgba(2, 128, 144, 0.2);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login::before, .login::after {
|
||||
|
@ -18,42 +19,30 @@
|
|||
position: absolute;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
border-top-left-radius: 40%;
|
||||
border-top-right-radius: 45%;
|
||||
border-bottom-left-radius: 35%;
|
||||
border-bottom-right-radius: 40%;
|
||||
border-radius: 40% 45% 35% 40%;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.login input {
|
||||
font-family: "Asap", sans-serif;
|
||||
.login-logo {
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
display: block;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
background: rgba(230, 230, 230);
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.button-row button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
padding: 10px 10px;
|
||||
margin: 15px -10px;
|
||||
}
|
||||
|
||||
.login button {
|
||||
font-family: "Asap", sans-serif;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
width: 80px;
|
||||
border: 0;
|
||||
padding: 10px 0;
|
||||
margin-top: 10px;
|
||||
margin-left: -5px;
|
||||
border-radius: 5px;
|
||||
background-color: #0084ff;
|
||||
transition: background-color 300ms;
|
||||
}
|
||||
|
||||
.login button:hover {
|
||||
background-color: #0271da;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
|
@ -64,3 +53,16 @@
|
|||
color: red;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@keyframes rotate360 {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.rotating {
|
||||
animation: rotate360 0.6s cubic-bezier(.42,0,1,1) infinite;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,27 @@
|
|||
<div>
|
||||
<form class="login" (ngSubmit)="onLogin()" #loginForm="ngForm">
|
||||
<img src="assets/images/logo.png" alt="Opengnsys"
|
||||
class="login-logo" [class.rotating]="isLoading">
|
||||
<h2>Opengnsys</h2>
|
||||
<input [(ngModel)]="loginObj.username" type="text" id="username" name="username" placeholder="usuario" required #usernameInput="ngModel" [ngClass]="{'invalid': !usernameInput.valid && usernameInput.touched}">
|
||||
<input [(ngModel)]="loginObj.password" type="password" id="password" name="password" placeholder="contraseña" required #passwordInput="ngModel" [ngClass]="{'invalid': !passwordInput.valid && passwordInput.touched}">
|
||||
<button type="submit">Login</button>
|
||||
<div *ngIf="errorMessage" class="error-message">{{ errorMessage }}</div>
|
||||
<mat-form-field>
|
||||
<mat-label>Introducte tu usuario</mat-label>
|
||||
<input matInput [(ngModel)]="loginObj.username" name="username" required #usernameInput="ngModel" [ngClass]="{'invalid': !usernameInput.valid && usernameInput.touched}" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Introduce tu contraseña</mat-label>
|
||||
<input matInput [type]="hide() ? 'password' : 'text'" required [(ngModel)]="loginObj.password" name="password"/>
|
||||
<button
|
||||
mat-icon-button
|
||||
matSuffix
|
||||
(click)="clickEvent($event)"
|
||||
[attr.aria-label]="'Ocultar contraseña'"
|
||||
[attr.aria-pressed]="hide()"
|
||||
>
|
||||
<mat-icon>{{hide() ? 'visibility_off' : 'visibility'}}</mat-icon>
|
||||
</button>
|
||||
</mat-form-field>
|
||||
<div class="button-row">
|
||||
<button mat-flat-button color="primary" type="submit" (keydown.enter)="$event.preventDefault()" [disabled]="!loginObj.username || !loginObj.password">Iniciar sesión</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component } from '@angular/core';
|
||||
import {Component, signal} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
|
@ -13,10 +14,18 @@ export class LoginComponent {
|
|||
"password": ""
|
||||
};
|
||||
errorMessage: string = '';
|
||||
isLoading: boolean = false;
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private router: Router,
|
||||
private toastService: ToastrService,
|
||||
) { }
|
||||
|
||||
constructor(private http: HttpClient, private router: Router) { }
|
||||
|
||||
onLogin() {
|
||||
this.errorMessage = '';
|
||||
this.isLoading = true;
|
||||
|
||||
if (!this.loginObj.username || !this.loginObj.password) {
|
||||
if (!this.loginObj.username) {
|
||||
document.getElementById('username')?.classList.add('invalid');
|
||||
|
@ -29,6 +38,8 @@ export class LoginComponent {
|
|||
} else {
|
||||
document.getElementById('password')?.classList.remove('invalid');
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -38,19 +49,29 @@ export class LoginComponent {
|
|||
localStorage.setItem('loginToken', res.token);
|
||||
localStorage.setItem('refreshToken', res.refreshToken);
|
||||
localStorage.setItem('username', this.loginObj.username);
|
||||
this.openSnackBar(false, 'Bienvenido ' + this.loginObj.username);
|
||||
this.router.navigateByUrl('/dashboard');
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: (err) => {
|
||||
if (err.status === 401) {
|
||||
this.errorMessage = 'Usuario o contraseña incorrectos';
|
||||
} else {
|
||||
this.errorMessage = 'Ha ocurrido un error. Por favor, inténtelo de nuevo.';
|
||||
|
||||
//BYPASS TO DASHBOARD
|
||||
/* this.router.navigateByUrl('/dashboard'); */
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.openSnackBar(true, 'Error al iniciar sesion: ' + err.error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hide = signal(true);
|
||||
clickEvent(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
this.hide.set(!this.hide());
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
openSnackBar(isError: boolean, message: string) {
|
||||
if (isError) {
|
||||
this.toastService.error(message, 'Error');
|
||||
} else
|
||||
this.toastService.success(message, 'Éxito');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,8 @@ import { Component } from '@angular/core';
|
|||
styleUrl: './admin.component.css'
|
||||
})
|
||||
export class AdminComponent {
|
||||
|
||||
|
||||
}
|
||||
|
||||
export class UsersComponent {
|
||||
}
|
||||
|
|
|
@ -14,3 +14,8 @@ table {
|
|||
.header-container h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<h1>Gestión de roles</h1>
|
||||
<button mat-flat-button color="primary" (click)="addUser()">+ Añadir</button>
|
||||
</div>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8 demo-table">
|
||||
<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>
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
.user-form .form-field {
|
||||
display: block;
|
||||
margin-bottom: 10px; /* Puedes ajustar el valor para cambiar la separación */
|
||||
}
|
||||
|
||||
.checkbox-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px; /* Ajusta este valor según necesites */
|
||||
}
|
||||
|
||||
display: block;
|
||||
margin-bottom: 10px; /* Puedes ajustar el valor para cambiar la separación */
|
||||
}
|
||||
|
||||
.checkbox-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px; /* Ajusta este valor según necesites */
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ 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 { UserService } from '../users.service';
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
||||
interface UserGroup {
|
||||
'@id': string;
|
||||
|
@ -24,7 +25,8 @@ export class AddUserModalComponent implements OnInit {
|
|||
public dialogRef: MatDialogRef<AddUserModalComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
private fb: FormBuilder,
|
||||
private userService: UserService // Inyecta el servicio
|
||||
private userService: UserService,
|
||||
private toastService: ToastrService
|
||||
) {
|
||||
this.userForm = this.fb.group({
|
||||
username: ['', Validators.required],
|
||||
|
@ -39,9 +41,9 @@ export class AddUserModalComponent implements OnInit {
|
|||
this.userGroups = data['hydra:member'];
|
||||
});
|
||||
this.userService.getOrganizationalUnits().subscribe((data) => {
|
||||
this.organizationalUnits = data['hydra:member'];
|
||||
this.organizationalUnits = data['hydra:member'].filter((item: any) => item.type === 'organizational-unit');
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
onNoClick(): void {
|
||||
|
@ -64,12 +66,21 @@ export class AddUserModalComponent implements OnInit {
|
|||
console.log('User added successfully:', response);
|
||||
this.userAdded.emit();
|
||||
this.dialogRef.close(this.userForm.value);
|
||||
this.openSnackBar(false, 'Usuario creado correctamente')
|
||||
},
|
||||
error => {
|
||||
console.error('Error adding user:', error);
|
||||
this.openSnackBar(true, error.error['hydra:description']);
|
||||
// Agregar alguna lógica para manejar el error en la interfaz de usuario
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
openSnackBar(isError: boolean, message: string) {
|
||||
if (isError) {
|
||||
this.toastService.error(' Error al eliminar la entidad: ' + message, 'Error');
|
||||
} else
|
||||
this.toastService.success(message, 'Éxito');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
.user-form .form-field {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.checkbox-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.checkbox-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
mat-spinner {
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
|
|
@ -2,28 +2,32 @@
|
|||
<div mat-dialog-content>
|
||||
<form [formGroup]="userForm" class="user-form">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Nombre de usuario</mat-label>
|
||||
<input matInput formControlName="username">
|
||||
<mat-label>Contraseña actual</mat-label>
|
||||
<input matInput formControlName="currentPassword" type="currentPassword">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Nueva contraseña</mat-label>
|
||||
<input matInput formControlName="password" type="password">
|
||||
<input matInput formControlName="newPassword" type="password">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Repite la contraseña</mat-label>
|
||||
<input matInput formControlName="confirmPassword" type="password">
|
||||
<input matInput formControlName="repeatNewPassword" type="password">
|
||||
</mat-form-field>
|
||||
|
||||
@if (loading){
|
||||
<mat-spinner></mat-spinner>
|
||||
}
|
||||
|
||||
@if (passwordMismatch) {
|
||||
<div class="error-message">Las contraseñas no coinciden</div>
|
||||
}
|
||||
|
||||
@if (updateError) {
|
||||
<div class="error-message">Error, inténtelo de nuevo</div>
|
||||
<div class="error-message">{{resetPasswordError}}</div>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onNoClick()">Cancelar</button>
|
||||
<button mat-button (click)="onSubmit()">Editar</button>
|
||||
<button mat-button (click)="onSubmit()" [disabled]="loading">Editar</button>
|
||||
</div>
|
||||
|
|
|
@ -7,13 +7,15 @@ import { UserService } from '../users.service';
|
|||
@Component({
|
||||
selector: 'app-change-password-modal',
|
||||
templateUrl: './change-password-modal.component.html',
|
||||
styleUrls: ['./change-password-modal.component.css']
|
||||
styleUrl: './change-password-modal.component.css'
|
||||
})
|
||||
export class ChangePasswordModalComponent {
|
||||
@Output() userEdited = new EventEmitter<void>();
|
||||
userForm: FormGroup;
|
||||
passwordMismatch: boolean = false;
|
||||
updateError: boolean = false;
|
||||
loading: boolean = false;
|
||||
resetPasswordError: string = '';
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<EditUserModalComponent>,
|
||||
|
@ -22,9 +24,9 @@ export class ChangePasswordModalComponent {
|
|||
private userService: UserService
|
||||
) {
|
||||
this.userForm = this.fb.group({
|
||||
username: [this.data.user, Validators.required],
|
||||
password: ['', Validators.required],
|
||||
confirmPassword: ['', Validators.required]
|
||||
currentPassword: ['', Validators.required],
|
||||
newPassword: ['', Validators.required],
|
||||
repeatNewPassword: ['', Validators.required]
|
||||
}, { validators: this.passwordMatchValidator });
|
||||
}
|
||||
|
||||
|
@ -40,34 +42,36 @@ export class ChangePasswordModalComponent {
|
|||
if (this.userForm.valid) {
|
||||
this.passwordMismatch = false;
|
||||
this.updateError = false;
|
||||
this.loading = true
|
||||
|
||||
const userPayload = {
|
||||
username: this.userForm.value.username,
|
||||
allowedOrganizationalUnits: [],
|
||||
password: this.userForm.value.password,
|
||||
enabled: true,
|
||||
userGroups: []
|
||||
currentPassword: this.userForm.value.currentPassword,
|
||||
newPassword: this.userForm.value.newPassword,
|
||||
repeatNewPassword: this.userForm.value.repeatNewPassword
|
||||
};
|
||||
console.log("THIS IS THE USER PAYLOAD: ", userPayload);
|
||||
this.userService.updateUser(this.data.uuid, userPayload).subscribe(
|
||||
this.userService.changePassword(this.data.uuid, userPayload).subscribe(
|
||||
response => {
|
||||
console.log('User updated successfully:', response);
|
||||
this.userEdited.emit();
|
||||
this.dialogRef.close(this.userForm.value);
|
||||
},
|
||||
error => {
|
||||
console.error('Error updating user:', error);
|
||||
console.error('Error updating user:', error.error['hydra:description']);
|
||||
this.resetPasswordError = error.error['hydra:description']
|
||||
this.updateError = true;
|
||||
this.loading = false
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.error('Form is invalid');
|
||||
this.passwordMismatch = this.userForm.hasError('mismatch');
|
||||
this.updateError = true;
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
private passwordMatchValidator(form: FormGroup): { [key: string]: boolean } | null {
|
||||
return form.get('password')?.value === form.get('confirmPassword')?.value ? null : { mismatch: true };
|
||||
return form.get('newPassword')?.value === form.get('repeatNewPassword')?.value ? null : { mismatch: true };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
.user-form .form-field {
|
||||
display: block;
|
||||
margin-bottom: 10px; /* Puedes ajustar el valor para cambiar la separación */
|
||||
}
|
||||
|
||||
.checkbox-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px; /* Ajusta este valor según necesites */
|
||||
}
|
||||
|
||||
display: block;
|
||||
margin-bottom: 10px; /* Puedes ajustar el valor para cambiar la separación */
|
||||
}
|
||||
|
||||
.checkbox-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px; /* Ajusta este valor según necesites */
|
||||
}
|
||||
|
||||
.loading-container{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<h1 mat-dialog-title>Editar Usuario</h1>
|
||||
<div mat-dialog-content>
|
||||
<mat-spinner *ngIf="loading" class="loading-container"></mat-spinner>
|
||||
<div *ngIf="!loading" mat-dialog-content>
|
||||
<form [formGroup]="userForm" class="user-form">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Nombre de usuario</mat-label>
|
||||
|
@ -9,9 +10,9 @@
|
|||
<mat-label>Contraseña</mat-label>
|
||||
<input matInput formControlName="password" type="password">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Rol</mat-label>
|
||||
<mat-select formControlName="role">
|
||||
<mat-select multiple formControlName="userGroups">
|
||||
<mat-option *ngFor="let group of userGroups" [value]="group['@id']">
|
||||
{{ group.name }}
|
||||
</mat-option>
|
||||
|
@ -20,13 +21,13 @@
|
|||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-label>Unidad organiativa</mat-label>
|
||||
<mat-select multiple formControlName="organizationalUnit">
|
||||
<mat-select multiple formControlName="allowedOrganizationalUnits">
|
||||
<mat-option *ngFor="let unit of organizationalUnits" [value]="unit['@id']">
|
||||
{{unit.name}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
|
|
|
@ -2,12 +2,7 @@ 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 { UserService } from '../users.service';
|
||||
|
||||
interface UserGroup {
|
||||
'@id': string;
|
||||
name: string;
|
||||
role: string[];
|
||||
}
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-user-modal',
|
||||
|
@ -16,29 +11,36 @@ interface UserGroup {
|
|||
})
|
||||
export class EditUserModalComponent implements OnInit {@Output() userEdited = new EventEmitter<void>();
|
||||
userForm: FormGroup;
|
||||
userGroups: UserGroup[] = [];
|
||||
userGroups: any[] = [];
|
||||
organizationalUnits: any[] = [];
|
||||
loading:boolean = false;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<EditUserModalComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
private fb: FormBuilder,
|
||||
private userService: UserService // Inyecta el servicio
|
||||
private userService: UserService, // Inyecta el servicio
|
||||
private toastService: ToastrService
|
||||
) {
|
||||
this.userForm = this.fb.group({
|
||||
username: [this.data],
|
||||
password: [''],
|
||||
role: this.data.user.allowedOrganizationalUnits,
|
||||
organizationalUnit: [[this.data.user.allowedOrganizationalUnits], Validators.required]
|
||||
username: [this.data.user.username],
|
||||
password: [null],
|
||||
userGroups: [this.data.user.userGroups.map((group: { '@id': any; }) => group['@id'])],
|
||||
allowedOrganizationalUnits: [this.data.user.allowedOrganizationalUnits.map((unit: { '@id': any; }) => unit['@id'])]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loading = true
|
||||
this.userService.getUserGroups().subscribe((data) => {
|
||||
this.userGroups = data['hydra:member'];
|
||||
});
|
||||
this.userService.getOrganizationalUnits().subscribe((data) => {
|
||||
this.organizationalUnits = data['hydra:member'];
|
||||
this.organizationalUnits = data['hydra:member'].filter((item: any) => item.type === 'organizational-unit');
|
||||
this.loading = false
|
||||
}, (error) => {
|
||||
console.error('Error fetching organizational units', error);
|
||||
this.loading = false
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -47,25 +49,34 @@ export class EditUserModalComponent implements OnInit {@Output() userEdited = ne
|
|||
}
|
||||
|
||||
onSubmit(): void {
|
||||
console.log(this.userForm.value);
|
||||
const userPayload = {
|
||||
username: this.userForm.value.username,
|
||||
allowedOrganizationalUnits: this.userForm.value.allowedOrganizationalUnits,
|
||||
password: this.userForm.value.password,
|
||||
enabled: true,
|
||||
userGroups: this.userForm.value.userGroups
|
||||
};
|
||||
|
||||
const userPayload = {
|
||||
username: this.userForm.value.username,
|
||||
allowedOrganizationalUnits: [],
|
||||
password: this.userForm.value.password,
|
||||
enabled: true,
|
||||
userGroups: [this.userForm.value.role ]
|
||||
};
|
||||
this.userService.updateUser(this.data.user.uuid, userPayload).subscribe(
|
||||
response => {
|
||||
console.log('User added successfully:', response);
|
||||
this.userEdited.emit();
|
||||
this.dialogRef.close(this.userForm.value);
|
||||
this.openSnackBar(false, 'Usuario actualizado correctamente')
|
||||
},
|
||||
error => {
|
||||
console.error('Error adding user:', error);
|
||||
this.openSnackBar(true, error.message);
|
||||
// Agregar alguna lógica para manejar el error en la interfaz de usuario
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.userService.updateUser(this.data.user.uuid, userPayload).subscribe(
|
||||
response => {
|
||||
console.log('User added successfully:', response);
|
||||
this.userEdited.emit();
|
||||
this.dialogRef.close(this.userForm.value);
|
||||
},
|
||||
error => {
|
||||
console.error('Error adding user:', error);
|
||||
// Agregar alguna lógica para manejar el error en la interfaz de usuario
|
||||
}
|
||||
);
|
||||
openSnackBar(isError: boolean, message: string) {
|
||||
if (isError) {
|
||||
this.toastService.error(' Error al eliminar la entidad: ' + message, 'Error');
|
||||
} else
|
||||
this.toastService.success(message, 'Éxito');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,3 +14,7 @@ table {
|
|||
.header-container h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mat-elevation-z8 {
|
||||
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<h1>Gestión de usuarios</h1>
|
||||
<button mat-flat-button color="primary" (click)="addUser()">+ Añadir</button>
|
||||
</div>
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8 demo-table">
|
||||
<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>
|
||||
|
|
|
@ -51,7 +51,7 @@ export class UsersComponent implements OnInit {
|
|||
}
|
||||
|
||||
addUser() {
|
||||
const dialogRef = this.dialog.open(AddUserModalComponent);
|
||||
const dialogRef = this.dialog.open(AddUserModalComponent, { width: '500px' });
|
||||
|
||||
dialogRef.componentInstance.userAdded.subscribe(() => {
|
||||
this.loadUsers();
|
||||
|
@ -62,7 +62,8 @@ export class UsersComponent implements OnInit {
|
|||
editUser(user: any) {
|
||||
// Implementar la lógica de edición
|
||||
const dialogRef = this.dialog.open(EditUserModalComponent, {
|
||||
data: { user: user }
|
||||
data: { user: user },
|
||||
width: '500px'
|
||||
});
|
||||
dialogRef.componentInstance.userEdited.subscribe(() => {
|
||||
this.loadUsers();
|
||||
|
@ -71,7 +72,7 @@ export class UsersComponent implements OnInit {
|
|||
|
||||
deleteUser(user: any) {
|
||||
const dialogRef = this.dialog.open(DeleteUserModalComponent, {
|
||||
data: user
|
||||
data: user,
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
|
|
|
@ -10,6 +10,12 @@ interface UserPayload {
|
|||
allowedOrganizationalUnits: any[];
|
||||
}
|
||||
|
||||
interface ChangePasswordPayload {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
repeatNewPassword: string;
|
||||
}
|
||||
|
||||
interface UserGroup {
|
||||
'@id': string;
|
||||
name: string;
|
||||
|
@ -38,6 +44,13 @@ export class UserService {
|
|||
return this.http.put(`${this.apiUrl}/users/${userId}`, userPayload, { headers });
|
||||
}
|
||||
|
||||
changePassword(userId: number, userPayload: ChangePasswordPayload): Observable<any> {
|
||||
const headers = new HttpHeaders({
|
||||
'Content-Type': 'application/ld+json',
|
||||
});
|
||||
return this.http.put(`${this.apiUrl}/users/${userId}/reset-password`, userPayload, { headers });
|
||||
}
|
||||
|
||||
getUserGroups(): Observable<{ 'hydra:member': UserGroup[] }> {
|
||||
return this.http.get<{ 'hydra:member': UserGroup[] }>(`${this.apiUrl}/user-groups`);
|
||||
}
|
||||
|
@ -47,7 +60,7 @@ export class UserService {
|
|||
}
|
||||
|
||||
getOrganizationalUnits(): Observable<any> {
|
||||
return this.http.get<any>(`${this.apiUrl}/organizational-units?page=1&itemsPerPage=30`);
|
||||
|
||||
return this.http.get<any>(`${this.apiUrl}/organizational-units?page=1&itemsPerPage=10000`);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
|
@ -0,0 +1,114 @@
|
|||
{
|
||||
"name": "oggui",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@angular/animations": "^18.1.0",
|
||||
"ngx-toastr": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/animations": {
|
||||
"version": "18.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.1.0.tgz",
|
||||
"integrity": "sha512-K0BhvZ/SIVoGXZVuh1KOJDdgcGlHfFGMGrs58utndndAb+gYXReMfz4GR5cQs2OObH6TKmIOY2EH7Og1CY2tsw==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "18.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/common": {
|
||||
"version": "18.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-18.1.0.tgz",
|
||||
"integrity": "sha512-noHDLarQSCZZh7hyNd0HR61Fut+q4QCVq9qc/jKPglfbV/6nPujQSmSpT+rNJlNuBOrCLuvH/CNBNbiqii+x3g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "18.1.0",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/core": {
|
||||
"version": "18.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-18.1.0.tgz",
|
||||
"integrity": "sha512-/57/s7CD/0CwlN+3FlhVmx7ypCWXjKi5UKtnlBAUg0D1denIf6ADxwTHFZABYZcYBqOTJgeQUtUw9u/A+0CIlg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rxjs": "^6.5.3 || ^7.4.0",
|
||||
"zone.js": "~0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-browser": {
|
||||
"version": "18.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.1.0.tgz",
|
||||
"integrity": "sha512-jCmxthiI4Zef54crckNht60xwfIsuciGeyZvb7SsXna2maLW9fA4uz1VhZqIWTiBnHwNynVlyfBX3/jBD7S9+g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/animations": "18.1.0",
|
||||
"@angular/common": "18.1.0",
|
||||
"@angular/core": "18.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@angular/animations": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ngx-toastr": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-19.0.0.tgz",
|
||||
"integrity": "sha512-6pTnktwwWD+kx342wuMOWB4+bkyX9221pAgGz3SHOJH0/MI9erLucS8PeeJDFwbUYyh75nQ6AzVtolgHxi52dQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=16.0.0-0",
|
||||
"@angular/core": ">=16.0.0-0",
|
||||
"@angular/platform-browser": ">=16.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
|
||||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
|
||||
},
|
||||
"node_modules/zone.js": {
|
||||
"version": "0.14.7",
|
||||
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.7.tgz",
|
||||
"integrity": "sha512-0w6DGkX2BPuiK/NLf+4A8FLE43QwBfuqz2dVgi/40Rj1WmqUskCqj329O/pwrqFJLG5X8wkeG2RhIAro441xtg==",
|
||||
"peer": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@angular/animations": "^18.1.0",
|
||||
"ngx-toastr": "^19.0.0"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue