UI: Device connectivity ui implementation

This commit is contained in:
rusikv 2023-10-24 16:00:56 +03:00
parent 7fc04e4b24
commit 4cdd5ce6d4
5 changed files with 346 additions and 53 deletions

View File

@ -15,7 +15,6 @@
limitations under the License. limitations under the License.
--> -->
<div>
<mat-card appearance="outlined" class="settings-card"> <mat-card appearance="outlined" class="settings-card">
<mat-card-header> <mat-card-header>
<mat-card-title> <mat-card-title>
@ -29,7 +28,7 @@
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content> <mat-card-content>
<form [formGroup]="generalSettings" (ngSubmit)="save()"> <form [formGroup]="generalSettings" (ngSubmit)="save()">
<fieldset [disabled]="isLoading$ | async"> <fieldset [disabled]="isLoading$ | async" style="margin-bottom: 10px">
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>admin.base-url</mat-label> <mat-label translate>admin.base-url</mat-label>
<input matInput formControlName="baseUrl" required/> <input matInput formControlName="baseUrl" required/>
@ -37,17 +36,185 @@
{{ 'admin.base-url-required' | translate }} {{ 'admin.base-url-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<tb-checkbox formControlName="prohibitDifferentUrl" style="display: block;"> <div class="tb-form-row" tb-hint-tooltip-icon="{{ 'admin.prohibit-different-url-hint' | translate }}">
<mat-slide-toggle class="mat-slide" formControlName="prohibitDifferentUrl">
{{ 'admin.prohibit-different-url' | translate }} {{ 'admin.prohibit-different-url' | translate }}
</tb-checkbox> </mat-slide-toggle>
<div class="tb-hint" style="padding-left: 10px;" translate>admin.prohibit-different-url-hint</div> </div>
<div fxLayout="row" fxLayoutAlign="end center" style="width: 100%;" class="layout-wrap"> </fieldset>
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px" class="layout-wrap">
<button mat-button color="primary"
[disabled]="generalSettings.pristine"
(click)="discardGeneralSettings()"
type="button">{{'action.undo' | translate}}
</button>
<button mat-button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || generalSettings.invalid || !generalSettings.dirty" <button mat-button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || generalSettings.invalid || !generalSettings.dirty"
type="submit">{{'action.save' | translate}} type="submit">{{'action.save' | translate}}
</button> </button>
</div> </div>
</fieldset>
</form> </form>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
<mat-card appearance="outlined" class="settings-card">
<mat-card-header>
<mat-card-title>
<span class="mat-headline-5" translate>admin.device-connectivity.device-connectivity</span>
</mat-card-title>
</mat-card-header>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content style="padding-top: 16px">
<tb-toggle-select class="toggle-group" appearance="fill" selectMediaBreakpoint="xs" [(ngModel)]="protocol">
<tb-toggle-option value="http">{{ "admin.device-connectivity.http-https" | translate }}</tb-toggle-option>
<tb-toggle-option value="mqtt">{{ 'admin.device-connectivity.mqtt-mqtts' | translate }}</tb-toggle-option>
<tb-toggle-option value="coap">{{ 'admin.device-connectivity.coap-coaps' | translate }}</tb-toggle-option>
</tb-toggle-select>
<mat-hint class="tb-form-hint tb-primary-fill hint">{{ 'admin.device-connectivity.hint' | translate }}</mat-hint>
<form [formGroup]="deviceConnectivitySettingsForm" (ngSubmit)="saveDeviceConnectivitySettings()">
<fieldset *ngIf="protocol === 'http'" [disabled]="isLoading$ | async" formGroupName="http" class="fields-group">
<mat-slide-toggle class="slide" formControlName="enabled">
{{ 'admin.device-connectivity.http' | translate }}
</mat-slide-toggle>
<div class="fields-row">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>admin.device-connectivity.host</mat-label>
<input matInput formControlName="host"/>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>admin.device-connectivity.port</mat-label>
<input matInput type="number" min="0" max="65535" formControlName="port"/>
<mat-error *ngIf="deviceConnectivitySettingsForm.get('http.port').hasError('pattern')">
{{ 'admin.device-connectivity.port-pattern' | translate }}
</mat-error>
<mat-error *ngIf="deviceConnectivitySettingsForm.get('http.port').hasError('min') ||
deviceConnectivitySettingsForm.get('http.port').hasError('max')">
{{ 'admin.device-connectivity.port-range' | translate }}
</mat-error>
</mat-form-field>
</div> </div>
</fieldset>
<fieldset *ngIf="protocol === 'http'" [disabled]="isLoading$ | async" formGroupName="https" class="fields-group">
<mat-slide-toggle class="slide" formControlName="enabled">
{{ 'admin.device-connectivity.https' | translate }}
</mat-slide-toggle>
<div class="fields-row">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>admin.device-connectivity.host</mat-label>
<input matInput formControlName="host"/>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>admin.device-connectivity.port</mat-label>
<input matInput type="number" min="0" max="65535" formControlName="port"/>
<mat-error *ngIf="deviceConnectivitySettingsForm.get('https.port').hasError('pattern')">
{{ 'admin.device-connectivity.port-pattern' | translate }}
</mat-error>
<mat-error *ngIf="deviceConnectivitySettingsForm.get('https.port').hasError('min') ||
deviceConnectivitySettingsForm.get('https.port').hasError('max')">
{{ 'admin.device-connectivity.port-range' | translate }}
</mat-error>
</mat-form-field>
</div>
</fieldset>
<fieldset *ngIf="protocol === 'mqtt'" [disabled]="isLoading$ | async" formGroupName="mqtt" class="fields-group">
<mat-slide-toggle class="slide" formControlName="enabled">
{{ 'admin.device-connectivity.mqtt' | translate }}
</mat-slide-toggle>
<div class="fields-row">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>admin.device-connectivity.host</mat-label>
<input matInput formControlName="host"/>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>admin.device-connectivity.port</mat-label>
<input matInput type="number" min="0" max="65535" formControlName="port"/>
<mat-error *ngIf="deviceConnectivitySettingsForm.get('mqtt.port').hasError('pattern')">
{{ 'admin.device-connectivity.port-pattern' | translate }}
</mat-error>
<mat-error *ngIf="deviceConnectivitySettingsForm.get('mqtt.port').hasError('min') ||
deviceConnectivitySettingsForm.get('mqtt.port').hasError('max')">
{{ 'admin.device-connectivity.port-range' | translate }}
</mat-error>
</mat-form-field>
</div>
</fieldset>
<fieldset *ngIf="protocol === 'mqtt'" [disabled]="isLoading$ | async" formGroupName="mqtts" class="fields-group">
<mat-slide-toggle class="slide" formControlName="enabled">
{{ 'admin.device-connectivity.mqtts' | translate }}
</mat-slide-toggle>
<div class="fields-row">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>admin.device-connectivity.host</mat-label>
<input matInput formControlName="host"/>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>admin.device-connectivity.port</mat-label>
<input matInput type="number" min="0" max="65535" formControlName="port"/>
<mat-error *ngIf="deviceConnectivitySettingsForm.get('mqtts.port').hasError('pattern')">
{{ 'admin.device-connectivity.port-pattern' | translate }}
</mat-error>
<mat-error *ngIf="deviceConnectivitySettingsForm.get('mqtts.port').hasError('min') ||
deviceConnectivitySettingsForm.get('mqtts.port').hasError('max')">
{{ 'admin.device-connectivity.port-range' | translate }}
</mat-error>
</mat-form-field>
</div>
</fieldset>
<fieldset *ngIf="protocol === 'coap'" [disabled]="isLoading$ | async" formGroupName="coap" class="fields-group">
<mat-slide-toggle class="slide" formControlName="enabled">
{{ 'admin.device-connectivity.coap' | translate }}
</mat-slide-toggle>
<div class="fields-row">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>admin.device-connectivity.host</mat-label>
<input matInput formControlName="host"/>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>admin.device-connectivity.port</mat-label>
<input matInput type="number" min="0" max="65535" formControlName="port"/>
<mat-error *ngIf="deviceConnectivitySettingsForm.get('coap.port').hasError('pattern')">
{{ 'admin.device-connectivity.port-pattern' | translate }}
</mat-error>
<mat-error *ngIf="deviceConnectivitySettingsForm.get('coap.port').hasError('min') ||
deviceConnectivitySettingsForm.get('coap.port').hasError('max')">
{{ 'admin.device-connectivity.port-range' | translate }}
</mat-error>
</mat-form-field>
</div>
</fieldset>
<fieldset *ngIf="protocol === 'coap'" [disabled]="isLoading$ | async" formGroupName="coaps" class="fields-group">
<mat-slide-toggle class="slide" formControlName="enabled">
{{ 'admin.device-connectivity.coaps' | translate }}
</mat-slide-toggle>
<div class="fields-row">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>admin.device-connectivity.host</mat-label>
<input matInput formControlName="host"/>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>admin.device-connectivity.port</mat-label>
<input matInput type="number" min="0" max="65535" formControlName="port"/>
<mat-error *ngIf="deviceConnectivitySettingsForm.get('coaps.port').hasError('pattern')">
{{ 'admin.device-connectivity.port-pattern' | translate }}
</mat-error>
<mat-error *ngIf="deviceConnectivitySettingsForm.get('coaps.port').hasError('min') ||
deviceConnectivitySettingsForm.get('coaps.port').hasError('max')">
{{ 'admin.device-connectivity.port-range' | translate }}
</mat-error>
</mat-form-field>
</div>
</fieldset>
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px" class="layout-wrap">
<button mat-button color="primary"
[disabled]="deviceConnectivitySettingsForm.pristine"
(click)="discardDeviceConnectivitySettings()"
type="button">{{'action.undo' | translate}}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async) || deviceConnectivitySettingsForm.invalid || !deviceConnectivitySettingsForm.dirty"
type="submit">{{'action.save' | translate}}
</button>
</div>
</form>
</mat-card-content>
</mat-card>

View File

@ -14,6 +14,26 @@
* limitations under the License. * limitations under the License.
*/ */
:host { :host {
.settings-card {
.toggle-group {
display: block;
margin-bottom: 12px;
}
.hint {
display: block;
}
.fields-group {
.slide {
padding: 16px 0;
}
.fields-row {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 12px;
}
}
}
} }
:host ::ng-deep { :host ::ng-deep {

View File

@ -14,13 +14,13 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, OnInit } from '@angular/core'; import { Component } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { AdminSettings, GeneralSettings } from '@shared/models/settings.models'; import { AdminSettings, DeviceConnectivitySettings, GeneralSettings } from '@shared/models/settings.models';
import { AdminService } from '@core/http/admin.service'; import { AdminService } from '@core/http/admin.service';
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
@ -29,26 +29,27 @@ import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
templateUrl: './general-settings.component.html', templateUrl: './general-settings.component.html',
styleUrls: ['./general-settings.component.scss', './settings-card.scss'] styleUrls: ['./general-settings.component.scss', './settings-card.scss']
}) })
export class GeneralSettingsComponent extends PageComponent implements OnInit, HasConfirmForm { export class GeneralSettingsComponent extends PageComponent implements HasConfirmForm {
generalSettings: UntypedFormGroup; generalSettings: UntypedFormGroup;
adminSettings: AdminSettings<GeneralSettings>; private adminSettings: AdminSettings<GeneralSettings>;
deviceConnectivitySettingsForm: UntypedFormGroup;
private deviceConnectivitySettings: AdminSettings<DeviceConnectivitySettings>;
protocol = 'http';
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private router: Router, private router: Router,
private adminService: AdminService, private adminService: AdminService,
public fb: UntypedFormBuilder) { public fb: UntypedFormBuilder) {
super(store); super(store);
}
ngOnInit() {
this.buildGeneralServerSettingsForm(); this.buildGeneralServerSettingsForm();
this.adminService.getAdminSettings<GeneralSettings>('general').subscribe( this.adminService.getAdminSettings<GeneralSettings>('general')
(adminSettings) => { .subscribe(adminSettings => this.processGeneralSettings(adminSettings));
this.adminSettings = adminSettings; this.buildDeviceConnectivitySettingsForm();
this.generalSettings.reset(this.adminSettings.jsonValue); this.adminService.getAdminSettings<DeviceConnectivitySettings>('connectivity')
} .subscribe(deviceConnectivitySettings => this.processDeviceConnectivitySettings(deviceConnectivitySettings));
);
} }
buildGeneralServerSettingsForm() { buildGeneralServerSettingsForm() {
@ -58,18 +59,73 @@ export class GeneralSettingsComponent extends PageComponent implements OnInit, H
}); });
} }
buildDeviceConnectivitySettingsForm() {
this.deviceConnectivitySettingsForm = this.fb.group({
http: this.fb.group({
enabled: [false, []],
host: ['', []],
port: [null, [Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]]
}),
https: this.fb.group({
enabled: [false, []],
host: ['', []],
port: [null, [Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]]
}),
mqtt: this.fb.group({
enabled: [false, []],
host: ['', []],
port: [null, [Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]]
}),
mqtts: this.fb.group({
enabled: [false, []],
host: ['', []],
port: [null, [Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]]
}),
coap: this.fb.group({
enabled: [false, []],
host: ['', []],
port: [null, [Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]]
}),
coaps: this.fb.group({
enabled: [false, []],
host: ['', []],
port: [null, [Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]]
}),
});
}
save(): void { save(): void {
this.adminSettings.jsonValue = {...this.adminSettings.jsonValue, ...this.generalSettings.value}; this.adminSettings.jsonValue = {...this.adminSettings.jsonValue, ...this.generalSettings.value};
this.adminService.saveAdminSettings(this.adminSettings).subscribe( this.adminService.saveAdminSettings(this.adminSettings)
(adminSettings) => { .subscribe(adminSettings => this.processGeneralSettings(adminSettings));
this.adminSettings = adminSettings; }
saveDeviceConnectivitySettings(): void {
this.deviceConnectivitySettings.jsonValue = {...this.deviceConnectivitySettings.jsonValue, ...this.deviceConnectivitySettingsForm.value};
this.adminService.saveAdminSettings<DeviceConnectivitySettings>(this.deviceConnectivitySettings)
.subscribe(deviceConnectivitySettings => this.processDeviceConnectivitySettings(deviceConnectivitySettings));
}
discardGeneralSettings(): void {
this.generalSettings.reset(this.adminSettings.jsonValue); this.generalSettings.reset(this.adminSettings.jsonValue);
} }
);
discardDeviceConnectivitySettings(): void {
this.deviceConnectivitySettingsForm.reset(this.deviceConnectivitySettings.jsonValue);
}
private processGeneralSettings(generalSettings: AdminSettings<GeneralSettings>): void {
this.adminSettings = generalSettings;
this.generalSettings.reset(this.adminSettings.jsonValue);
}
private processDeviceConnectivitySettings(deviceConnectivitySettings: AdminSettings<DeviceConnectivitySettings>): void {
this.deviceConnectivitySettings = deviceConnectivitySettings;
this.deviceConnectivitySettingsForm.reset(this.deviceConnectivitySettings.jsonValue);
} }
confirmForm(): UntypedFormGroup { confirmForm(): UntypedFormGroup {
return this.generalSettings; return this.generalSettings.dirty ? this.generalSettings : this.deviceConnectivitySettingsForm;
} }
} }

View File

@ -87,6 +87,39 @@ export interface GeneralSettings {
baseUrl: string; baseUrl: string;
} }
export interface DeviceConnectivitySettings {
http: {
enabled: boolean;
host: string;
port: number;
},
https: {
enabled: boolean;
host: string;
port: number;
},
mqtt: {
enabled: boolean;
host: string;
port: number;
},
mqtts: {
enabled: boolean;
host: string;
port: number;
},
coap: {
enabled: boolean;
host: string;
port: number;
},
coaps: {
enabled: boolean;
host: string;
port: number;
}
}
export interface UserPasswordPolicy { export interface UserPasswordPolicy {
minimumLength: number; minimumLength: number;
minimumUppercaseLetters: number; minimumUppercaseLetters: number;

View File

@ -105,6 +105,23 @@
"base-url-required": "Base URL is required.", "base-url-required": "Base URL is required.",
"prohibit-different-url": "Prohibit to use hostname from the client request headers", "prohibit-different-url": "Prohibit to use hostname from the client request headers",
"prohibit-different-url-hint": "This setting should be enabled for production environments. May cause security issues when disabled", "prohibit-different-url-hint": "This setting should be enabled for production environments. May cause security issues when disabled",
"device-connectivity": {
"device-connectivity": "Device connectivity",
"http-https": "HTTP/HTTPS",
"mqtt-mqtts": "MQTT/MQTTS",
"coap-coaps": "COAP/COAPS",
"http": "HTPP",
"https": "HTTPS",
"mqtt": "MQTT",
"mqtts": "MQTTS",
"coap": "COAP",
"coaps": "COAPS",
"hint": "If host or port fields are empty will be used default protocol value.",
"host": "Host",
"port": "Port",
"port-pattern": "Port must be a positive integer.",
"port-range": "Port should be in a range from 1 to 65535."
},
"mail-from": "Mail From", "mail-from": "Mail From",
"mail-from-required": "Mail From is required.", "mail-from-required": "Mail From is required.",
"smtp-protocol": "SMTP protocol", "smtp-protocol": "SMTP protocol",