committed by
GitHub
17 changed files with 9302 additions and 2688 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,54 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2025 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-api-usage-panel" [style.padding]="padding" [style]="backgroundStyle$ | async"> |
||||
|
<div class="tb-api-usage-overlay" [style]="overlayStyle"></div> |
||||
|
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> |
||||
|
<div class="tb-api-usage-content"> |
||||
|
<div class="api-items-list"> |
||||
|
@for (api of apiUsages; track api){ |
||||
|
<div class="api-item {{api.status.value}}" |
||||
|
[class.cursor-pointer]="api.state" |
||||
|
[class.active]="api.state && api.state === currentState" |
||||
|
(click)="updateState($event, api.state)"> |
||||
|
<div class="api-item-content"> |
||||
|
<div class="api-item-title">{{ api.label }}</div> |
||||
|
<div class="api-item-statistic"> |
||||
|
<div class="api-item-statistic-count">{{ api.current.value }} / {{ api.maxLimit.value }}</div> |
||||
|
<div class="api-item-statistic-progress"> |
||||
|
<mat-progress-bar mode="determinate" |
||||
|
[value]="api.progress" |
||||
|
></mat-progress-bar> |
||||
|
</div> |
||||
|
<div class="api-item-statistic-status"> |
||||
|
<div class="statistic-status">{{ ('api-usage.status.' + api.status.value) | translate }}</div> |
||||
|
<div class="statistic-icon"> |
||||
|
@if (api.status.value === 'enabled') { |
||||
|
<tb-icon class="tb-mat-18">check_circle</tb-icon> |
||||
|
} @else { |
||||
|
<tb-icon class="tb-mat-18">warning</tb-icon> |
||||
|
} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<mat-divider></mat-divider> |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,125 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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 "../../../../../../../scss/constants"; |
||||
|
|
||||
|
$enabled-color: #198038; |
||||
|
$disabled-color: #D12730; |
||||
|
$warning-color: #FAA405; |
||||
|
|
||||
|
.tb-no-notification-svg-color { |
||||
|
color: $tb-primary-color; |
||||
|
} |
||||
|
|
||||
|
.tb-api-usage-panel { |
||||
|
> div:not(.tb-api-usage-overlay) { |
||||
|
z-index: 1; |
||||
|
} |
||||
|
.tb-api-usage-overlay { |
||||
|
position: absolute; |
||||
|
top: 12px; |
||||
|
left: 12px; |
||||
|
bottom: 12px; |
||||
|
right: 12px; |
||||
|
} |
||||
|
.tb-api-usage-content { |
||||
|
height: 100%; |
||||
|
min-height: 0; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
.api-items-list { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
width: 100%; |
||||
|
.api-item { |
||||
|
&.enabled { |
||||
|
.api-item-statistic-status { |
||||
|
color: $enabled-color; |
||||
|
} |
||||
|
.mat-mdc-progress-bar { |
||||
|
--mdc-linear-progress-active-indicator-color: #{$enabled-color}; |
||||
|
} |
||||
|
} |
||||
|
&.disabled { |
||||
|
.api-item-statistic-status { |
||||
|
color: $disabled-color; |
||||
|
} |
||||
|
.mat-mdc-progress-bar { |
||||
|
--mdc-linear-progress-active-indicator-color: #{$disabled-color}; |
||||
|
} |
||||
|
} |
||||
|
&.warning { |
||||
|
.api-item-statistic-status { |
||||
|
color: $warning-color; |
||||
|
} |
||||
|
.mat-mdc-progress-bar { |
||||
|
--mdc-linear-progress-active-indicator-color: #{$warning-color}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:hover { |
||||
|
background-color: rgba(0, 0, 0, 0.06); |
||||
|
} |
||||
|
&.active { |
||||
|
background-color: rgba($tb-primary-color, 0.06); |
||||
|
.mat-divider { |
||||
|
--mat-divider-color: #{$tb-primary-color}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.api-item-content { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
gap: 8px; |
||||
|
padding: 5px 16px; |
||||
|
.api-item-title { |
||||
|
display: flex; |
||||
|
flex: 1; |
||||
|
font-size: 14px; |
||||
|
font-weight: 500; |
||||
|
color: $tb-primary-color; |
||||
|
} |
||||
|
.api-item-statistic { |
||||
|
display: flex; |
||||
|
flex: 1; |
||||
|
flex-direction: column; |
||||
|
&-count { |
||||
|
font-size: 14px; |
||||
|
color: rgba(0, 0, 0, 0.54); |
||||
|
} |
||||
|
&-status { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
&-progress { |
||||
|
--mdc-linear-progress-track-height: 8px; |
||||
|
--mdc-linear-progress-active-indicator-height: 8px; |
||||
|
padding: 4px 0; |
||||
|
.mat-mdc-progress-bar { |
||||
|
border-radius: 6px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,177 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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 { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, TemplateRef, ViewEncapsulation } from '@angular/core'; |
||||
|
import { WidgetContext } from '@home/models/widget-component.models'; |
||||
|
import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
import { ImagePipe } from '@shared/pipe/image.pipe'; |
||||
|
import { DomSanitizer } from '@angular/platform-browser'; |
||||
|
import { DataKey, DatasourceType, widgetType } from "@shared/models/widget.models"; |
||||
|
import { WidgetSubscriptionOptions } from "@core/api/widget-api.models"; |
||||
|
import { formattedDataFormDatasourceData } from "@core/utils"; |
||||
|
|
||||
|
import { UtilsService } from "@core/services/utils.service"; |
||||
|
import { |
||||
|
ApiUsageDataKeysSettings, |
||||
|
apiUsageDefaultSettings, |
||||
|
ApiUsageWidgetSettings |
||||
|
} from "@home/components/widget/lib/settings/cards/api-usage-settings.component.models"; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-api-usage-widget', |
||||
|
templateUrl: './api-usage-widget.component.html', |
||||
|
styleUrls: ['api-usage-widget.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class ApiUsageWidgetComponent implements OnInit, OnDestroy { |
||||
|
|
||||
|
settings: ApiUsageWidgetSettings; |
||||
|
|
||||
|
@Input() |
||||
|
ctx: WidgetContext; |
||||
|
|
||||
|
@Input() |
||||
|
widgetTitlePanel: TemplateRef<any>; |
||||
|
|
||||
|
backgroundStyle$: Observable<ComponentStyle>; |
||||
|
overlayStyle: ComponentStyle = {}; |
||||
|
padding: string; |
||||
|
|
||||
|
apiUsages = []; |
||||
|
currentState = ''; |
||||
|
noDataDisplayMessageText: string; |
||||
|
|
||||
|
private contentResize$: ResizeObserver; |
||||
|
private powers: {key: string, value: number}[] = [ |
||||
|
{ key: 'Q', value: 1e15 }, |
||||
|
{ key: 'T', value: 1e12 }, |
||||
|
{ key: 'B', value: 1e9 }, |
||||
|
{ key: 'M', value: 1e6 }, |
||||
|
{ key: 'K', value: 1e3 } |
||||
|
]; |
||||
|
|
||||
|
constructor(private imagePipe: ImagePipe, |
||||
|
private utils: UtilsService, |
||||
|
private sanitizer: DomSanitizer, |
||||
|
private cd: ChangeDetectorRef) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.ctx.$scope.apiUsageWidget = this; |
||||
|
this.settings = {...apiUsageDefaultSettings, ...this.ctx.settings}; |
||||
|
|
||||
|
this.parseApiUsages(); |
||||
|
|
||||
|
const ds = { |
||||
|
type: DatasourceType.entity, |
||||
|
name: '', |
||||
|
entityAliasId: this.settings.dsEntityAliasId, |
||||
|
dataKeys: this.getUniqueDataKeys(this.settings.dataKeys) |
||||
|
} |
||||
|
|
||||
|
const apiUsageSubscriptionOptions: WidgetSubscriptionOptions = { |
||||
|
datasources: [ds], |
||||
|
useDashboardTimewindow: false, |
||||
|
type: widgetType.latest, |
||||
|
callbacks: { |
||||
|
onDataUpdated: (subscription) => { |
||||
|
const data = formattedDataFormDatasourceData(subscription.data); |
||||
|
this.apiUsages.forEach(key => { |
||||
|
const progress = data[0][key.maxLimit.key] !== 0 ? Math.min(100, ((data[0][key.current.key] / data[0][key.maxLimit.key]) * 100)) : 0; |
||||
|
key.progress = isFinite(progress) ? progress : 0; |
||||
|
key.status.value = data[0][key.status.key] ? data[0][key.status.key].toLowerCase() : 'enabled'; |
||||
|
key.maxLimit.value = isFinite(data[0][key.maxLimit.key]) && data[0][key.maxLimit.key] !== 0 ? this.toShortNumber(data[0][key.maxLimit.key]) : '∞'; |
||||
|
key.current.value = isFinite(data[0][key.current.key]) ? this.toShortNumber(data[0][key.current.key]) : 0; |
||||
|
}); |
||||
|
this.cd.detectChanges(); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
this.ctx.subscriptionApi.createSubscription(apiUsageSubscriptionOptions, true).subscribe(); |
||||
|
|
||||
|
this.currentState = this.ctx.stateController.getStateId(); |
||||
|
this.ctx.stateController.stateId().subscribe((state) => { |
||||
|
this.ctx.updateParamsFromData(true); |
||||
|
this.currentState = state; |
||||
|
this.cd.markForCheck(); |
||||
|
}); |
||||
|
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); |
||||
|
this.overlayStyle = overlayStyle(this.settings.background.overlay); |
||||
|
this.padding = this.settings.background.overlay.enabled ? undefined : this.settings.padding; |
||||
|
} |
||||
|
|
||||
|
updateState($event: MouseEvent, stateName: string) { |
||||
|
$event?.preventDefault(); |
||||
|
if (stateName?.length) { |
||||
|
this.ctx.stateController.updateState(stateName, this.ctx.stateController.getStateParams(), this.ctx.isMobile); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
parseApiUsages() { |
||||
|
this.settings.dataKeys.forEach((key) => { |
||||
|
this.apiUsages.push({ |
||||
|
label: this.utils.customTranslation(key.label, key.label), |
||||
|
state: key.state, |
||||
|
progress: 0, |
||||
|
status: {key: key.status.name, value: 'enabled'}, |
||||
|
maxLimit: {key: key.maxLimit.name, value: '∞'}, |
||||
|
current: {key: key.current.name, value: 0}, |
||||
|
}); |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
getUniqueDataKeys(data: ApiUsageDataKeysSettings[]): DataKey[] { |
||||
|
const seenNames = new Set<string>(); |
||||
|
return data |
||||
|
.flatMap(item => [item.status, item.maxLimit, item.current]) |
||||
|
.filter(key => { |
||||
|
if (seenNames.has(key.name)) { |
||||
|
return false; |
||||
|
} |
||||
|
seenNames.add(key.name); |
||||
|
return true; |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
if (this.contentResize$) { |
||||
|
this.contentResize$.disconnect(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private toShortNumber(number: any, decimals = 1) { |
||||
|
if (!Number.isFinite(number) || number < 0) { |
||||
|
return '0'; |
||||
|
} |
||||
|
for (const power of this.powers) { |
||||
|
if (number >= power.value) { |
||||
|
const reduced = number / power.value; |
||||
|
const rounded = Number(reduced.toFixed(decimals)); |
||||
|
return `${rounded}${power.key}`; |
||||
|
} |
||||
|
} |
||||
|
return `${Number(number.toFixed(decimals))}`; |
||||
|
} |
||||
|
|
||||
|
public onInit() { |
||||
|
const borderRadius = this.ctx.$widgetElement.css('borderRadius'); |
||||
|
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; |
||||
|
this.cd.detectChanges(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2025 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 [formGroup]="dataKeyFormGroup" class="tb-form-table-row tb-api-usage-data-key-row"> |
||||
|
<div class="tb-source-field"> |
||||
|
<mat-form-field |
||||
|
class="tb-label-field tb-inline-field" appearance="outline" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-string-autocomplete [fetchOptionsFn]="fetchDashboardStates.bind(this)" |
||||
|
placeholderText="{{ 'widget-config.set' | translate }}" |
||||
|
style="flex: 1" |
||||
|
formControlName="state"> |
||||
|
</tb-string-autocomplete> |
||||
|
</div> |
||||
|
<div class="tb-data-key-fields"> |
||||
|
<tb-data-key-input |
||||
|
class="tb-data-key-field" |
||||
|
required |
||||
|
requiredText="{{ 'widgets.api-usage.status-required'}}" |
||||
|
[datasourceType]="DatasourceType.entity" |
||||
|
[entityAliasId]="dsEntityAliasId" |
||||
|
[aliasController]="context.aliasController" |
||||
|
[widgetType]="widgetType.latest" |
||||
|
[dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" |
||||
|
[callbacks]="context.callbacks" |
||||
|
[generateKey]="context.generateDataKey" |
||||
|
(keyEdit)="editKey('status')" |
||||
|
formControlName="status"> |
||||
|
</tb-data-key-input> |
||||
|
<tb-data-key-input |
||||
|
class="tb-data-key-field" |
||||
|
required |
||||
|
requiredText="{{ 'widgets.api-usage.limit-required'}}" |
||||
|
[datasourceType]="DatasourceType.entity" |
||||
|
[entityAliasId]="dsEntityAliasId" |
||||
|
[aliasController]="context.aliasController" |
||||
|
[widgetType]="widgetType.latest" |
||||
|
[dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" |
||||
|
[callbacks]="context.callbacks" |
||||
|
[generateKey]="context.generateDataKey" |
||||
|
(keyEdit)="editKey('maxLimit')" |
||||
|
formControlName="maxLimit"> |
||||
|
</tb-data-key-input> |
||||
|
<tb-data-key-input |
||||
|
class="tb-data-key-field" |
||||
|
required |
||||
|
requiredText="{{ 'widgets.api-usage.current-number-required'}}" |
||||
|
[datasourceType]="DatasourceType.entity" |
||||
|
[entityAliasId]="dsEntityAliasId" |
||||
|
[aliasController]="context.aliasController" |
||||
|
[widgetType]="widgetType.latest" |
||||
|
[dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" |
||||
|
[callbacks]="context.callbacks" |
||||
|
[generateKey]="context.generateDataKey" |
||||
|
(keyEdit)="editKey('current')" |
||||
|
formControlName="current"> |
||||
|
</tb-data-key-input> |
||||
|
</div> |
||||
|
<div class="tb-form-table-row-cell-buttons"> |
||||
|
<div class="tb-remove-button"> |
||||
|
<button type="button" |
||||
|
mat-icon-button |
||||
|
(click)="dataKeyRemoved.emit()" |
||||
|
matTooltip="{{ 'widgets.api-usage.delete-key' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>delete</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,56 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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 '../../../../../../../../scss/constants'; |
||||
|
|
||||
|
.tb-form-table-row.tb-api-usage-data-key-row { |
||||
|
|
||||
|
.tb-source-field { |
||||
|
flex: 1 1 25%; |
||||
|
display: flex; |
||||
|
gap: 12px; |
||||
|
.tb-label-field { |
||||
|
flex: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-data-key-fields { |
||||
|
display: flex; |
||||
|
gap: 12px; |
||||
|
min-width: 0; |
||||
|
flex: 1 1 50%; |
||||
|
} |
||||
|
|
||||
|
.tb-data-key-field { |
||||
|
flex: 1; |
||||
|
min-width: 0; |
||||
|
} |
||||
|
|
||||
|
.tb-remove-button { |
||||
|
width: 40px; |
||||
|
min-width: 40px; |
||||
|
} |
||||
|
|
||||
|
@media #{$mat-lt-lg} { |
||||
|
.tb-source-field { |
||||
|
flex-direction: column; |
||||
|
flex: 1 1 50%; |
||||
|
} |
||||
|
.tb-data-key-fields{ |
||||
|
flex-direction: column; |
||||
|
flex: 1 1 50%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,156 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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 { |
||||
|
ChangeDetectorRef, |
||||
|
Component, |
||||
|
DestroyRef, |
||||
|
EventEmitter, |
||||
|
forwardRef, |
||||
|
Input, |
||||
|
OnInit, |
||||
|
Output, |
||||
|
ViewEncapsulation |
||||
|
} from '@angular/core'; |
||||
|
import { |
||||
|
ControlValueAccessor, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
UntypedFormBuilder, |
||||
|
UntypedFormGroup, |
||||
|
Validators |
||||
|
} from '@angular/forms'; |
||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; |
||||
|
import { DataKey, DatasourceType, widgetType } from '@shared/models/widget.models'; |
||||
|
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; |
||||
|
import { |
||||
|
ApiUsageDataKeysSettings, |
||||
|
ApiUsageSettingsContext |
||||
|
} from "@home/components/widget/lib/settings/cards/api-usage-settings.component.models"; |
||||
|
import { Observable, of } from "rxjs"; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-api-usage-data-key-row', |
||||
|
templateUrl: './api-usage-data-key-row.component.html', |
||||
|
styleUrls: ['./api-usage-data-key-row.component.scss'], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => ApiUsageDataKeyRowComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class ApiUsageDataKeyRowComponent implements ControlValueAccessor, OnInit { |
||||
|
|
||||
|
DatasourceType = DatasourceType; |
||||
|
DataKeyType = DataKeyType; |
||||
|
|
||||
|
widgetType = widgetType; |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
dsEntityAliasId: string; |
||||
|
|
||||
|
@Input() |
||||
|
context: ApiUsageSettingsContext; |
||||
|
|
||||
|
@Output() |
||||
|
dataKeyRemoved = new EventEmitter(); |
||||
|
|
||||
|
dataKeyFormGroup: UntypedFormGroup; |
||||
|
|
||||
|
modelValue: ApiUsageDataKeysSettings; |
||||
|
|
||||
|
private propagateChange = (_val: any) => {}; |
||||
|
|
||||
|
constructor(private fb: UntypedFormBuilder, |
||||
|
private cd: ChangeDetectorRef, |
||||
|
private destroyRef: DestroyRef) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.dataKeyFormGroup = this.fb.group({ |
||||
|
label: [null, [Validators.required]], |
||||
|
state: [null, []], |
||||
|
status: [null, [Validators.required]], |
||||
|
maxLimit: [null, [Validators.required]], |
||||
|
current: [null, [Validators.required]] |
||||
|
}); |
||||
|
this.dataKeyFormGroup.valueChanges.pipe( |
||||
|
takeUntilDestroyed(this.destroyRef) |
||||
|
).subscribe( |
||||
|
() => this.updateModel() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(_fn: any): void { |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (isDisabled) { |
||||
|
this.dataKeyFormGroup.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.dataKeyFormGroup.enable({emitEvent: false}); |
||||
|
this.updateValidators(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writeValue(value: ApiUsageDataKeysSettings): void { |
||||
|
this.modelValue = value; |
||||
|
this.dataKeyFormGroup.patchValue( |
||||
|
{ |
||||
|
label: value?.label, |
||||
|
state: value?.state, |
||||
|
status: value?.status, |
||||
|
maxLimit: value?.maxLimit, |
||||
|
current: value?.current |
||||
|
}, {emitEvent: false} |
||||
|
); |
||||
|
this.updateValidators(); |
||||
|
this.cd.markForCheck(); |
||||
|
} |
||||
|
|
||||
|
editKey(keyType: 'status' | 'maxLimit' | 'current') { |
||||
|
const targetDataKey: DataKey = this.dataKeyFormGroup.get(keyType).value; |
||||
|
this.context.editKey(targetDataKey, this.dsEntityAliasId).subscribe( |
||||
|
(updatedDataKey) => { |
||||
|
if (updatedDataKey) { |
||||
|
this.dataKeyFormGroup.get(keyType).patchValue(updatedDataKey); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private updateValidators() { |
||||
|
} |
||||
|
|
||||
|
private updateModel() { |
||||
|
this.modelValue = {...this.modelValue, ...this.dataKeyFormGroup.value}; |
||||
|
this.propagateChange(this.modelValue); |
||||
|
} |
||||
|
|
||||
|
fetchDashboardStates(searchText?: string): Observable<Array<string>> { |
||||
|
return of(this.context.callbacks.fetchDashboardStates(searchText)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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 { IAliasController } from '@core/api/widget-api.models'; |
||||
|
import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models'; |
||||
|
import { DataKey, Widget, widgetType } from '@shared/models/widget.models'; |
||||
|
import { Observable } from "rxjs"; |
||||
|
import { BackgroundSettings, BackgroundType } from "@shared/models/widget-settings.models"; |
||||
|
import { DataKeyType } from "@shared/models/telemetry/telemetry.models"; |
||||
|
import { materialColors } from "@shared/models/material.models"; |
||||
|
|
||||
|
export interface ApiUsageSettingsContext { |
||||
|
aliasController: IAliasController; |
||||
|
callbacks: WidgetConfigCallbacks; |
||||
|
widget: Widget; |
||||
|
editKey: (key: DataKey, entityAliasId: string, WidgetType?: widgetType) => Observable<DataKey>; |
||||
|
generateDataKey: (key: DataKey) => DataKey; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
export interface ApiUsageWidgetSettings { |
||||
|
dsEntityAliasId: string; |
||||
|
dataKeys: ApiUsageDataKeysSettings[]; |
||||
|
targetDashboardState: string; |
||||
|
background: BackgroundSettings; |
||||
|
padding: string; |
||||
|
} |
||||
|
|
||||
|
export interface ApiUsageDataKeysSettings { |
||||
|
label: string; |
||||
|
state: string; |
||||
|
status: DataKey; |
||||
|
maxLimit: DataKey; |
||||
|
current: DataKey; |
||||
|
} |
||||
|
|
||||
|
const generateDataKey = (label: string, status: string, maxLimit: string, current: string) => { |
||||
|
return { |
||||
|
label, |
||||
|
state: '', |
||||
|
status: { |
||||
|
name: status, |
||||
|
label: status, |
||||
|
type: DataKeyType.timeseries, |
||||
|
funcBody: undefined, |
||||
|
settings: {}, |
||||
|
color: materialColors[0].value |
||||
|
}, |
||||
|
maxLimit: { |
||||
|
name: maxLimit, |
||||
|
label: maxLimit, |
||||
|
type: DataKeyType.timeseries, |
||||
|
funcBody: undefined, |
||||
|
settings: {}, |
||||
|
color: materialColors[0].value |
||||
|
}, |
||||
|
current: { |
||||
|
name: current, |
||||
|
label: current, |
||||
|
type: DataKeyType.timeseries, |
||||
|
funcBody: undefined, |
||||
|
settings: {}, |
||||
|
color: materialColors[0].value |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const apiUsageDefaultSettings: ApiUsageWidgetSettings = { |
||||
|
dsEntityAliasId: '', |
||||
|
dataKeys: [ |
||||
|
generateDataKey('{i18n:api-usage.transport-messages}', 'transportApiState', 'transportMsgLimit', 'transportMsgCount'), |
||||
|
generateDataKey('{i18n:api-usage.transport-data-points}', 'transportApiState', 'transportDataPointsLimit', 'transportDataPointsCount'), |
||||
|
generateDataKey('{i18n:api-usage.rule-engine-executions}', 'ruleEngineApiState', 'ruleEngineExecutionLimit', 'ruleEngineExecutionCount'), |
||||
|
generateDataKey('{i18n:api-usage.javascript-function-executions}', 'jsExecutionApiState', 'jsExecutionLimit', 'jsExecutionCount'), |
||||
|
generateDataKey('{i18n:api-usage.tbel-function-executions}', 'tbelExecutionApiState', 'tbelExecutionLimit', 'tbelExecutionCount'), |
||||
|
generateDataKey('{i18n:api-usage.data-points-storage-days}', 'dbApiState', 'storageDataPointsLimit', 'storageDataPointsCount'), |
||||
|
generateDataKey('{i18n:api-usage.alarms-created}', 'alarmApiState', 'createdAlarmsLimit', 'createdAlarmsCount'), |
||||
|
generateDataKey('{i18n:api-usage.emails}', 'emailApiState', 'emailLimit', 'emailCount'), |
||||
|
generateDataKey('{i18n:api-usage.sms}', 'notificationApiState', 'smsLimit', 'smsCount'), |
||||
|
], |
||||
|
targetDashboardState: 'default', |
||||
|
background: { |
||||
|
type: BackgroundType.color, |
||||
|
color: '#fff', |
||||
|
overlay: { |
||||
|
enabled: false, |
||||
|
color: 'rgba(255,255,255,0.72)', |
||||
|
blur: 3 |
||||
|
} |
||||
|
}, |
||||
|
padding: '0' |
||||
|
}; |
||||
|
|
||||
@ -0,0 +1,99 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2025 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. |
||||
|
|
||||
|
--> |
||||
|
<ng-container [formGroup]="apiUsageWidgetSettingsForm"> |
||||
|
<div class="tb-form-panel"> |
||||
|
<div class="tb-form-panel-title" translate>widget-config.datasource</div> |
||||
|
<tb-entity-alias-select |
||||
|
class="tb-entity-alias-field" |
||||
|
tbRequired |
||||
|
[appearance]="'outline'" |
||||
|
[aliasController]="context.aliasController" |
||||
|
formControlName="dsEntityAliasId" |
||||
|
[callbacks]="context.callbacks"> |
||||
|
</tb-entity-alias-select> |
||||
|
|
||||
|
<div class="tb-form-panel no-padding no-border"> |
||||
|
<div class="tb-form-table tb-map-data-layers"> |
||||
|
<div class="tb-form-table-header no-padding-right"> |
||||
|
<div class="tb-form-table-header-cell tb-key-header" translate>widgets.api-usage.label</div> |
||||
|
<div class="tb-form-table-header-cell tb-key-header" translate>widgets.api-usage.state-name</div> |
||||
|
<div class="tb-form-table-header-cell tb-key-header" translate>widgets.api-usage.status</div> |
||||
|
<div class="tb-form-table-header-cell tb-key-header" translate>widgets.api-usage.limit</div> |
||||
|
<div class="tb-form-table-header-cell tb-key-header" translate>widgets.api-usage.current-number</div> |
||||
|
<div class="tb-form-table-header-cell tb-actions-header"></div> |
||||
|
</div> |
||||
|
<div cdkDropList cdkDropListOrientation="vertical" |
||||
|
[cdkDropListDisabled]="!dragEnabled" |
||||
|
(cdkDropListDropped)="layerDrop($event)" *ngIf="dataKeysFormArray().controls?.length; else noDataLayers" class="tb-form-table-body"> |
||||
|
<div cdkDrag [cdkDragDisabled]="!dragEnabled" |
||||
|
*ngFor="let dataKeyControl of dataKeysFormArray().controls; trackBy: trackByDataKey; let $index = index;"> |
||||
|
<div class="tb-draggable-form-table-row"> |
||||
|
<tb-api-usage-data-key-row class="flex-1" |
||||
|
[context]="context" |
||||
|
[dsEntityAliasId]="apiUsageWidgetSettingsForm.get('dsEntityAliasId').value" |
||||
|
[formControl]="dataKeyControl" |
||||
|
(dataKeyRemoved)="removeDataKey($index)"> |
||||
|
</tb-api-usage-data-key-row> |
||||
|
<div class="tb-form-table-row-cell-buttons"> |
||||
|
<button [class.tb-hidden]="!dragEnabled" |
||||
|
mat-icon-button |
||||
|
type="button" |
||||
|
cdkDragHandle |
||||
|
matTooltip="{{ 'action.drag' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>drag_indicator</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div> |
||||
|
<button type="button" mat-stroked-button color="primary" (click)="addDataKey()"> |
||||
|
{{ 'widgets.api-usage.add-key' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<ng-template #noDataLayers> |
||||
|
<span class="tb-prompt flex items-center justify-center">{{ 'widgets.api-usage.no-key' | translate }}</span> |
||||
|
</ng-template> |
||||
|
<tb-string-autocomplete [fetchOptionsFn]="fetchDashboardStates.bind(this)" |
||||
|
additionalClass="tb-suffix-show-on-hover" |
||||
|
appearance="outline" |
||||
|
panelWidth="" |
||||
|
label="{{ 'widgets.api-usage.target-dashboard-state' | translate }}" |
||||
|
style="flex: 1" |
||||
|
formControlName="targetDashboardState"> |
||||
|
</tb-string-autocomplete> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-form-panel"> |
||||
|
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<div>{{ 'widgets.background.background' | translate }}</div> |
||||
|
<tb-background-settings formControlName="background"> |
||||
|
</tb-background-settings> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<div>{{ 'widget-config.card-padding' | translate }}</div> |
||||
|
<mat-form-field appearance="outline" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="padding" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
@ -0,0 +1,62 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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 '../../../../../../../../scss/constants'; |
||||
|
|
||||
|
.tb-map-data-layers { |
||||
|
.tb-form-table-header-cell { |
||||
|
&.tb-source-header { |
||||
|
flex: 1 1 50%; |
||||
|
} |
||||
|
&.tb-x-pos-header { |
||||
|
flex: 1 1 25%; |
||||
|
} |
||||
|
&.tb-y-pos-header { |
||||
|
flex: 1 1 25%; |
||||
|
} |
||||
|
&.tb-key-header { |
||||
|
flex: 1 1 50%; |
||||
|
} |
||||
|
&.tb-actions-header { |
||||
|
width: 80px; |
||||
|
min-width: 80px; |
||||
|
} |
||||
|
@media #{$mat-lt-lg} { |
||||
|
&.tb-source-header { |
||||
|
flex: 1 1 30%; |
||||
|
} |
||||
|
&.tb-x-pos-header, &.tb-y-pos-header { |
||||
|
flex: 1 1 35%; |
||||
|
} |
||||
|
&.tb-key-header { |
||||
|
flex: 1 1 70%; |
||||
|
} |
||||
|
} |
||||
|
@media #{$mat-xs} { |
||||
|
&.tb-x-pos-header, &.tb-y-pos-header { |
||||
|
display: none; |
||||
|
} |
||||
|
&.tb-key-header { |
||||
|
display: none; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-form-table-body { |
||||
|
tb-api-usage-data-key-row { |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,211 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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 { Component, forwardRef } from '@angular/core'; |
||||
|
import { |
||||
|
DataKey, |
||||
|
DataKeyConfigMode, |
||||
|
WidgetSettings, |
||||
|
WidgetSettingsComponent, |
||||
|
widgetType |
||||
|
} from '@shared/models/widget.models'; |
||||
|
import { |
||||
|
AbstractControl, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
UntypedFormArray, |
||||
|
UntypedFormBuilder, |
||||
|
UntypedFormGroup, |
||||
|
ValidationErrors, |
||||
|
ValidatorFn |
||||
|
} from '@angular/forms'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { |
||||
|
ApiUsageDataKeysSettings, |
||||
|
apiUsageDefaultSettings, |
||||
|
ApiUsageSettingsContext |
||||
|
} from "@home/components/widget/lib/settings/cards/api-usage-settings.component.models"; |
||||
|
import { deepClone } from "@core/utils"; |
||||
|
import { Observable, of } from "rxjs"; |
||||
|
import { |
||||
|
DataKeyConfigDialogComponent, |
||||
|
DataKeyConfigDialogData |
||||
|
} from "@home/components/widget/lib/settings/common/key/data-key-config-dialog.component"; |
||||
|
import { MatDialog } from "@angular/material/dialog"; |
||||
|
import { CdkDragDrop } from "@angular/cdk/drag-drop"; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-api-usage-widget-settings', |
||||
|
templateUrl: './api-usage-widget-settings.component.html', |
||||
|
styleUrls: ['./../widget-settings.scss', 'api-usage-widget-settings.component.scss'], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => ApiUsageWidgetSettingsComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
], |
||||
|
}) |
||||
|
export class ApiUsageWidgetSettingsComponent extends WidgetSettingsComponent { |
||||
|
|
||||
|
apiUsageWidgetSettingsForm: UntypedFormGroup; |
||||
|
|
||||
|
context: ApiUsageSettingsContext; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
private dialog: MatDialog, |
||||
|
private fb: UntypedFormBuilder) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.context = { |
||||
|
aliasController: this.aliasController, |
||||
|
callbacks: this.callbacks, |
||||
|
widget: this.widget, |
||||
|
editKey: this.editKey.bind(this), |
||||
|
generateDataKey: this.generateDataKey.bind(this) |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
protected doUpdateSettings(settingsForm: UntypedFormGroup, settings: WidgetSettings) { |
||||
|
settingsForm.setControl('dataKeys', this.prepareDataKeysFormArray(settings?.dataKeys), {emitEvent: false}); |
||||
|
} |
||||
|
|
||||
|
dataKeysFormArray(): UntypedFormArray { |
||||
|
return this.apiUsageWidgetSettingsForm.get('dataKeys') as UntypedFormArray; |
||||
|
} |
||||
|
|
||||
|
trackByDataKey(index: number): any { |
||||
|
return index; |
||||
|
} |
||||
|
|
||||
|
get dragEnabled(): boolean { |
||||
|
return this.dataKeysFormArray().controls.length > 1; |
||||
|
} |
||||
|
|
||||
|
layerDrop(event: CdkDragDrop<string[]>) { |
||||
|
const layer = this.dataKeysFormArray().at(event.previousIndex); |
||||
|
this.dataKeysFormArray().removeAt(event.previousIndex); |
||||
|
this.dataKeysFormArray().insert(event.currentIndex, layer); |
||||
|
} |
||||
|
|
||||
|
removeDataKey(index: number) { |
||||
|
(this.apiUsageWidgetSettingsForm.get('dataKeys') as UntypedFormArray).removeAt(index); |
||||
|
} |
||||
|
|
||||
|
addDataKey() { |
||||
|
const dataKey = { |
||||
|
label: '', |
||||
|
state: '', |
||||
|
status: null, |
||||
|
maxLimit: null, |
||||
|
current: null |
||||
|
}; |
||||
|
const dataKeysArray = this.apiUsageWidgetSettingsForm.get('dataKeys') as UntypedFormArray; |
||||
|
const dataKeyControl = this.fb.control(dataKey, [this.apiUsageDataKeyValidator()]); |
||||
|
dataKeysArray.push(dataKeyControl); |
||||
|
} |
||||
|
|
||||
|
protected settingsForm(): UntypedFormGroup { |
||||
|
return this.apiUsageWidgetSettingsForm; |
||||
|
} |
||||
|
|
||||
|
protected defaultSettings(): WidgetSettings { |
||||
|
return apiUsageDefaultSettings; |
||||
|
} |
||||
|
|
||||
|
protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { |
||||
|
return { |
||||
|
dsEntityAliasId: settings?.dsEntityAliasId, |
||||
|
dataKeys: settings?.dataKeys, |
||||
|
targetDashboardState: settings?.targetDashboardState, |
||||
|
background: settings?.background, |
||||
|
padding: settings.padding |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
protected onSettingsSet(settings: WidgetSettings) { |
||||
|
this.apiUsageWidgetSettingsForm = this.fb.group({ |
||||
|
dsEntityAliasId: [settings?.dsEntityAliasId], |
||||
|
dataKeys: this.prepareDataKeysFormArray(settings?.dataKeys), |
||||
|
targetDashboardState: [settings?.targetDashboardState], |
||||
|
background: [settings?.background, []], |
||||
|
padding: [settings.padding, []] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private prepareDataKeysFormArray(dataKeys: ApiUsageDataKeysSettings[]): UntypedFormArray { |
||||
|
const dataKeysControls: Array<AbstractControl> = []; |
||||
|
if (dataKeys) { |
||||
|
dataKeys.forEach((dataLayer) => { |
||||
|
dataKeysControls.push(this.fb.control(dataLayer, [this.apiUsageDataKeyValidator()])); |
||||
|
}); |
||||
|
} |
||||
|
return this.fb.array(dataKeysControls); |
||||
|
} |
||||
|
|
||||
|
protected validatorTriggers(): string[] { |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
protected updateValidators() { |
||||
|
} |
||||
|
|
||||
|
apiUsageDataKeyValidator = (): ValidatorFn => { |
||||
|
return (control: AbstractControl): ValidationErrors | null => { |
||||
|
const value: ApiUsageDataKeysSettings = control.value; |
||||
|
if (!value?.label || !value?.current || !value?.maxLimit || !value?.status) { |
||||
|
return { |
||||
|
dataKey: true |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
private editKey(key: DataKey, entityAliasId: string, _widgetType = widgetType.latest): Observable<DataKey> { |
||||
|
return this.dialog.open<DataKeyConfigDialogComponent, DataKeyConfigDialogData, DataKey>(DataKeyConfigDialogComponent, |
||||
|
{ |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
dataKey: deepClone(key), |
||||
|
dataKeyConfigMode: DataKeyConfigMode.general, |
||||
|
aliasController: this.aliasController, |
||||
|
widgetType: _widgetType, |
||||
|
entityAliasId, |
||||
|
showPostProcessing: true, |
||||
|
callbacks: this.callbacks, |
||||
|
hideDataKeyColor: true, |
||||
|
hideDataKeyDecimals: true, |
||||
|
hideDataKeyUnits: true, |
||||
|
widget: this.widget, |
||||
|
dashboard: null, |
||||
|
dataKeySettingsForm: null, |
||||
|
dataKeySettingsDirective: null |
||||
|
} |
||||
|
}).afterClosed(); |
||||
|
} |
||||
|
|
||||
|
private generateDataKey(key: DataKey): DataKey { |
||||
|
return this.callbacks.generateDataKey(key.name, key.type, null, false, null); |
||||
|
} |
||||
|
|
||||
|
fetchDashboardStates(searchText?: string): Observable<Array<string>> { |
||||
|
return of(this.callbacks.fetchDashboardStates(searchText)); |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue