33 changed files with 9663 additions and 2816 deletions
@ -1,43 +1,150 @@ |
|||
# ThingsBoard |
|||
[](https://builds.thingsboard.io/viewType.html?buildTypeId=ThingsBoard_Build&guest=1) |
|||
 |
|||
|
|||
<div align="center"> |
|||
|
|||
# Open-source IoT platform for data collection, processing, visualization, and device management. |
|||
|
|||
</div> |
|||
<br> |
|||
<div align="center"> |
|||
|
|||
💡 [Get started](https://thingsboard.io/docs/getting-started-guides/helloworld/) • 🌐 [Website](https://thingsboard.io/) • 📚 [Documentation](https://thingsboard.io/docs/) • 📔 [Blog](https://thingsboard.io/blog/) • ▶️ [Live demo](https://demo.thingsboard.io/signup) • 🔗 [LinkedIn](https://www.linkedin.com/company/thingsboard/posts/?feedView=all) |
|||
|
|||
</div> |
|||
|
|||
## 🚀 Installation options |
|||
|
|||
* Install ThingsBoard [On-premise](https://thingsboard.io/docs/user-guide/install/installation-options/?ceInstallType=onPremise) |
|||
* Try [ThingsBoard Cloud](https://thingsboard.io/installations/) |
|||
* or [Use our Live demo](https://demo.thingsboard.io/signup) |
|||
|
|||
## 💡 Getting started with ThingsBoard |
|||
|
|||
Check out our [Getting Started guide](https://thingsboard.io/docs/getting-started-guides/helloworld/) or [watch the video](https://www.youtube.com/watch?v=80L0ubQLXsc) to learn the basics of ThingsBoard and create your first dashboard! You will learn to: |
|||
|
|||
* Connect devices to ThingsBoard |
|||
* Push data from devices to ThingsBoard |
|||
* Build real-time dashboards |
|||
* Create a Customer and assign the dashboard with them. |
|||
* Define thresholds and trigger alarms |
|||
* Set up notifications via email, SMS, mobile apps, or integrate with third-party services. |
|||
|
|||
## ✨ Features |
|||
|
|||
<table> |
|||
<tr> |
|||
<td width="50%" valign="top"> |
|||
<br> |
|||
<div align="center"> |
|||
<img src="https://github.com/user-attachments/assets/255cca4f-b111-44e8-99ea-0af55f8e3681" alt="Provision and manage devices and assets" width="378" /> |
|||
<h3>Provision and manage <br> devices and assets</h3> |
|||
</div> |
|||
<div align="center"> |
|||
<p>Provision, monitor and control your IoT entities in secure way using rich server-side APIs. Define relations between your devices, assets, customers or any other entities.</p> |
|||
</div> |
|||
<br> |
|||
<div align="center"> |
|||
<a href="https://thingsboard.io/docs/user-guide/entities-and-relations/">Read more ➜</a> |
|||
</div> |
|||
<br> |
|||
</td> |
|||
<td width="50%" valign="top"> |
|||
<br> |
|||
<div align="center"> |
|||
<img src="https://github.com/user-attachments/assets/24b41d10-150a-42dd-ab1a-32ac9b5978c1" alt="Collect and visualize your data" width="378" /> |
|||
<h3>Collect and visualize <br> your data</h3> |
|||
</div> |
|||
<div align="center"> |
|||
<p>Collect and store telemetry data in scalable and fault-tolerant way. Visualize your data with built-in or custom widgets and flexible dashboards. Share dashboards with your customers.</p> |
|||
</div> |
|||
<br> |
|||
<div align="center"> |
|||
<a href="https://thingsboard.io/iot-data-visualization/">Read more ➜</a> |
|||
</div> |
|||
<br> |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
<td width="50%" valign="top"> |
|||
<br> |
|||
<div align="center"> |
|||
<img src="https://github.com/user-attachments/assets/6f2a6dd2-7b33-4d17-8b92-d1f995adda2c" alt="SCADA Dashboards" width="378" /> |
|||
<h3>SCADA Dashboards</h3> |
|||
</div> |
|||
<div align="center"> |
|||
<p>Monitor and control your industrial processes in real time with SCADA. Use SCADA symbols on dashboards to create and manage any workflow, offering full flexibility to design and oversee operations according to your requirements.</p> |
|||
</div> |
|||
<br> |
|||
<div align="center"> |
|||
<a href="https://thingsboard.io/use-cases/scada/">Read more ➜</a> |
|||
</div> |
|||
<br> |
|||
</td> |
|||
<td width="50%" valign="top"> |
|||
<br> |
|||
<div align="center"> |
|||
<img src="https://github.com/user-attachments/assets/c23dcc9b-aeba-40ef-9973-49b953fc1257" alt="Process and React" width="378" /> |
|||
<h3>Process and React</h3> |
|||
</div> |
|||
<div align="center"> |
|||
<p>Define data processing rule chains. Transform and normalize your device data. Raise alarms on incoming telemetry events, attribute updates, device inactivity and user actions.<br></p> |
|||
</div> |
|||
<br> |
|||
<br> |
|||
<div align="center"> |
|||
<a href="https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/">Read more ➜</a> |
|||
</div> |
|||
<br> |
|||
</td> |
|||
</tr> |
|||
</table> |
|||
|
|||
## ⚙️ Powerful IoT Rule Engine |
|||
|
|||
ThingsBoard allows you to create complex [Rule Chains](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/) to process data from your devices and match your application specific use cases. |
|||
|
|||
[](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/) |
|||
|
|||
<div align="center"> |
|||
|
|||
[**Read more about Rule Engine ➜**](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/) |
|||
|
|||
</div> |
|||
|
|||
## 📦 Real-Time IoT Dashboards |
|||
|
|||
ThingsBoard is a scalable, user-friendly, and device-agnostic IoT platform that speeds up time-to-market with powerful built-in solution templates. It enables data collection and analysis from any devices, saving resources on routine tasks and letting you focus on your solution’s unique aspects. See more our Use Cases [here](https://thingsboard.io/iot-use-cases/). |
|||
|
|||
[**Smart energy**](https://thingsboard.io/use-cases/smart-energy/) |
|||
|
|||
[](https://thingsboard.io/use-cases/smart-energy/) |
|||
|
|||
[**SCADA swimming pool**](https://thingsboard.io/use-cases/scada/) |
|||
|
|||
[](https://thingsboard.io/use-cases/scada/) |
|||
|
|||
[**Fleet tracking**](https://thingsboard.io/use-cases/fleet-tracking/) |
|||
|
|||
[](https://thingsboard.io/use-cases/fleet-tracking/) |
|||
|
|||
[**Smart farming**](https://thingsboard.io/use-cases/smart-farming/) |
|||
|
|||
[](https://thingsboard.io/use-cases/smart-farming/) |
|||
|
|||
ThingsBoard is an open-source IoT platform for data collection, processing, visualization, and device management. |
|||
|
|||
<img src="./img/logo.png?raw=true" width="100" height="100"> |
|||
|
|||
|
|||
## Documentation |
|||
|
|||
ThingsBoard documentation is hosted on [thingsboard.io](https://thingsboard.io/docs). |
|||
|
|||
## IoT use cases |
|||
|
|||
[**Smart energy**](https://thingsboard.io/smart-energy/) |
|||
[](https://thingsboard.io/smart-energy/) |
|||
|
|||
[**SCADA Swimming pool**](https://thingsboard.io/use-cases/scada/) |
|||
[](https://thingsboard.io/use-cases/scada/) |
|||
|
|||
[**Fleet tracking**](https://thingsboard.io/fleet-tracking/) |
|||
[](https://thingsboard.io/fleet-tracking/) |
|||
|
|||
[**Smart farming**](https://thingsboard.io/smart-farming/) |
|||
[](https://thingsboard.io/smart-farming/) |
|||
[**Smart metering**](https://thingsboard.io/smart-metering/) |
|||
|
|||
[**IoT Rule Engine**](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/) |
|||
[](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/) |
|||
[](https://thingsboard.io/smart-metering/) |
|||
|
|||
[**Smart metering**](https://thingsboard.io/smart-metering/) |
|||
[](https://thingsboard.io/smart-metering/) |
|||
<div align="center"> |
|||
|
|||
## Getting Started |
|||
[**Check more of our use cases ➜**](https://thingsboard.io/iot-use-cases/) |
|||
|
|||
Collect and Visualize your IoT data in minutes by following this [guide](https://thingsboard.io/docs/getting-started-guides/helloworld/). |
|||
</div> |
|||
|
|||
## Support |
|||
## 🫶 Support |
|||
|
|||
- [Stackoverflow](http://stackoverflow.com/questions/tagged/thingsboard) |
|||
To get support, please visit our [GitHub issues page](https://github.com/thingsboard/thingsboard/issues) |
|||
|
|||
## Licenses |
|||
## 📄 Licenses |
|||
|
|||
This project is released under [Apache 2.0 License](./LICENSE). |
|||
This project is released under [Apache 2.0 License](./LICENSE) |
|||
|
|||
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