Browse Source

Merge pull request #9483 from rusikv/feature/connectivity-ui

Device connectivity UI implementation
pull/9457/head
Vladyslav 3 years ago
committed by GitHub
parent
commit
92d4f283c9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java
  2. 139
      ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html
  3. 12
      ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss
  4. 101
      ui-ngx/src/app/modules/home/pages/admin/general-settings.component.ts
  5. 10
      ui-ngx/src/app/shared/models/settings.models.ts
  6. 17
      ui-ngx/src/assets/locale/locale.constant-en_US.json

15
dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java

@ -41,7 +41,6 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.util.DeviceConnectivityUtil;
import javax.annotation.PostConstruct;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
@ -86,18 +85,6 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService
@Value("${device.connectivity.mqtts.pem_cert_file:}")
private String mqttsPemCertFile;
@PostConstruct
private void init() {
DeviceConnectivityInfo mqtts = getConnectivity(MQTTS);
if (mqtts != null && mqtts.isEnabled()) {
String certFilePath = mqttsPemCertFile;
if (StringUtils.isBlank(certFilePath) || !ResourceUtils.resourceExists(this, certFilePath)) {
String error = StringUtils.isBlank(certFilePath) ? "path is empty" : "file is not exists";
log.error("MQTTS is enabled but cert {}!", error);
}
}
}
@Override
public JsonNode findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException {
DeviceId deviceId = device.getId();
@ -189,7 +176,7 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService
private DeviceConnectivityInfo getConnectivity(String protocol) {
AdminSettings connectivitySettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "connectivity");
JsonNode connectivity;
if (connectivitySettings != null && (connectivity = connectivitySettings.getJsonValue()) != null) {
if (connectivitySettings != null && (connectivity = connectivitySettings.getJsonValue()) != null) {
return JacksonUtil.convertValue(connectivity.get(protocol), DeviceConnectivityInfo.class);
}
return null;

139
ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html

@ -15,39 +15,110 @@
limitations under the License.
-->
<div>
<mat-card appearance="outlined" class="settings-card">
<mat-card-header>
<mat-card-title>
<div fxLayout="row">
<span class="mat-headline-5" translate>admin.general-settings</span>
</div>
</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>
<form [formGroup]="generalSettings" (ngSubmit)="save()">
<fieldset [disabled]="isLoading$ | async">
<mat-form-field class="mat-block">
<mat-label translate>admin.base-url</mat-label>
<input matInput formControlName="baseUrl" required/>
<mat-error *ngIf="generalSettings.get('baseUrl').hasError('required')">
{{ 'admin.base-url-required' | translate }}
</mat-error>
</mat-form-field>
<tb-checkbox formControlName="prohibitDifferentUrl" style="display: block;">
<mat-card appearance="outlined" class="settings-card">
<mat-card-header>
<mat-card-title>
<div fxLayout="row">
<span class="mat-headline-5" translate>admin.general-settings</span>
</div>
</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>
<form [formGroup]="generalSettings" (ngSubmit)="save()">
<fieldset [disabled]="isLoading$ | async" class="margin-bottom">
<mat-form-field class="mat-block">
<mat-label translate>admin.base-url</mat-label>
<input matInput formControlName="baseUrl" required/>
<mat-error *ngIf="generalSettings.get('baseUrl').hasError('required')">
{{ 'admin.base-url-required' | translate }}
</mat-error>
</mat-form-field>
<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">
<button mat-button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || generalSettings.invalid || !generalSettings.dirty"
type="submit">{{'action.save' | translate}}
</button>
</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>
</form>
</mat-card-content>
</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 margin-bottom" 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>
<div class="tb-form-hint tb-primary-fill margin-bottom">{{ 'admin.device-connectivity.hint' | translate }}</div>
<form [formGroup]="deviceConnectivitySettingsForm" (ngSubmit)="saveDeviceConnectivitySettings()">
<ng-container *ngIf="protocol === 'http'">
<ng-container *ngTemplateOutlet="connectivitySettings; context:{protocol: protocol}"></ng-container>
<ng-container *ngTemplateOutlet="connectivitySettings; context:{protocol: protocol + 's'}"></ng-container>
</ng-container>
<ng-container *ngIf="protocol === 'mqtt'">
<ng-container *ngTemplateOutlet="connectivitySettings; context:{protocol: protocol}"></ng-container>
<ng-container *ngTemplateOutlet="connectivitySettings; context:{protocol: protocol + 's'}"></ng-container>
</ng-container>
<ng-container *ngIf="protocol === 'coap'">
<ng-container *ngTemplateOutlet="connectivitySettings; context:{protocol: protocol}"></ng-container>
<ng-container *ngTemplateOutlet="connectivitySettings; context:{protocol: protocol + 's'}"></ng-container>
</ng-container>
<ng-template #connectivitySettings let-protocol="protocol">
<div class="tb-form-panel stroked margin-bottom" [formGroupName]="protocol">
<mat-slide-toggle class="mat-slide" formControlName="enabled">
{{ 'admin.device-connectivity.' + protocol | translate }}
</mat-slide-toggle>
<div class="tb-form-row column-xs no-border no-padding tb-standard-fields">
<mat-form-field fxFlex>
<mat-label translate>admin.device-connectivity.host</mat-label>
<input matInput formControlName="host"/>
</mat-form-field>
<mat-form-field 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(protocol + '.port').hasError('pattern')">
{{ 'admin.device-connectivity.port-pattern' | translate }}
</mat-error>
<mat-error *ngIf="deviceConnectivitySettingsForm.get(protocol + '.port').hasError('min') ||
deviceConnectivitySettingsForm.get(protocol + '.port').hasError('max')">
{{ 'admin.device-connectivity.port-range' | translate }}
</mat-error>
</mat-form-field>
</div>
</fieldset>
</form>
</mat-card-content>
</mat-card>
</div>
</div>
</ng-template>
<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>

12
ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss

@ -14,10 +14,12 @@
* limitations under the License.
*/
:host {
}
:host ::ng-deep {
.mat-checkbox-layout {
white-space: normal;
.settings-card {
.toggle-group {
display: block;
}
.margin-bottom {
margin-bottom: 12px;
}
}
}

101
ui-ngx/src/app/modules/home/pages/admin/general-settings.component.ts

@ -14,13 +14,18 @@
/// 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,
DeviceConnectivityProtocol,
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 +34,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: DeviceConnectivityProtocol = '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 +64,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.generalSettings.reset(this.adminSettings.jsonValue);
}
);
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;
}
}

10
ui-ngx/src/app/shared/models/settings.models.ts

@ -87,6 +87,16 @@ export interface GeneralSettings {
baseUrl: string;
}
export type DeviceConnectivityProtocol = 'http' | 'https' | 'mqtt' | 'mqtts' | 'coap' | 'coaps';
export interface DeviceConnectivityInfo {
enabled: boolean;
host: string;
port: number;
}
export type DeviceConnectivitySettings = Record<DeviceConnectivityProtocol, DeviceConnectivityInfo>;
export interface UserPasswordPolicy {
minimumLength: number;
minimumUppercaseLetters: number;

17
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -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",

Loading…
Cancel
Save