Delete timeseries UI implementation (#8932)

This commit is contained in:
Ruslan Vasylkiv 2023-07-13 15:24:45 +03:00 committed by GitHub
parent 4b7cc4571d
commit 2f1290e7e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 357 additions and 29 deletions

View File

@ -50,10 +50,11 @@ export class AttributeService {
}
public deleteEntityTimeseries(entityId: EntityId, timeseries: Array<AttributeData>, deleteAllDataForKeys = false,
startTs?: number, endTs?: number, config?: RequestConfig): Observable<any> {
startTs?: number, endTs?: number, rewriteLatestIfDeleted = false, deleteLatest = false,
config?: RequestConfig): Observable<any> {
const keys = timeseries.map(attribute => encodeURIComponent(attribute.key)).join(',');
let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` +
`?keys=${keys}&deleteAllDataForKeys=${deleteAllDataForKeys}`;
`?keys=${keys}&deleteAllDataForKeys=${deleteAllDataForKeys}&rewriteLatestIfDeleted=${rewriteLatestIfDeleted}&deleteLatest=${deleteLatest}`;
if (isDefinedAndNotNull(startTs)) {
url += `&startTs=${startTs}`;
}
@ -63,6 +64,12 @@ export class AttributeService {
return this.http.delete(url, defaultHttpOptionsFromConfig(config));
}
public deleteEntityLatestTimeseries(entityId: EntityId, timeseries: Array<AttributeData>, config?: RequestConfig): Observable<any> {
const keys = timeseries.map(attribute => encodeURIComponent(attribute.key)).join(',');
let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/latest/delete?keys=${keys}`;
return this.http.delete(url, defaultHttpOptionsFromConfig(config));
}
public saveEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array<AttributeData>,
config?: RequestConfig): Observable<any> {
const attributesData: {[key: string]: any} = {};
@ -103,7 +110,8 @@ export class AttributeService {
});
let deleteEntityTimeseriesObservable: Observable<any>;
if (deleteTimeseries.length) {
deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, true, null, null, config);
deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, true,
null, null, false, false, config);
} else {
deleteEntityTimeseriesObservable = of(null);
}

View File

@ -93,6 +93,14 @@
(click)="deleteAttributes($event)">
<mat-icon>delete</mat-icon>
</button>
<button [fxShow]="attributeScope === latestTelemetryTypes.LATEST_TELEMETRY && dataSource.selection.selected.length > 1"
class="button-widget-action"
mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'action.delete' | translate }}"
matTooltipPosition="above"
(click)="deleteTimeseries($event)">
<mat-icon>delete</mat-icon>
</button>
<button mat-raised-button color="accent"
class="button-widget-action"
[disabled]="isLoading$ | async"
@ -198,6 +206,10 @@
<span [fxShow]="!isClientSideTelemetryTypeMap.get(attributeScope)">
<mat-icon>edit</mat-icon>
</span>
<button mat-icon-button [fxShow]="attributeScope === latestTelemetryTypes.LATEST_TELEMETRY"
(click)="deleteTimeseries($event, attribute)">
<mat-icon>delete</mat-icon>
</button>
</div>
</mat-cell>
</ng-container>

View File

@ -38,7 +38,7 @@ import { TranslateService } from '@ngx-translate/core';
import { MatDialog } from '@angular/material/dialog';
import { DialogService } from '@core/services/dialog.service';
import { Direction, SortOrder } from '@shared/models/page/sort-order';
import { fromEvent, merge } from 'rxjs';
import { fromEvent, merge, Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
import { EntityId } from '@shared/models/id/entity-id';
import {
@ -48,7 +48,7 @@ import {
isClientSideTelemetryType,
LatestTelemetry,
TelemetryType,
telemetryTypeTranslations,
telemetryTypeTranslations, TimeseriesDeleteStrategy,
toTelemetryType
} from '@shared/models/telemetry/telemetry.models';
import { AttributeDatasource } from '@home/models/datasource/attribute-datasource';
@ -82,10 +82,14 @@ import {
AddWidgetToDashboardDialogComponent,
AddWidgetToDashboardDialogData
} from '@home/components/attribute/add-widget-to-dashboard-dialog.component';
import { deepClone } from '@core/utils';
import { deepClone, isUndefinedOrNull } from '@core/utils';
import { Filters } from '@shared/models/query/query.models';
import { hidePageSizePixelValue } from '@shared/models/constants';
import { ResizeObserver } from '@juggle/resize-observer';
import {
DELETE_TIMESERIES_PANEL_DATA,
DeleteTimeseriesPanelComponent, DeleteTimeseriesPanelData
} from '@home/components/attribute/delete-timeseries-panel.component';
@Component({
@ -378,6 +382,79 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
});
}
deleteTimeseries($event: Event, attribute?: AttributeData) {
if ($event) {
$event.stopPropagation();
}
const isMultipleDeletion = isUndefinedOrNull(attribute);
const target = $event.target || $event.srcElement || $event.currentTarget;
const config = new OverlayConfig();
config.backdropClass = 'cdk-overlay-transparent-backdrop';
config.hasBackdrop = true;
const connectedPosition: ConnectedPosition = {
originX: 'start',
originY: 'top',
overlayX: 'end',
overlayY: 'top'
};
config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
.withPositions([connectedPosition]);
config.maxWidth = '488px';
config.width = '100%';
const overlayRef = this.overlay.create(config);
overlayRef.backdropClick().subscribe(() => {
overlayRef.dispose();
});
const providers: StaticProvider[] = [
{
provide: DELETE_TIMESERIES_PANEL_DATA,
useValue: {
isMultipleDeletion: isMultipleDeletion
} as DeleteTimeseriesPanelData
},
{
provide: OverlayRef,
useValue: overlayRef
}
];
const injector = Injector.create({parent: this.viewContainerRef.injector, providers});
const componentRef = overlayRef.attach(new ComponentPortal(DeleteTimeseriesPanelComponent,
this.viewContainerRef, injector));
componentRef.onDestroy(() => {
if (componentRef.instance.result !== null) {
const strategy = componentRef.instance.result;
const timeseries = isMultipleDeletion ? this.dataSource.selection.selected : [attribute];
let deleteAllDataForKeys = false;
let rewriteLatestIfDeleted = false;
let startTs = null;
let endTs = null;
let deleteLatest = false;
let task: Observable<any>;
if (strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY) {
deleteAllDataForKeys = true;
deleteLatest = true;
}
if (strategy === TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE) {
deleteAllDataForKeys = true;
}
if (strategy === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE) {
task = this.attributeService.deleteEntityLatestTimeseries(this.entityIdValue, timeseries);
}
if (strategy === TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD) {
startTs = componentRef.instance.startDateTime.getTime();
endTs = componentRef.instance.endDateTime.getTime();
rewriteLatestIfDeleted = componentRef.instance.rewriteLatestIfDeleted;
}
if (!task) {
task = this.attributeService.deleteEntityTimeseries(this.entityIdValue, timeseries, deleteAllDataForKeys,
startTs, endTs, rewriteLatestIfDeleted, deleteLatest);
}
task.subscribe(() => this.reloadAttributes());
}
});
}
deleteAttributes($event: Event) {
if ($event) {
$event.stopPropagation();

View File

@ -0,0 +1,74 @@
<!--
Copyright © 2016-2023 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="mat-elevation-z1" style="max-width: 488px">
<mat-toolbar>
<h2>{{ "attribute.delete-timeseries.delete-strategy" | translate }}</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<div style="padding: 16px; min-height: 188px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>attribute.delete-timeseries.strategy</mat-label>
<mat-select [(ngModel)]="strategy">
<mat-option *ngFor="let strategy of strategiesTranslationsMap.keys()" [value]="strategy">
{{ strategiesTranslationsMap.get(strategy) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<div *ngIf="isPeriodStrategy()">
<div fxLayout="row" fxLayoutGap="22px" fxLayout.xs="column" fxLayoutGap.xs="8px">
<mat-form-field class="mat-block">
<mat-label translate>attribute.delete-timeseries.start-time</mat-label>
<mat-datetimepicker-toggle [for]="startDateTimePicker" matPrefix></mat-datetimepicker-toggle>
<mat-datetimepicker #startDateTimePicker type="datetime" openOnFocus="true"></mat-datetimepicker>
<input required matInput [(ngModel)]="startDateTime" [matDatetimepicker]="startDateTimePicker"
(ngModelChange)="onStartDateTimeChange($event)">
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>attribute.delete-timeseries.ends-on</mat-label>
<mat-datetimepicker-toggle [for]="endDatePicker" matPrefix></mat-datetimepicker-toggle>
<mat-datetimepicker #endDatePicker type="datetime" openOnFocus="true"></mat-datetimepicker>
<input required matInput [(ngModel)]="endDateTime" [matDatetimepicker]="endDatePicker"
(ngModelChange)="onEndDateTimeChange($event)">
</mat-form-field>
</div>
<mat-slide-toggle [(ngModel)]="rewriteLatestIfDeleted">
{{ "attribute.delete-timeseries.rewrite-latest-value-if-deleted" | translate }}
</mat-slide-toggle>
</div>
</div>
<div fxLayout="row" class="tb-panel-actions">
<span fxFlex></span>
<button mat-button color="primary"
type="button"
(click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }}
</button>
<button mat-button mat-raised-button color="primary"
type="submit"
(click)="delete()">
{{ 'action.apply' | translate }}
</button>
</div>
</div>

View File

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2023 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%;
background-color: #fff;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3), 0px 2px 6px 2px rgba(0, 0, 0, 0.15);
border-radius: 4px;
}
:host ::ng-deep{
div .mat-toolbar {
background: none;
}
}

View File

@ -0,0 +1,100 @@
///
/// Copyright © 2016-2023 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, Inject, InjectionToken, OnInit } from '@angular/core';
import { OverlayRef } from '@angular/cdk/overlay';
import {
TimeseriesDeleteStrategy,
timeseriesDeleteStrategyTranslations
} from '@shared/models/telemetry/telemetry.models';
import { MINUTE } from '@shared/models/time/time.models';
export const DELETE_TIMESERIES_PANEL_DATA = new InjectionToken<any>('DeleteTimeseriesPanelData');
export interface DeleteTimeseriesPanelData {
isMultipleDeletion: boolean;
}
@Component({
selector: 'tb-delete-timeseries-panel',
templateUrl: './delete-timeseries-panel.component.html',
styleUrls: ['./delete-timeseries-panel.component.scss']
})
export class DeleteTimeseriesPanelComponent implements OnInit {
strategy: string = TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY;
result: string = null;
startDateTime: Date;
endDateTime: Date;
rewriteLatestIfDeleted: boolean = false;
strategiesTranslationsMap = timeseriesDeleteStrategyTranslations;
multipleDeletionStrategies = [
TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY,
TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE
];
constructor(@Inject(DELETE_TIMESERIES_PANEL_DATA) public data: DeleteTimeseriesPanelData,
public overlayRef: OverlayRef) { }
ngOnInit(): void {
let today = new Date();
this.startDateTime = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate());
this.endDateTime = today;
if (this.data.isMultipleDeletion) {
this.strategiesTranslationsMap = new Map(Array.from(this.strategiesTranslationsMap.entries())
.filter(([strategy]) => {
return this.multipleDeletionStrategies.includes(strategy);
}))
}
}
delete(): void {
this.result = this.strategy;
this.overlayRef.dispose();
}
cancel(): void {
this.overlayRef.dispose();
}
isPeriodStrategy(): boolean {
return this.strategy === TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD;
}
onStartDateTimeChange(newStartDateTime: Date) {
const endDateTimeTs = this.endDateTime.getTime();
if (newStartDateTime.getTime() >= endDateTimeTs) {
this.startDateTime = new Date(endDateTimeTs - MINUTE);
} else {
this.startDateTime = newStartDateTime;
}
}
onEndDateTimeChange(newEndDateTime: Date) {
const startDateTimeTs = this.startDateTime.getTime();
if (newEndDateTime.getTime() <= startDateTimeTs) {
this.endDateTime = new Date(startDateTimeTs + MINUTE);
} else {
this.endDateTime = newEndDateTime;
}
}
}

View File

@ -177,6 +177,7 @@ import {
} from '@home/components/widget/action/manage-widget-actions-dialog.component';
import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module';
import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module';
import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component';
@NgModule({
declarations:
@ -205,6 +206,7 @@ import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/ba
AttributeTableComponent,
AddAttributeDialogComponent,
EditAttributeValuePanelComponent,
DeleteTimeseriesPanelComponent,
AliasesEntitySelectPanelComponent,
AliasesEntitySelectComponent,
AliasesEntityAutocompleteComponent,

View File

@ -61,6 +61,13 @@ export enum TelemetryFeature {
TIMESERIES = 'TIMESERIES'
}
export enum TimeseriesDeleteStrategy {
DELETE_ALL_DATA_INCLUDING_KEY = 'DELETE_ALL_DATA_INCLUDING_KEY',
DELETE_OLD_DATA_EXCEPT_LATEST_VALUE = 'DELETE_OLD_DATA_EXCEPT_LATEST_VALUE',
DELETE_LATEST_VALUE = 'DELETE_LATEST_VALUE',
DELETE_DATA_FOR_TIME_PERIOD = 'DELETE_DATA_FOR_TIME_PERIOD'
}
export type TelemetryType = LatestTelemetry | AttributeScope;
export const toTelemetryType = (val: string): TelemetryType => {
@ -73,7 +80,7 @@ export const toTelemetryType = (val: string): TelemetryType => {
export const telemetryTypeTranslations = new Map<TelemetryType, string>(
[
[LatestTelemetry.LATEST_TELEMETRY, 'attribute.scope-latest-telemetry'],
[LatestTelemetry.LATEST_TELEMETRY, 'attribute.scope-telemetry'],
[AttributeScope.CLIENT_SCOPE, 'attribute.scope-client'],
[AttributeScope.SERVER_SCOPE, 'attribute.scope-server'],
[AttributeScope.SHARED_SCOPE, 'attribute.scope-shared']
@ -89,6 +96,15 @@ export const isClientSideTelemetryType = new Map<TelemetryType, boolean>(
]
);
export const timeseriesDeleteStrategyTranslations = new Map<TimeseriesDeleteStrategy, string>(
[
[TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY, 'attribute.delete-timeseries.all-data-including-key'],
[TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE, 'attribute.delete-timeseries.old-data-except-latest'],
[TimeseriesDeleteStrategy.DELETE_LATEST_VALUE, 'attribute.delete-timeseries.latest-value'],
[TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD, 'attribute.delete-timeseries.data-for-time-period']
]
)
export interface AttributeData {
lastUpdateTs?: number;
key: string;

View File

@ -632,7 +632,7 @@
"attributes": "Atributs",
"latest-telemetry": "Última telemetria",
"attributes-scope": "Abast dels atributs del dispositiu",
"scope-latest-telemetry": "Última telemetria",
"scope-telemetry": "Telemetria",
"scope-client": "Atributs del Client",
"scope-server": "Atributs del Servidor",
"scope-shared": "Atributs Compartits",

View File

@ -445,7 +445,7 @@
"attributes": "Atributy",
"latest-telemetry": "Poslední telemetrie",
"attributes-scope": "Rozsah atributů entity",
"scope-latest-telemetry": "Poslední telemetrie",
"scope-telemetry": "Telemetrie",
"scope-client": "Atributy klienta",
"scope-server": "Atributy serveru",
"scope-shared": "Sdílené atributy",

View File

@ -453,7 +453,7 @@
"attributes": "Attributter",
"latest-telemetry": "Seneste telemetri",
"attributes-scope": "Omfang af entitetsattributter",
"scope-latest-telemetry": "Seneste telemetri",
"scope-telemetry": "Telemetri",
"scope-client": "Klientattributter",
"scope-server": "Serverattributter",
"scope-shared": "Delte attributter",

View File

@ -324,7 +324,7 @@
"attributes": "Eigenschaften",
"latest-telemetry": "Neueste Telemetrie",
"attributes-scope": "Entitätseigenschaftsbereich",
"scope-latest-telemetry": "Neueste Telemetrie",
"scope-telemetry": "Telemetrie",
"scope-client": "Client Eigenschaften",
"scope-server": "Server Eigenschaften",
"scope-shared": "Gemeinsame Eigenschaften",

View File

@ -291,7 +291,7 @@
"attributes": "Χαρακτηριστικά",
"latest-telemetry": "Τελευταία τηλεμετρία",
"attributes-scope": "Πεδίο εφαρμογής Χαρακτηριστικών Οντότητας",
"scope-latest-telemetry": "Τελευταία τηλεμετρία",
"scope-telemetry": "Τηλεμετρία",
"scope-client": "Χαρακτηριστικά Client",
"scope-server": "Χαρακτηριστικά Server",
"scope-shared": "Κοινόχρηστα Χαρακτηριστικά",

View File

@ -691,7 +691,7 @@
"attributes": "Attributes",
"latest-telemetry": "Latest telemetry",
"attributes-scope": "Entity attributes scope",
"scope-latest-telemetry": "Latest telemetry",
"scope-telemetry": "Telemetry",
"scope-client": "Client attributes",
"scope-server": "Server attributes",
"scope-shared": "Shared attributes",
@ -717,7 +717,18 @@
"no-attributes-text": "No attributes found",
"no-telemetry-text": "No telemetry found",
"copy-key": "Copy key",
"copy-value": "Copy value"
"copy-value": "Copy value",
"delete-timeseries": {
"start-time": "Start time",
"ends-on": "Ends on",
"strategy": "Strategy",
"delete-strategy": "Delete strategy",
"all-data-including-key": "Delete all data including key",
"old-data-except-latest": "Delete old data except latest value",
"latest-value": "Delete latest value",
"data-for-time-period": "Delete data for time period",
"rewrite-latest-value-if-deleted": "Rewrite latest value if deleted"
}
},
"api-usage": {
"api-features": "API features",

View File

@ -667,7 +667,7 @@
"attributes": "Atributos",
"latest-telemetry": "Última telemetría",
"attributes-scope": "Alcance de los atributos del dispositivo",
"scope-latest-telemetry": "Última telemetría",
"scope-telemetry": "Telemetría",
"scope-client": "Atributos de Cliente",
"scope-server": "Atributos de Servidor",
"scope-shared": "Atributos Compartidos",

View File

@ -254,7 +254,7 @@
"attributes": "ويژگي ها",
"latest-telemetry": "آخرين سنجش",
"attributes-scope": "حوزه ويژگي هاي موجودي",
"scope-latest-telemetry": "آخرين سنجش",
"scope-telemetry": "تله متری",
"scope-client": "ويژگي هاي مشتري",
"scope-server": "ويژگي هاي سِروِر",
"scope-shared": "ويژگي هاي مشترک",

View File

@ -459,7 +459,7 @@
"next-widget": "Widget suivant",
"prev-widget": "Widget précédent",
"scope-client": "Attributs du client",
"scope-latest-telemetry": "Dernière télémétrie",
"scope-telemetry": "Télémétrie",
"scope-server": "Attributs du serveur",
"scope-shared": "Attributs partagés",
"selected-attributes": "{count, plural, =1 {1 attribut} other {# attributs} } sélectionnés",

View File

@ -276,7 +276,7 @@
"attributes": "Attributi",
"latest-telemetry": "Ultima telemetria",
"attributes-scope": "Visibilità attributi entità",
"scope-latest-telemetry": "Ultima telemetria",
"scope-telemetry": "Telemetria",
"scope-client": "Attributi client",
"scope-server": "Attributi server",
"scope-shared": "Attributi condivisi",

View File

@ -244,7 +244,7 @@
"attributes": "属性",
"latest-telemetry": "最新テレメトリ",
"attributes-scope": "エンティティ属性のスコープ",
"scope-latest-telemetry": "最新テレメトリ",
"scope-telemetry": "テレメトリ",
"scope-client": "クライアントの属性",
"scope-server": "サーバーの属性",
"scope-shared": "共有属性",

View File

@ -290,7 +290,7 @@
"attributes": "ატრიბუტები",
"latest-telemetry": "უახლესი ტელემეტრია",
"attributes-scope": "ობიექტის ატრიბუტების ფარგლები",
"scope-latest-telemetry": "უახლესი ტელემეტრია",
"scope-telemetry": "ტელემეტრია",
"scope-client": "კლიენტის ატრიბუტები",
"scope-server": "სერვერის ატრიბუტები",
"scope-shared": "ატრიბუტების გაზიარება",

View File

@ -408,7 +408,7 @@
"attributes": "속성",
"latest-telemetry": "최근 데이터",
"attributes-scope": "장치 속성 범위",
"scope-latest-telemetry": "최근 데이터",
"scope-telemetry": "원격 측정",
"scope-client": "클라이언트 속성",
"scope-server": "서버 속성",
"scope-shared": "공유 속성",

View File

@ -256,7 +256,7 @@
"attributes": "Attribūti",
"latest-telemetry": "Jaunākā telemetrija",
"attributes-scope": "Vienības atribūtu darbības joma",
"scope-latest-telemetry": "Jaunākā telemetrija",
"scope-telemetry": "Telemetrija",
"scope-client": "Klientu atribūti",
"scope-server": "Servera atribūti",
"scope-shared": "Dalītie atribūti",

View File

@ -309,7 +309,7 @@
"attributes": "Atributos",
"latest-telemetry": "Última telemetria",
"attributes-scope": "Escopo de atributos de entidade",
"scope-latest-telemetry": "Última telemetria",
"scope-telemetry": "Telemetria",
"scope-client": "Atributos do cliente",
"scope-server": "Atributos do servidor",
"scope-shared": "Atributos compartilhados",

View File

@ -285,7 +285,7 @@
"attributes": "Atribute",
"latest-telemetry": "Ultimele Date Telemetrice",
"attributes-scope": "Scop Atribute Entitate",
"scope-latest-telemetry": "Ultimele Date Telemetrice",
"scope-telemetry": "Telemetrie",
"scope-client": "Atribute Client",
"scope-server": "Atribute Server",
"scope-shared": "Atribute Partajate",

View File

@ -408,7 +408,7 @@
"attributes": "Lastnosti",
"latest-telemetry": "Najnovejša telemetrija",
"attributes-scope": "Obseg atributov entitete",
"scope-latest-telemetry": "Najnovejša telemetrija",
"scope-telemetry": "Telemetrija",
"scope-client": "Atributi odjemalca",
"scope-server": "Atributi strežnika",
"scope-shared": "Skupni atributi",

View File

@ -445,7 +445,7 @@
"attributes": "Öznitelikler",
"latest-telemetry": "Son telemetri",
"attributes-scope": "Varlık öznitelik kapsamı",
"scope-latest-telemetry": "Son telemetri",
"scope-telemetry": "telemetri",
"scope-client": "İstemci öznitelikler",
"scope-server": "Sunucu öznitelikler",
"scope-shared": "Paylaşılan öznitelikler",

View File

@ -342,7 +342,7 @@
"attributes": "Атрибути",
"latest-telemetry": "Остання телеметрія",
"attributes-scope": "Область видимості атрибутів",
"scope-latest-telemetry": "Остання телеметрія",
"scope-telemetry": "Телеметрія",
"scope-client": "Клієнтські атрибути",
"scope-server": "Серверні атрибути",
"scope-shared": "Спільні атрибути",

View File

@ -590,7 +590,7 @@
"attributes": "属性",
"latest-telemetry": "最新遥测数据",
"attributes-scope": "设备属性范围",
"scope-latest-telemetry": "最新遥测数据",
"scope-telemetry": "遥测",
"scope-client": "客户端属性",
"scope-server": "服务端属性",
"scope-shared": "共享属性",

View File

@ -519,7 +519,7 @@
"attributes": "屬性",
"latest-telemetry": "最新遙測",
"attributes-scope": "設備屬性範圍",
"scope-latest-telemetry": "最新遙測",
"scope-telemetry": "遙測",
"scope-client": "客戶端屬性",
"scope-server": "服務端屬性",
"scope-shared": "共享屬性",