Changed Security block for REST Connector

This commit is contained in:
mpetrov 2024-06-19 17:20:15 +03:00
parent c4e5ab65ee
commit 580f3b5b3c
7 changed files with 249 additions and 66 deletions

View File

@ -0,0 +1,65 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-form-row space-between same-padding tb-flex column" [formGroup]="securityFormGroup">
<div class="tb-flex row space-between align-center no-gap fill-width">
<div class="fields-label" translate>gateway.security</div>
<tb-toggle-select formControlName="type" appearance="fill">
<tb-toggle-option *ngFor="let type of securityTypes" [value]="type">
{{ SecurityTypeTranslationsMap.get(type) | translate }}
</tb-toggle-option>
</tb-toggle-select>
</div>
<ng-container *ngIf="securityFormGroup.get('type').value === BrokerSecurityType.BASIC">
<div class="tb-form-row space-between tb-flex fill-width">
<div class="fixed-title-width" translate>gateway.username</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput name="value" formControlName="username" placeholder="{{ 'gateway.set' | translate }}"/>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="('gateway.username-required') | translate"
*ngIf="securityFormGroup.get('username').hasError('required') && securityFormGroup.get('username').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
</div>
<div class="tb-form-row space-between tb-flex fill-width">
<div class="fixed-title-width" translate>gateway.password</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput type="password" name="value" formControlName="password" placeholder="{{ 'gateway.set' | translate }}"/>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="('gateway.password-required') | translate"
*ngIf="securityFormGroup.get('password').hasError('required')
&& securityFormGroup.get('password').touched"
class="tb-error">
warning
</mat-icon>
<div [class.hide-toggle]="securityFormGroup.get('password').hasError('required')" class="tb-flex no-gap align-center fill-height" matSuffix>
<tb-toggle-password class="tb-flex align-center fill-height"></tb-toggle-password>
</div>
</mat-form-field>
</div>
</div>
</ng-container>
</div>

View File

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
width: 100%;
height: 100%;
display: block;
margin-bottom: 10px;
.fields-label {
font-weight: 500;
}
.hide-toggle {
display: none;
}
}

View File

@ -0,0 +1,132 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
ChangeDetectionStrategy,
Component,
forwardRef,
OnDestroy,
} from '@angular/core';
import { Subject } from 'rxjs';
import {
ControlValueAccessor,
FormBuilder,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormGroup,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
import { takeUntil } from 'rxjs/operators';
import {
noLeadTrailSpacesRegex,
RestSecurityType,
RestSecurityTypeTranslationsMap
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common';
@Component({
selector: 'tb-rest-connector-security',
templateUrl: './rest-connector-security.component.html',
styleUrls: ['./rest-connector-security.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RestConnectorSecurityComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => RestConnectorSecurityComponent),
multi: true
}
],
standalone: true,
imports: [
SharedModule,
CommonModule,
]
})
export class RestConnectorSecurityComponent implements ControlValueAccessor, Validator, OnDestroy {
BrokerSecurityType = RestSecurityType;
securityTypes: RestSecurityType[] = Object.values(RestSecurityType);
SecurityTypeTranslationsMap = RestSecurityTypeTranslationsMap;
securityFormGroup: UntypedFormGroup;
private destroy$ = new Subject<void>();
private propagateChange = (_: any) => {};
constructor(private fb: FormBuilder) {
this.securityFormGroup = this.fb.group({
type: [RestSecurityType.ANONYMOUS, []],
username: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
password: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
});
this.observeSecurityForm();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {}
writeValue(deviceInfo: any): void {
if (!deviceInfo.type) {
deviceInfo.type = RestSecurityType.ANONYMOUS;
}
this.securityFormGroup.reset(deviceInfo);
this.updateView(deviceInfo);
}
validate(): ValidationErrors | null {
return this.securityFormGroup.valid ? null : {
securityForm: { valid: false }
};
}
updateView(value: any): void {
this.propagateChange(value);
}
private updateValidators(type: RestSecurityType): void {
if (type === RestSecurityType.BASIC) {
this.securityFormGroup.get('username').enable({emitEvent: false});
this.securityFormGroup.get('password').enable({emitEvent: false});
} else {
this.securityFormGroup.get('username').disable({emitEvent: false});
this.securityFormGroup.get('password').disable({emitEvent: false});
}
}
private observeSecurityForm(): void {
this.securityFormGroup.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(value => this.updateView(value));
this.securityFormGroup.get('type').valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(type => this.updateValidators(type));
}
}

View File

@ -368,38 +368,7 @@
{{ 'gateway.rpc.add-header' | translate }} {{ 'gateway.rpc.add-header' | translate }}
</button> </button>
</fieldset> </fieldset>
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="security"> <tb-rest-connector-security [formControl]="commandForm.get('security')"></tb-rest-connector-security>
<span class="fields-label">{{ 'gateway.rpc.security' | translate }}</span>
<div class="border" fxLayout="column" fxLayoutGap="10px" *ngIf="getFormArrayControls('security').length">
<div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center">
<span fxFlex class="title">{{ 'gateway.rpc.security-name' | translate }}</span>
<span fxFlex class="title">{{ 'gateway.rpc.value' | translate }}</span>
<span fxFlex="30px"></span>
</div>
<mat-divider></mat-divider>
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"
*ngFor="let control of getFormArrayControls('security'); let i = index">
<ng-container [formGroupName]="i">
<mat-form-field appearance="outline" fxFlex>
<input matInput formControlName="securityName"/>
</mat-form-field>
<mat-form-field appearance="outline" fxFlex>
<input matInput formControlName="value" placeholder="anonymous"/>
</mat-form-field>
<mat-icon style="cursor:pointer;"
fxFlex="30px"
(click)="removeHTTPSecurity(i)"
matTooltip="{{ 'gateway.rpc.remove' | translate }}">delete
</mat-icon>
</ng-container>
</div>
</div>
<button mat-raised-button
fxFlexAlign="start"
(click)="addHTTPSecurity()">
{{ 'gateway.rpc.add-security' | translate }}
</button>
</fieldset>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.REQUEST"> <ng-template [ngSwitchCase]="ConnectorType.REQUEST">
<mat-form-field> <mat-form-field>

View File

@ -114,17 +114,12 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue
this.commandForm = this.connectorParamsFormGroupByType(this.connectorType); this.commandForm = this.connectorParamsFormGroupByType(this.connectorType);
this.commandForm.valueChanges.subscribe(value => { this.commandForm.valueChanges.subscribe(value => {
const httpHeaders = {}; const httpHeaders = {};
const security = {};
switch (this.connectorType) { switch (this.connectorType) {
case ConnectorType.REST: case ConnectorType.REST:
value.httpHeaders.forEach(data => { value.httpHeaders.forEach(data => {
httpHeaders[data.headerName] = data.value; httpHeaders[data.headerName] = data.value;
}) })
value.httpHeaders = httpHeaders; value.httpHeaders = httpHeaders;
value.security.forEach(data => {
security[data.securityName] = data.value;
})
value.security = security;
break; break;
case ConnectorType.REQUEST: case ConnectorType.REQUEST:
value.httpHeaders.forEach(data => { value.httpHeaders.forEach(data => {
@ -252,7 +247,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue
tries: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]], tries: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]],
valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
httpHeaders: this.fb.array([]), httpHeaders: this.fb.array([]),
security: this.fb.array([]) security: [{}, [Validators.required]]
}) })
break; break;
case ConnectorType.REQUEST: case ConnectorType.REQUEST:
@ -312,22 +307,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue
oidsFA.removeAt(index); oidsFA.removeAt(index);
} }
addHTTPSecurity(value: { securityName: string, value: string } = {securityName: null, value: null}) {
const securityFA = this.commandForm.get('security') as FormArray;
const formGroup = this.fb.group({
securityName: [value.securityName, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
value: [value.value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]]
})
if (securityFA) {
securityFA.push(formGroup, {emitEvent: false});
}
}
removeHTTPSecurity(index: number) {
const oidsFA = this.commandForm.get('security') as FormArray;
oidsFA.removeAt(index);
}
getFormArrayControls(path: string) { getFormArrayControls(path: string) {
return (this.commandForm.get(path) as FormArray).controls as FormControl[]; return (this.commandForm.get(path) as FormArray).controls as FormControl[];
} }
@ -402,11 +381,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue
break; break;
case ConnectorType.REST: case ConnectorType.REST:
this.clearFromArrayByName("httpHeaders"); this.clearFromArrayByName("httpHeaders");
this.clearFromArrayByName("security");
value.security && Object.entries(value.security).forEach(securityHeader => {
this.addHTTPSecurity({securityName: securityHeader[0], value: securityHeader[1] as string})
})
delete value.security;
value.httpHeaders && Object.entries(value.httpHeaders).forEach(httpHeader => { value.httpHeaders && Object.entries(value.httpHeaders).forEach(httpHeader => {
this.addHTTPHeader({headerName: httpHeader[0], value: httpHeader[1] as string}) this.addHTTPHeader({headerName: httpHeader[0], value: httpHeader[1] as string})
}) })

View File

@ -360,6 +360,18 @@ export const BrokerSecurityTypeTranslationsMap = new Map<BrokerSecurityType, str
] ]
); );
export enum RestSecurityType {
ANONYMOUS = 'anonymous',
BASIC = 'basic',
}
export const RestSecurityTypeTranslationsMap = new Map<RestSecurityType, string>(
[
[RestSecurityType.ANONYMOUS, 'gateway.broker.security-types.anonymous'],
[RestSecurityType.BASIC, 'gateway.broker.security-types.basic'],
]
);
export const MqttVersions = [ export const MqttVersions = [
{ name: 3.1, value: 3 }, { name: 3.1, value: 3 },
{ name: 3.11, value: 4 }, { name: 3.11, value: 4 },

View File

@ -100,6 +100,7 @@ import { BarChartWidgetComponent } from '@home/components/widget/lib/chart/bar-c
import { PolarAreaWidgetComponent } from '@home/components/widget/lib/chart/polar-area-widget.component'; import { PolarAreaWidgetComponent } from '@home/components/widget/lib/chart/polar-area-widget.component';
import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/radar-chart-widget.component'; import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/radar-chart-widget.component';
import { MobileAppQrcodeWidgetComponent } from '@home/components/widget/lib/mobile-app-qrcode-widget.component'; import { MobileAppQrcodeWidgetComponent } from '@home/components/widget/lib/mobile-app-qrcode-widget.component';
import { RestConnectorSecurityComponent } from '@home/components/widget/lib/gateway/connectors-configuration/rest-connector-secuirity/rest-connector-security.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -168,13 +169,14 @@ import { MobileAppQrcodeWidgetComponent } from '@home/components/widget/lib/mobi
PolarAreaWidgetComponent, PolarAreaWidgetComponent,
RadarChartWidgetComponent RadarChartWidgetComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
SharedModule, SharedModule,
RpcWidgetsModule, RpcWidgetsModule,
HomePageWidgetsModule, HomePageWidgetsModule,
SharedHomeComponentsModule SharedHomeComponentsModule,
], RestConnectorSecurityComponent
],
exports: [ exports: [
EntitiesTableWidgetComponent, EntitiesTableWidgetComponent,
AlarmsTableWidgetComponent, AlarmsTableWidgetComponent,