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,8 +15,7 @@
limitations under the License.
-->
<div>
<mat-card appearance="outlined" class="settings-card">
<mat-card appearance="outlined" class="settings-card">
<mat-card-header>
<mat-card-title>
<div fxLayout="row">
@ -29,7 +28,7 @@
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content>
<form [formGroup]="generalSettings" (ngSubmit)="save()">
<fieldset [disabled]="isLoading$ | async">
<fieldset [disabled]="isLoading$ | async" style="margin-bottom: 10px">
<mat-form-field class="mat-block">
<mat-label translate>admin.base-url</mat-label>
<input matInput formControlName="baseUrl" required/>
@ -37,17 +36,185 @@
{{ 'admin.base-url-required' | translate }}
</mat-error>
</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 }}
</tb-checkbox>
<div class="tb-hint" style="padding-left: 10px;" translate>admin.prohibit-different-url-hint</div>
<div fxLayout="row" fxLayoutAlign="end center" style="width: 100%;" class="layout-wrap">
</mat-slide-toggle>
</div>
</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"
type="submit">{{'action.save' | translate}}
</button>
</div>
</fieldset>
</form>
</mat-card-content>
</mat-card>
</div>
</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>
</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.
*/
: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 {

View File

@ -14,13 +14,13 @@
/// limitations under the License.
///
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { PageComponent } from '@shared/components/page.component';
import { Router } from '@angular/router';
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 { 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',
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;
adminSettings: AdminSettings<GeneralSettings>;
private adminSettings: AdminSettings<GeneralSettings>;
deviceConnectivitySettingsForm: UntypedFormGroup;
private deviceConnectivitySettings: AdminSettings<DeviceConnectivitySettings>;
protocol = 'http';
constructor(protected store: Store<AppState>,
private router: Router,
private adminService: AdminService,
public fb: UntypedFormBuilder) {
super(store);
}
ngOnInit() {
this.buildGeneralServerSettingsForm();
this.adminService.getAdminSettings<GeneralSettings>('general').subscribe(
(adminSettings) => {
this.adminSettings = adminSettings;
this.generalSettings.reset(this.adminSettings.jsonValue);
}
);
this.adminService.getAdminSettings<GeneralSettings>('general')
.subscribe(adminSettings => this.processGeneralSettings(adminSettings));
this.buildDeviceConnectivitySettingsForm();
this.adminService.getAdminSettings<DeviceConnectivitySettings>('connectivity')
.subscribe(deviceConnectivitySettings => this.processDeviceConnectivitySettings(deviceConnectivitySettings));
}
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 {
this.adminSettings.jsonValue = {...this.adminSettings.jsonValue, ...this.generalSettings.value};
this.adminService.saveAdminSettings(this.adminSettings).subscribe(
(adminSettings) => {
this.adminSettings = adminSettings;
this.adminService.saveAdminSettings(this.adminSettings)
.subscribe(adminSettings => this.processGeneralSettings(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);
}
);
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 {
return this.generalSettings;
return this.generalSettings.dirty ? this.generalSettings : this.deviceConnectivitySettingsForm;
}
}

View File

@ -87,6 +87,39 @@ export interface GeneralSettings {
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 {
minimumLength: number;
minimumUppercaseLetters: number;

View File

@ -105,6 +105,23 @@
"base-url-required": "Base URL is required.",
"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",
"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-required": "Mail From is required.",
"smtp-protocol": "SMTP protocol",