Browse Source

Actualización administración SCADAs

Jose Brito 6 tháng trước cách đây
mục cha
commit
ebe80c3942

+ 15 - 20
package-lock.json

@@ -10,13 +10,13 @@
       "dependencies": {
         "@airgap/beacon-sdk": "^2.4.0-beta.2",
         "@angular/animations": "^19.2.6",
-        "@angular/cdk": "^19.0.5",
+        "@angular/cdk": "^19.2.9",
         "@angular/common": "^19.2.6",
         "@angular/compiler": "^19.2.6",
         "@angular/core": "^19.2.6",
         "@angular/forms": "^19.2.6",
-        "@angular/material": "^19.0.5",
-        "@angular/material-moment-adapter": "^19.0.5",
+        "@angular/material": "^19.2.9",
+        "@angular/material-moment-adapter": "^19.2.9",
         "@angular/platform-browser": "^19.2.6",
         "@angular/platform-browser-dynamic": "^19.2.6",
         "@angular/router": "^19.2.6",
@@ -1328,16 +1328,14 @@
       }
     },
     "node_modules/@angular/cdk": {
-      "version": "19.0.5",
-      "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.0.5.tgz",
-      "integrity": "sha512-+D++QUrJlDuwk5RhQBDTejQseb0ZP6c6S4r8wBBab7UPtrwigySudSb0PxhiAzp2YHr5Ch3klhkTf/NSWeUXUQ==",
+      "version": "19.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.9.tgz",
+      "integrity": "sha512-4If3BjWQPwW/xqRUCL7Mx0dHS0SuZY7Tq/Ocf7liwYTYCmSv8Ew7NqaiPA4RS3FnyUJcZW/UAd231uWI/ZHChg==",
       "license": "MIT",
       "dependencies": {
+        "parse5": "^7.1.2",
         "tslib": "^2.3.0"
       },
-      "optionalDependencies": {
-        "parse5": "^7.1.2"
-      },
       "peerDependencies": {
         "@angular/common": "^19.0.0 || ^20.0.0",
         "@angular/core": "^19.0.0 || ^20.0.0",
@@ -1593,16 +1591,15 @@
       }
     },
     "node_modules/@angular/material": {
-      "version": "19.0.5",
-      "resolved": "https://registry.npmjs.org/@angular/material/-/material-19.0.5.tgz",
-      "integrity": "sha512-yiW/ZJNkOPlQdqgj5U8DHTu3r7OHMI5R1cAbCpOmHlsVHEoc/Vw4V3RFUgpWLqCGgdRIkayoilMAooT52gG2Dg==",
+      "version": "19.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/material/-/material-19.2.9.tgz",
+      "integrity": "sha512-zkc49u6AIZ+aQe76MvOmoZ06c+/ZNpFeBS3dH65kCmnIpFhh8c+zLjxgwgBIlrvUmfjy6J1iJaG05hmkLzAggg==",
       "license": "MIT",
       "dependencies": {
         "tslib": "^2.3.0"
       },
       "peerDependencies": {
-        "@angular/animations": "^19.0.0 || ^20.0.0",
-        "@angular/cdk": "19.0.5",
+        "@angular/cdk": "19.2.9",
         "@angular/common": "^19.0.0 || ^20.0.0",
         "@angular/core": "^19.0.0 || ^20.0.0",
         "@angular/forms": "^19.0.0 || ^20.0.0",
@@ -1611,16 +1608,16 @@
       }
     },
     "node_modules/@angular/material-moment-adapter": {
-      "version": "19.0.5",
-      "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-19.0.5.tgz",
-      "integrity": "sha512-37pzxYZt8vDQrZ02z3JxL1e05ERk75QMYGkr9vWrj/pk0YSI8kgbuYFuFJLgA6wlGwFo3Y7mB1N30BMLST/PLA==",
+      "version": "19.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-19.2.9.tgz",
+      "integrity": "sha512-lujYPTyMruzN6yYdWe3CnqE/0B6rRFxVMwVRKZacz4BXPqscEeY/gVQCSyUjReIi4trYc8iKKl/zlJRfoTF6EA==",
       "license": "MIT",
       "dependencies": {
         "tslib": "^2.3.0"
       },
       "peerDependencies": {
         "@angular/core": "^19.0.0 || ^20.0.0",
-        "@angular/material": "19.0.5",
+        "@angular/material": "19.2.9",
         "moment": "^2.18.1"
       }
     },
@@ -8663,7 +8660,6 @@
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
       "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
-      "devOptional": true,
       "engines": {
         "node": ">=0.12"
       },
@@ -12772,7 +12768,6 @@
       "version": "7.1.2",
       "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
       "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
-      "devOptional": true,
       "dependencies": {
         "entities": "^4.4.0"
       },

+ 3 - 3
package.json

@@ -12,13 +12,13 @@
   "dependencies": {
     "@airgap/beacon-sdk": "^2.4.0-beta.2",
     "@angular/animations": "^19.2.6",
-    "@angular/cdk": "^19.0.5",
+    "@angular/cdk": "^19.2.9",
     "@angular/common": "^19.2.6",
     "@angular/compiler": "^19.2.6",
     "@angular/core": "^19.2.6",
     "@angular/forms": "^19.2.6",
-    "@angular/material": "^19.0.5",
-    "@angular/material-moment-adapter": "^19.0.5",
+    "@angular/material": "^19.2.9",
+    "@angular/material-moment-adapter": "^19.2.9",
     "@angular/platform-browser": "^19.2.6",
     "@angular/platform-browser-dynamic": "^19.2.6",
     "@angular/router": "^19.2.6",

+ 2 - 0
src/app/app.module.ts

@@ -417,6 +417,7 @@ import { StationViewDetailedComponent } from './components/equipment-management/
 import { StationVewPositionComponent } from './components/equipment-management/equipment-location/station-vew-position/station-vew-position.component';
 import { ViaLevelSelectorComponent } from './components/equipment-management/equipment-location/via-level-selector/via-level-selector.component';
 import { StationLocationComponent } from './components/equipment-management/equipment-location/station-location/station-location.component';
+import { EditScadaPasswordComponent } from './components/system-admin/web-services-admin/scada-admin/edit-scada-password/edit-scada-password.component';
 /*FIN Componentes SAM*/
 
 @NgModule({
@@ -778,6 +779,7 @@ import { StationLocationComponent } from './components/equipment-management/equi
     StationVewPositionComponent,
     ViaLevelSelectorComponent,
     StationLocationComponent,
+    EditScadaPasswordComponent,
   ],
   imports: [
     SocketIoModule.forRoot(config),

+ 0 - 0
src/app/components/system-admin/web-services-admin/scada-admin/edit-scada-password/edit-scada-password.component.css


+ 43 - 0
src/app/components/system-admin/web-services-admin/scada-admin/edit-scada-password/edit-scada-password.component.html

@@ -0,0 +1,43 @@
+<h1 mat-dialog-title class="prevent-select">Editar contraseña del SCADA</h1>
+<div mat-dialog-content class="prevent-select">
+  <div class="is-loading animated fadeIn fast" *ngIf="isLoading">
+    <mat-spinner align="center"></mat-spinner>
+    <h3>Cargando datos ...</h3>
+  </div>
+  <div class="has-error animated fadeIn" *ngIf="!isLoading && hasError">
+    <mat-icon class="red_primary_font">error</mat-icon>
+    <h2>{{ errorStr }}</h2>
+  </div>
+  <div class="dialog_details_column animated fadeIn" *ngIf="!isLoading && !hasError">
+    <div class="form-column" [formGroup]="scadaPasswordFormGroup">
+      <div class="form-row">
+        <div class="form-cell C12">
+          <mat-form-field appearance="outline" class="w-100 mt-8">
+            <mat-label>Contraseña actual</mat-label>
+            <input matInput placeholder="Ingrese la contraseña actual del SCADA" [type]="showLastPassword ? 'text' : 'password'" 
+            formControlName="lastPaswordControl" (paste)="avoidPaste()">
+            <button mat-icon-button matSuffix (click)="showLastPassword = !showLastPassword">
+              <mat-icon>{{ showLastPassword ? 'visibility_off' : 'visibility' }}</mat-icon>
+            </button>
+            <mat-error *ngIf="scadaPasswordFormGroup.invalid">{{ getErrorMessage("lastPaswordControl") }}</mat-error>
+          </mat-form-field>
+        </div>
+        <div class="form-cell C12">
+          <mat-form-field appearance="outline" class="w-100 mt-8">
+            <mat-label>Contraseña nueva</mat-label>
+            <input matInput placeholder="Ingrese la nueva contraseña del SCADA" [type]="showNewPassword ? 'text' : 'password'" 
+            formControlName="newPasswordControl" [errorStateMatcher]="generalMatcher!">
+            <button mat-icon-button matSuffix (click)="showNewPassword = !showNewPassword">
+              <mat-icon>{{ showNewPassword ? 'visibility_off' : 'visibility' }}</mat-icon>
+            </button>
+            <mat-error *ngIf="scadaPasswordFormGroup.invalid">{{ getErrorMessage("newPasswordControl") }}</mat-error>
+          </mat-form-field>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+<div mat-dialog-actions align="end">
+  <button mat-button mat-dialog-close cdkFocusInitial>Cancelar</button>
+  <button mat-button (click)="savePassword()" [disabled]="scadaPasswordFormGroup.invalid || samePasswords">Guardar cambios</button>
+</div>

+ 23 - 0
src/app/components/system-admin/web-services-admin/scada-admin/edit-scada-password/edit-scada-password.component.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { EditScadaPasswordComponent } from './edit-scada-password.component';
+
+describe('EditScadaPasswordComponent', () => {
+  let component: EditScadaPasswordComponent;
+  let fixture: ComponentFixture<EditScadaPasswordComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [EditScadaPasswordComponent]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(EditScadaPasswordComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 209 - 0
src/app/components/system-admin/web-services-admin/scada-admin/edit-scada-password/edit-scada-password.component.ts

@@ -0,0 +1,209 @@
+import { Component, OnInit } from '@angular/core';
+import { AbstractControl, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
+import { MatDialogRef } from '@angular/material/dialog';
+import { SystemAdminService } from '../../../../../services/system-admin.service';
+import { PasswordFormatResponse } from '../../../../../interfaces/password-format.interface';
+import { lastValueFrom } from 'rxjs';
+import { GeneralMatcher, VALID_CHARS } from '../../../../users-profiles/users-admin/new-user/new-user.component';
+import { ResourcesService } from '../../../../../services/resources.service';
+import { EncService } from '../../../../../services/enc.service';
+
+@Component({
+  selector: 'app-edit-scada-password',
+  standalone: false,
+  templateUrl: './edit-scada-password.component.html',
+  styleUrl: './edit-scada-password.component.css'
+})
+export class EditScadaPasswordComponent implements OnInit {
+  isLoading: boolean;
+  hasError: boolean;
+  errorStr: string;
+  showLastPassword: boolean;
+  showNewPassword: boolean;
+  generalMatcher: GeneralMatcher | null;
+  samePasswords: boolean;
+  
+  passMinLength: number;
+  passMinUpper: number;
+  passMinNumber: number;
+  passMinChars: number;
+
+  scadaPasswordFormGroup: FormGroup;
+
+  constructor(
+    private _dialogRef: MatDialogRef<EditScadaPasswordComponent>,
+    private _systemAdminService: SystemAdminService,
+    private _resourcesService: ResourcesService,
+    private _encService: EncService,
+  ){
+    this.isLoading = true;
+    this.hasError = false;
+    this.errorStr = "";
+    this.showLastPassword = false;
+    this.showNewPassword = false;
+    this.generalMatcher = new GeneralMatcher();
+    this.samePasswords = false;
+    
+    this.passMinLength = 0;
+    this.passMinUpper = 0;
+    this.passMinNumber = 0;
+    this.passMinChars = 0;
+
+    this.scadaPasswordFormGroup = new FormGroup({
+      lastPaswordControl: new FormControl('', Validators.required),
+      newPasswordControl: new FormControl(''),
+    });
+  }
+
+  ngOnInit(): void {
+    this.getPasswordRules();
+
+    this.scadaPasswordFormGroup.controls['lastPaswordControl'].valueChanges.subscribe(lastPassword => {
+      let newPassword = this.scadaPasswordFormGroup.controls['newPasswordControl'].value;
+      if(lastPassword == newPassword){
+        this.samePasswords = true;
+        this._resourcesService.openSnackBar('Las contraseñas ingresadas son iguales.');
+      }else{
+        this.samePasswords = false;
+      }
+    });
+
+    this.scadaPasswordFormGroup.controls['newPasswordControl'].valueChanges.subscribe(newPassword => {
+      let lastPassword = this.scadaPasswordFormGroup.controls['lastPaswordControl'].value;
+      if(lastPassword == newPassword){
+        this.samePasswords = true;
+        this._resourcesService.openSnackBar('Las contraseñas ingresadas son iguales.');
+      }else{
+        this.samePasswords = false;
+      }
+    })
+  }
+  
+  private async getPasswordRules(){
+    try{
+      let idUser = localStorage.getItem('idusuario')!;
+      let passwordPattern: PasswordFormatResponse = await lastValueFrom(this._systemAdminService.getPasswordFormat(idUser, 1));
+
+      this.hasError = passwordPattern.error;
+      this.errorStr = passwordPattern.msg;
+
+      if(!this.hasError){
+        this.scadaPasswordFormGroup.controls['newPasswordControl'].setValidators([
+          Validators.minLength(passwordPattern.response.password_format.min_length),
+          Validators.required,
+        ]);
+        
+        this.passMinLength = passwordPattern.response.password_format.min_length;
+        this.scadaPasswordFormGroup.addValidators(this.whiteSpacesValidator);
+
+        if(passwordPattern.response.password_format.upper_enabled){
+          this.passMinUpper = passwordPattern.response.password_format.min_upper;
+          this.scadaPasswordFormGroup.addValidators(this.passMinUpperValidator);
+        }
+
+        if(passwordPattern.response.password_format.number_enabled){
+          this.passMinNumber = passwordPattern.response.password_format.min_number;
+          this.scadaPasswordFormGroup.addValidators(this.passMinNumberValidator);
+        }
+
+        if(passwordPattern.response.password_format.chars_enabled){
+          this.passMinChars = passwordPattern.response.password_format.min_chars;
+          this.scadaPasswordFormGroup.addValidators(this.passMinCharsValidator);
+        }
+      }
+
+      this.isLoading = false;
+    }catch(error: any){
+      if(error.error == undefined){
+        this.errorStr = "Ocurrió un error inesperado.";
+      }else if(error.error.msg == undefined){
+        this.errorStr = "Ocurrió un error inesperado.";
+      }else{
+        this.errorStr = error.error.msg;
+      }
+
+      this.hasError = true;
+      this.isLoading = false;
+    }
+  }
+
+  passMinUpperValidator: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
+    let pass = group.get('newPasswordControl')!.value;
+    let valArr = pass.split('');
+    let upperCount = 0;
+
+    valArr.forEach((char: string) => {
+      let upperRegex = new RegExp('[A-Z]');
+      if(char.match(upperRegex)){
+        upperCount++;
+      }
+    });
+    
+    return upperCount >= this.passMinUpper ? null : { minUpper: true }
+  }
+
+  passMinNumberValidator: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
+    let pass = group.get('newPasswordControl')!.value;
+    let valArr = pass.split('');
+    let numberCount = 0;
+
+    valArr.forEach((char: string) => {
+      let upperRegex = new RegExp('[0-9]');
+      if(char.match(upperRegex)){
+        numberCount++;
+      }
+    });
+
+    return numberCount >= this.passMinNumber ? null : { minNumber: true }
+  }
+  
+  passMinCharsValidator: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
+    let pass = group.get('newPasswordControl')!.value;
+    let valArr = pass.split('');
+    let charsCount = 0;
+
+    valArr.forEach((char: string) => {
+      let specialChars = VALID_CHARS;
+      if(specialChars.includes(char)){
+        charsCount++;
+      }
+    });
+    
+    return charsCount >= this.passMinChars ? null : { minChars: true }
+  }
+
+  whiteSpacesValidator: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
+    let pass = group.get('newPasswordControl')!.value;
+    return !pass.includes(' ') ? null : { whiteSpaces: true }
+  }
+
+  getErrorMessage(control: string) {
+    switch(control){
+      case 'lastPaswordControl':
+        if(this.scadaPasswordFormGroup.get('lastPaswordControl')?.hasError('required')) return "Este campo es obligatorio.";
+      break;
+      case 'newPasswordControl':
+        if(this.scadaPasswordFormGroup.get('newPasswordControl')?.hasError('required')) return "Este campo es obligatorio.";
+        if(this.scadaPasswordFormGroup.get('newPasswordControl')?.hasError('minlength')) return `La contraseña debe tener al menos ${this.passMinLength} caracteres.`;
+        if(this.scadaPasswordFormGroup.errors != null && this.scadaPasswordFormGroup.errors!['minUpper']) return `La contraseña debe contener al menos ${this.passMinUpper} letra(s) mayúscula(s).`;
+        if(this.scadaPasswordFormGroup.errors != null && this.scadaPasswordFormGroup.errors!['minNumber']) return `La contraseña debe contener al menos ${this.passMinNumber} número(s).`;
+        if(this.scadaPasswordFormGroup.errors != null && this.scadaPasswordFormGroup.errors!['minChars']) return `La contraseña debe contener al menos ${this.passMinChars} caracter(es) especial(es).`;
+        if(this.scadaPasswordFormGroup.errors != null && this.scadaPasswordFormGroup.errors!['whiteSpaces']) return `La contraseña no debe contener espacios en blanco.`;
+      break;
+    }
+    
+    return "A";
+  }
+
+  avoidPaste(): boolean{
+    this._resourcesService.openSnackBar('No se puede pegar información en este campo');
+    return false;
+  }
+
+  async savePassword() {
+    let passwordsStr = JSON.stringify(this.scadaPasswordFormGroup.getRawValue());
+    let passwordsEnc = await this._encService.encrypt(passwordsStr);
+
+    this._dialogRef.close(passwordsEnc);
+  }
+}

+ 38 - 9
src/app/components/system-admin/web-services-admin/scada-admin/register-scada/register-scada.component.html

@@ -1,13 +1,42 @@
-<h1 mat-dialog-title>{{type == 'new' ? 'Registrar nuevo' : 'Editar'}} SCADA</h1>
-<div mat-dialog-content>
-  <mat-form-field appearance="outline" class="w-100 mt-8">
-    <mat-label>Nombre del SCADA</mat-label>
-    <input matInput placeholder="Ingrese el nombre del SCADA" [(ngModel)]="nombreSCADA">
-    <mat-icon matSuffix>sensors</mat-icon>
-  </mat-form-field>
+<h1 mat-dialog-title class="prevent-select">{{ isNew ? 'Registrar nuevo' : 'Editar' }} SCADA</h1>
+<div mat-dialog-content class="prevent-select">
+  <div class="is-loading animated fadeIn fast" *ngIf="isLoading">
+    <mat-spinner align="center"></mat-spinner>
+    <h3>Cargando datos ...</h3>
+  </div>
+  <div class="has-error animated fadeIn" *ngIf="!isLoading && hasError">
+    <mat-icon class="red_primary_font">error</mat-icon>
+    <h2>{{ errorStr }}</h2>
+  </div>
+  <div class="dialog_details_column animated fadeIn" *ngIf="!isLoading && !hasError">
+    <div class="form-column" [formGroup]="scadaConfigFormGroup">
+      <div class="form-row">
+        <div class="form-cell C12">
+          <mat-form-field appearance="outline" class="w-100 mt-8">
+            <mat-label>Nombre del SCADA</mat-label>
+            <input matInput placeholder="Ingrese el nombre del SCADA" formControlName="scadaNameControl">
+            <mat-icon matSuffix>sensors</mat-icon>
+            <mat-error *ngIf="scadaConfigFormGroup.invalid">{{ getErrorMessage("scadaNameControl") }}</mat-error>
+          </mat-form-field>
+        </div>
+        <div class="form-cell C12">
+          <mat-form-field appearance="outline" class="w-100 mt-8">
+            <mat-label>Contraseña de acceso al Web Service</mat-label>
+            <input matInput placeholder="Ingrese la contraseña del SCADA" [type]="showPassword ? 'text' : 'password'" 
+            formControlName="scadaPassControl" [errorStateMatcher]="generalMatcher!" [readonly]="!isNew">
+            <button mat-icon-button matSuffix (click)="showPassword = !showPassword">
+              <mat-icon>{{ showPassword ? 'visibility_off' : 'visibility' }}</mat-icon>
+            </button>
+            <mat-error *ngIf="scadaConfigFormGroup.invalid">{{ getErrorMessage("scadaPassControl") }}</mat-error>
+          </mat-form-field>
+        </div>
+      </div>
+    </div>
+  </div>
 </div>
 <mat-dialog-actions align="end">
   <button mat-button mat-dialog-close cdkFocusInitial>Cancelar</button>
-  <button mat-button (click)="validateName()">{{type == 'new' ? 'Registrar SCADA' : 'Guardar cambios'}}</button>
-  <button [mat-dialog-close]="nombreSCADA + '|' + type" class="hidden" #regButton></button>
+  <button mat-button (click)="validateName()" [disabled]="scadaConfigFormGroup.invalid">
+    {{ isNew ? 'Registrar SCADA' : 'Guardar cambios' }}
+  </button>
 </mat-dialog-actions>

+ 206 - 18
src/app/components/system-admin/web-services-admin/scada-admin/register-scada/register-scada.component.ts

@@ -1,6 +1,12 @@
-import { Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
-import { MAT_DIALOG_DATA } from '@angular/material/dialog';
-import { ResourcesService } from '../../../../../services/resources.service';
+import { Component, Inject, OnInit } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { AbstractControl, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
+import { SystemAdminService } from '../../../../../services/system-admin.service';
+import { lastValueFrom } from 'rxjs';
+import { PasswordFormatResponse } from '../../../../../interfaces/password-format.interface';
+import { GeneralMatcher, VALID_CHARS } from '../../../../users-profiles/users-admin/new-user/new-user.component';
+import { EncService } from '../../../../../services/enc.service';
+import { SCADADetailsResponse } from '../../../../../interfaces/scada-list.interface';
 
 @Component({
     selector: 'app-register-scada',
@@ -9,29 +15,211 @@ import { ResourcesService } from '../../../../../services/resources.service';
     standalone: false
 })
 export class RegisterScadaComponent implements OnInit {
-  nombreSCADA: string;
-  type: string;
-
-  @ViewChild('regButton') register!: ElementRef;
+  isNew: boolean;
+  isLoading: boolean;
+  hasError: boolean;
+  errorStr: string;
+  scadaName: string;
+  scadaPass: string;
+  showPassword: boolean;
+  scadaConfigFormGroup: FormGroup;
+  generalMatcher: GeneralMatcher | null;
+  
+  passMinLength: number;
+  passMinUpper: number;
+  passMinNumber: number;
+  passMinChars: number;
 
   constructor(
-    @Inject(MAT_DIALOG_DATA) public data: any,
-    private _resourcesService: ResourcesService,
+    @Inject(MAT_DIALOG_DATA) private _data: any,
+    private _dialogRef: MatDialogRef<RegisterScadaComponent>,
+    private _systemAdminService: SystemAdminService,
+    private _encService: EncService,
   ) { 
-    this.nombreSCADA = "";
-    this.type = "";
+    this.isNew = true;
+    this.isLoading = true;
+    this.hasError = false;
+    this.errorStr = "";
+    this.scadaName = "";
+    this.scadaPass = "";
+    this.showPassword = false;
+    this.generalMatcher = new GeneralMatcher();
+    
+    this.passMinLength = 0;
+    this.passMinUpper = 0;
+    this.passMinNumber = 0;
+    this.passMinChars = 0;
+
+    this.scadaConfigFormGroup = new FormGroup({
+      scadaNameControl: new FormControl('', Validators.required),
+      scadaPassControl: new FormControl(''),
+    });
   }
 
   ngOnInit(): void {
-    this.type = this.data.type;
-    this.nombreSCADA = this.data.name;
+    if(this._data.type == 'new'){
+      this.getPasswordRules();
+    }else{
+      this.isNew = false;
+      this.getSCADA();
+    }
   }
 
-  validateName(){
-    if(this.nombreSCADA == ""){
-      this._resourcesService.openSnackBar('El nombre del SCADA no debe estar vacío');
-    }else{
-      this.register?.nativeElement.click();
+  async getSCADA(){
+    try{
+      let idUser = localStorage.getItem('idusuario')!;
+      let scadaDetails: SCADADetailsResponse = await lastValueFrom(this._systemAdminService.getSCADA(this._data.id, idUser, 1));
+
+      this.hasError = scadaDetails.error;
+      this.errorStr = scadaDetails.msg;
+
+      if(!this.hasError){
+        this.scadaConfigFormGroup.controls['scadaNameControl'].setValue(scadaDetails.response.NOMBRESCADA);
+
+        let passwordEncrypted = await this._encService.isEncrypted(scadaDetails.response.CONTRASCADA);
+        if(passwordEncrypted){
+          let passwordDec = await this._encService.decrypt(scadaDetails.response.CONTRASCADA);
+          this.scadaConfigFormGroup.controls['scadaPassControl'].setValue(passwordDec);
+        }else{
+          this.scadaConfigFormGroup.controls['scadaPassControl'].clearValidators();
+        }
+      }
+
+      this.isLoading = false;
+    }catch(error: any){
+      if(error.error == undefined){
+        this.errorStr = 'Ocurrió un error inesperado.';
+      }else if(error.error.msg == undefined){
+        this.errorStr = 'Ocurrió un error inesperado.';
+      }else{
+        this.errorStr = error.error.msg;
+      }
+
+      this.hasError = true;
+      this.isLoading = false;
+    }
+  }
+
+  async validateName(){
+    let scadaConfigStr = JSON.stringify(this.scadaConfigFormGroup.getRawValue());
+    let scadaConfigEnc = await this._encService.encrypt(scadaConfigStr);
+
+    this._dialogRef.close(scadaConfigEnc);
+  }
+
+  private async getPasswordRules(){
+    try{
+      let idUser = localStorage.getItem('idusuario')!;
+      let passwordPattern: PasswordFormatResponse = await lastValueFrom(this._systemAdminService.getPasswordFormat(idUser, 1));
+
+      this.hasError = passwordPattern.error;
+      this.errorStr = passwordPattern.msg;
+
+      if(!this.hasError && this.isNew){
+        this.scadaConfigFormGroup.controls['scadaPassControl'].setValidators([
+          Validators.minLength(passwordPattern.response.password_format.min_length),
+          Validators.required,
+        ]);
+        
+        this.passMinLength = passwordPattern.response.password_format.min_length;
+        this.scadaConfigFormGroup.addValidators(this.whiteSpacesValidator);
+
+        if(passwordPattern.response.password_format.upper_enabled){
+          this.passMinUpper = passwordPattern.response.password_format.min_upper;
+          this.scadaConfigFormGroup.addValidators(this.passMinUpperValidator);
+        }
+
+        if(passwordPattern.response.password_format.number_enabled){
+          this.passMinNumber = passwordPattern.response.password_format.min_number;
+          this.scadaConfigFormGroup.addValidators(this.passMinNumberValidator);
+        }
+
+        if(passwordPattern.response.password_format.chars_enabled){
+          this.passMinChars = passwordPattern.response.password_format.min_chars;
+          this.scadaConfigFormGroup.addValidators(this.passMinCharsValidator);
+        }
+      }
+
+      this.isLoading = false;
+    }catch(error: any){
+      if(error.error == undefined){
+        this.errorStr = "Ocurrió un error inesperado.";
+      }else if(error.error.msg == undefined){
+        this.errorStr = "Ocurrió un error inesperado.";
+      }else{
+        this.errorStr = error.error.msg;
+      }
+
+      this.hasError = true;
+      this.isLoading = false;
+    }
+  }
+
+  passMinUpperValidator: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
+    let pass = group.get('scadaPassControl')!.value;
+    let valArr = pass.split('');
+    let upperCount = 0;
+
+    valArr.forEach((char: string) => {
+      let upperRegex = new RegExp('[A-Z]');
+      if(char.match(upperRegex)){
+        upperCount++;
+      }
+    });
+    
+    return upperCount >= this.passMinUpper ? null : { minUpper: true }
+  }
+
+  passMinNumberValidator: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
+    let pass = group.get('scadaPassControl')!.value;
+    let valArr = pass.split('');
+    let numberCount = 0;
+
+    valArr.forEach((char: string) => {
+      let upperRegex = new RegExp('[0-9]');
+      if(char.match(upperRegex)){
+        numberCount++;
+      }
+    });
+
+    return numberCount >= this.passMinNumber ? null : { minNumber: true }
+  }
+  
+  passMinCharsValidator: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
+    let pass = group.get('scadaPassControl')!.value;
+    let valArr = pass.split('');
+    let charsCount = 0;
+
+    valArr.forEach((char: string) => {
+      let specialChars = VALID_CHARS;
+      if(specialChars.includes(char)){
+        charsCount++;
+      }
+    });
+    
+    return charsCount >= this.passMinChars ? null : { minChars: true }
+  }
+
+  whiteSpacesValidator: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
+    let pass = group.get('scadaPassControl')!.value;
+    return !pass.includes(' ') ? null : { whiteSpaces: true }
+  }
+
+  getErrorMessage(control: string) {
+    switch(control){
+      case 'scadaNameControl':
+        if(this.scadaConfigFormGroup.get('scadaNameControl')?.hasError('required')) return "Este campo es obligatorio.";
+      break;
+      case 'scadaPassControl':
+        if(this.scadaConfigFormGroup.get('scadaPassControl')?.hasError('required')) return "Este campo es obligatorio.";
+        if(this.scadaConfigFormGroup.get('scadaPassControl')?.hasError('minlength')) return `La contraseña debe tener al menos ${this.passMinLength} caracteres.`;
+        if(this.scadaConfigFormGroup.errors != null && this.scadaConfigFormGroup.errors!['minUpper']) return `La contraseña debe contener al menos ${this.passMinUpper} letra(s) mayúscula(s).`;
+        if(this.scadaConfigFormGroup.errors != null && this.scadaConfigFormGroup.errors!['minNumber']) return `La contraseña debe contener al menos ${this.passMinNumber} número(s).`;
+        if(this.scadaConfigFormGroup.errors != null && this.scadaConfigFormGroup.errors!['minChars']) return `La contraseña debe contener al menos ${this.passMinChars} caracter(es) especial(es).`;
+        if(this.scadaConfigFormGroup.errors != null && this.scadaConfigFormGroup.errors!['whiteSpaces']) return `La contraseña no debe contener espacios en blanco.`;
+      break;
     }
+    
+    return "A";
   }
 }

+ 4 - 0
src/app/components/system-admin/web-services-admin/scada-admin/scada-admin.component.html

@@ -78,6 +78,10 @@
               [disabled]="row.ESTATUS == 'Eliminado' || !regEditSCADAEnabled" color="primary">
                 <mat-icon>edit</mat-icon>
               </button>
+              <button mat-mini-fab matTooltip="Cambiar contraseña" class="no_shadow mr-4" (click)="openEditPassword(row.IDSCADA)" 
+              [disabled]="row.ESTATUS == 'Eliminado'" [ngClass]="{blue_primary_background: row.ESTATUS == 'Activo', white_font: row.ESTATUS == 'Activo'}">
+                <mat-icon>password</mat-icon>
+              </button>
               <button mat-mini-fab matTooltip="Eliminar" class="no_shadow" (click)="openDeleteAlert(row.IDSCADA)" 
               [disabled]="row.ESTATUS == 'Eliminado'" [ngClass]="{red_primary_background: row.ESTATUS == 'Activo', white_font: row.ESTATUS == 'Activo'}">
                 <mat-icon>delete</mat-icon>

+ 66 - 13
src/app/components/system-admin/web-services-admin/scada-admin/scada-admin.component.ts

@@ -17,12 +17,13 @@ import { ProfileInterface } from '../../../../interfaces/profile.interface';
 import { Permissions } from '../../../../interfaces/permissions.interface';
 import { RegisterScadaComponent } from './register-scada/register-scada.component';
 import { AlertComponent } from '../../../resources/dialogs/alert/alert.component';
+import { EditScadaPasswordComponent } from './edit-scada-password/edit-scada-password.component';
 
 @Component({
-    selector: 'app-scada-admin',
-    templateUrl: './scada-admin.component.html',
-    styleUrl: './scada-admin.component.css',
-    standalone: false
+  selector: 'app-scada-admin',
+  templateUrl: './scada-admin.component.html',
+  styleUrl: './scada-admin.component.css',
+  standalone: false
 })
 export class ScadaAdminComponent implements OnInit {
   btnSmall: boolean;
@@ -173,40 +174,44 @@ export class ScadaAdminComponent implements OnInit {
     }
   }
 
-  openRegister(type: string, id?: number, name?: string){
+  async openRegister(type: string, id?: number, name?: string){
+    let idEnc = id == undefined ? id : await this._encService.encrypt(`${id}`);
     let dialogRef = this._matDialog.open(RegisterScadaComponent, {
       width: '480px',
+      minWidth: '480px',
       data: {
         type: type,
-        id: id,
+        id: idEnc,
         name: name,
       }
     });
 
     dialogRef.afterClosed().subscribe(res => {
       if(res != undefined && res != null && res != ""){
-        let resArr = res.split('|');
-        this.registerSCADA(resArr[0], resArr[1], id);
+        this.registerSCADA(res, type, idEnc);
       }
     });
   }
 
-  async registerSCADA(name: string, type: string, id?: number){
+  async registerSCADA(data: string, type: string, id?: number | string){
     try{
+      let dataDec = await this._encService.decrypt(data);
+      let dataObj = JSON.parse(dataDec);
+      let passEnc = await this._encService.encrypt(dataObj.scadaPassControl);
+      
       let idUser = localStorage.getItem('idusuario');
       let formData = new FormData();
 
       formData.append('id_user', idUser!);
       formData.append('linea', '1');
-      formData.append('scada_name', name);
+      formData.append('scada_name', dataObj.scadaNameControl);
+      formData.append('scada_pass', passEnc);
 
       if(type == 'new'){
         await lastValueFrom(this._systemAdminService.registerSCADA(formData));
         this._resourcesService.openSnackBar('El SCADA se registró correctamente.');
       }else{
-        let idSCADA = await this._encService.encrypt(`${id}`);
-        formData.append('id_scada', idSCADA);
-        
+        formData.append('id_scada', `${id}`);
         await lastValueFrom(this._systemAdminService.updateSCADA(formData));
         this._resourcesService.openSnackBar('El SCADA se actualizó correctamente.');
       }
@@ -224,6 +229,54 @@ export class ScadaAdminComponent implements OnInit {
     }
   }
 
+  async openEditPassword(idSCADA: number){
+    let idSCADAEnc = await this._encService.encrypt(`${idSCADA}`);
+    let dialogRef = this._matDialog.open(EditScadaPasswordComponent, {
+      width: '480px',
+      minWidth: '480px',
+      data: {
+        id: idSCADAEnc
+      }
+    });
+
+    dialogRef.afterClosed().subscribe(res => {
+      if(res != null && res != undefined && res != ''){
+        this.updateSCADAPassword(idSCADAEnc, res);
+      }
+    })
+  }
+
+  async updateSCADAPassword(idSCADA: string, dataEnc: string){
+    try{
+      let idUser = localStorage.getItem('idusuario')!;
+      let dataDec = await this._encService.decrypt(dataEnc);
+      let dataObj = JSON.parse(dataDec);
+      let formData = new FormData();
+
+      let lastPasswordEnc = await this._encService.encrypt(dataObj.lastPaswordControl);
+      let newPasswordEnc = await this._encService.encrypt(dataObj.newPasswordControl);
+
+      formData.append('id_user', idUser);
+      formData.append('linea', '1');
+      formData.append('last_password', lastPasswordEnc);
+      formData.append('new_password', newPasswordEnc);
+      formData.append('id_scada', idSCADA);
+
+      await lastValueFrom(this._systemAdminService.updateSCADAPassword(formData));
+
+      this._resourcesService.openSnackBar('La contraseña del SCADA se actualizó correctamente.');
+      this.getData();
+    }catch(error: any){
+      if(error.error == undefined){
+        this._resourcesService.openSnackBar('Ocurrió un error inesperado.');
+      }else if(error.error.msg == undefined){
+        this._resourcesService.openSnackBar('Ocurrió un error inesperado.');
+      }else{
+        this._resourcesService.openSnackBar(error.error.msg);
+      }
+    }
+  }
+
   getUserName(usrReg: string, usrMod?: string): string{
     if(usrMod != null){
       return usrMod!;

+ 17 - 0
src/app/interfaces/scada-list.interface.ts

@@ -12,4 +12,21 @@ export interface SCADA{
   FECHAREGISTRO: string;
   USUARIOMODIFICO?: string;
   FECHAMODIFICACION?: string;
+}
+
+export interface SCADADetailsResponse{
+  error: boolean;
+  msg: string;
+  response: SCADADetails;
+}
+
+export interface SCADADetails{
+  IDSCADA: number | string;
+  NOMBRESCADA: string;
+  CONTRASCADA: string;
+  ESTATUS: string;
+  USUARIOREGISTRO: string;
+  FECHAREGISTRO: string;
+  USUARIOMODIFICO: string | null;
+  FECHAMODIFICACION: string | null;
 }

+ 8 - 0
src/app/services/system-admin.service.ts

@@ -69,6 +69,10 @@ export class SystemAdminService {
     return this.getQuery(`get-login-images/${idUser}/${line}`).pipe(map((data: any) => data))
   }
 
+  getSCADA(idSCADA: string, idUser: string, line: number){
+    return this.getQuery(`get-scada/${idSCADA}/${idUser}/${line}`).pipe(map((data: any) => data))
+  }
+
   getSCADAList(idUser: string, line: number){
     return this.getQuery(`get-scada-list/${idUser}/${line}`).pipe(map((data: any) => data))
   }
@@ -254,6 +258,10 @@ export class SystemAdminService {
     return this.postQuery("register-scada", body).pipe(map((data: any) => data))
   }
 
+  updateSCADAPassword(body: any){
+    return this.postQuery("update-scada-password", body).pipe(map((data: any) => data))
+  }
+
   updateSCADA(body: any){
     return this.postQuery("update-scada", body).pipe(map((data: any) => data))
   }