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.settings.AdminSettingsService;
import org.thingsboard.server.dao.util.DeviceConnectivityUtil; import org.thingsboard.server.dao.util.DeviceConnectivityUtil;
import javax.annotation.PostConstruct;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URI; import java.net.URI;
@ -86,18 +85,6 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService
@Value("${device.connectivity.mqtts.pem_cert_file:}") @Value("${device.connectivity.mqtts.pem_cert_file:}")
private String mqttsPemCertFile; 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 @Override
public JsonNode findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException { public JsonNode findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException {
DeviceId deviceId = device.getId(); DeviceId deviceId = device.getId();
@ -189,7 +176,7 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService
private DeviceConnectivityInfo getConnectivity(String protocol) { private DeviceConnectivityInfo getConnectivity(String protocol) {
AdminSettings connectivitySettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "connectivity"); AdminSettings connectivitySettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "connectivity");
JsonNode 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 JacksonUtil.convertValue(connectivity.get(protocol), DeviceConnectivityInfo.class);
} }
return null; return null;

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

@ -15,39 +15,110 @@
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> <div fxLayout="row">
<div fxLayout="row"> <span class="mat-headline-5" translate>admin.general-settings</span>
<span class="mat-headline-5" translate>admin.general-settings</span> </div>
</div> </mat-card-title>
</mat-card-title> </mat-card-header>
</mat-card-header> <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> </mat-progress-bar>
</mat-progress-bar> <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" class="margin-bottom">
<fieldset [disabled]="isLoading$ | async"> <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/> <mat-error *ngIf="generalSettings.get('baseUrl').hasError('required')">
<mat-error *ngIf="generalSettings.get('baseUrl').hasError('required')"> {{ 'admin.base-url-required' | translate }}
{{ 'admin.base-url-required' | translate }} </mat-error>
</mat-error> </mat-form-field>
</mat-form-field> <div class="tb-form-row" tb-hint-tooltip-icon="{{ 'admin.prohibit-different-url-hint' | translate }}">
<tb-checkbox formControlName="prohibitDifferentUrl" style="display: block;"> <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>
<button mat-button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || generalSettings.invalid || !generalSettings.dirty" <div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px" class="layout-wrap">
type="submit">{{'action.save' | translate}} <button mat-button color="primary"
</button> [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> </div>
</fieldset> </div>
</form> </ng-template>
</mat-card-content> <div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px" class="layout-wrap">
</mat-card> <button mat-button color="primary"
</div> [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. * limitations under the License.
*/ */
:host { :host {
} .settings-card {
.toggle-group {
:host ::ng-deep { display: block;
.mat-checkbox-layout { }
white-space: normal; .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. /// 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,
DeviceConnectivityProtocol,
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 +34,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: DeviceConnectivityProtocol = '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 +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 { 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; }
this.generalSettings.reset(this.adminSettings.jsonValue);
} 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 { 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; 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 { export interface UserPasswordPolicy {
minimumLength: number; minimumLength: number;
minimumUppercaseLetters: 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.", "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",

Loading…
Cancel
Save