Browse Source

Added to widgets bundles and widget functional preview and description

pull/4205/head
Vladyslav_Prykhodko 6 years ago
parent
commit
22a1d24291
  1. 23
      application/src/main/data/upgrade/3.2.1/schema_update.sql
  2. 1
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  3. 11
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  4. 15
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java
  5. 22
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java
  6. 3
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  7. 10
      dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeEntity.java
  8. 10
      dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java
  9. 8
      dao/src/main/resources/sql/schema-entities-hsql.sql
  10. 8
      dao/src/main/resources/sql/schema-entities.sql
  11. 2
      ui-ngx/src/app/core/http/widget.service.ts
  12. 2
      ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts
  13. 2
      ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts
  14. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  15. 6
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  16. 106
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html
  17. 39
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss
  18. 73
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts
  19. 7
      ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts
  20. 2
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  21. 10
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  22. 29
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html
  23. 10
      ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html
  24. 10
      ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts
  25. 1
      ui-ngx/src/app/shared/components/image-input.component.html
  26. 4
      ui-ngx/src/app/shared/components/image-input.component.scss
  27. 20
      ui-ngx/src/app/shared/components/image-input.component.ts
  28. 8
      ui-ngx/src/app/shared/models/widget.models.ts
  29. 1
      ui-ngx/src/app/shared/models/widgets-bundle.model.ts
  30. 56
      ui-ngx/src/app/shared/pipe/file-size.pipe.ts
  31. 1
      ui-ngx/src/app/shared/pipe/public-api.ts
  32. 4
      ui-ngx/src/app/shared/shared.module.ts
  33. 2
      ui-ngx/src/assets/locale/locale.constant-cs_CZ.json
  34. 2
      ui-ngx/src/assets/locale/locale.constant-de_DE.json
  35. 2
      ui-ngx/src/assets/locale/locale.constant-el_GR.json
  36. 10
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  37. 2
      ui-ngx/src/assets/locale/locale.constant-es_ES.json
  38. 2
      ui-ngx/src/assets/locale/locale.constant-fa_IR.json
  39. 2
      ui-ngx/src/assets/locale/locale.constant-fr_FR.json
  40. 2
      ui-ngx/src/assets/locale/locale.constant-it_IT.json
  41. 2
      ui-ngx/src/assets/locale/locale.constant-ja_JA.json
  42. 2
      ui-ngx/src/assets/locale/locale.constant-ka_GE.json
  43. 2
      ui-ngx/src/assets/locale/locale.constant-ko_KR.json
  44. 2
      ui-ngx/src/assets/locale/locale.constant-lv_LV.json
  45. 2
      ui-ngx/src/assets/locale/locale.constant-pt_BR.json
  46. 2
      ui-ngx/src/assets/locale/locale.constant-ro_RO.json
  47. 2
      ui-ngx/src/assets/locale/locale.constant-ru_RU.json
  48. 2
      ui-ngx/src/assets/locale/locale.constant-sl_SI.json
  49. 2
      ui-ngx/src/assets/locale/locale.constant-tr_TR.json
  50. 2
      ui-ngx/src/assets/locale/locale.constant-uk_UA.json
  51. 4
      ui-ngx/src/assets/locale/locale.constant-zh_CN.json
  52. 2
      ui-ngx/src/assets/locale/locale.constant-zh_TW.json

23
application/src/main/data/upgrade/3.2.1/schema_update.sql

@ -0,0 +1,23 @@
--
-- Copyright © 2016-2021 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.
--
ALTER TABLE widget_type
ADD COLUMN IF NOT EXISTS image varchar (1000000),
ADD COLUMN IF NOT EXISTS description varchar (255);
ALTER TABLE widgets_bundle
ADD COLUMN IF NOT EXISTS image varchar (1000000),
ADD COLUMN IF NOT EXISTS description varchar (255);

1
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -187,6 +187,7 @@ public class ThingsboardInstallService {
databaseEntitiesUpgradeService.upgradeDatabase("3.2.0");
case "3.2.1":
log.info("Upgrading ThingsBoard from version 3.2.1 to 3.3.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.2.1");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break;

11
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -434,6 +434,17 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.info("Schema updated.");
}
break;
case "3.2.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.1", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;");
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}

15
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java

@ -31,6 +31,8 @@ public class WidgetType extends BaseData<WidgetTypeId> implements HasTenantId {
private String bundleAlias;
private String alias;
private String name;
private String image;
private String description;
private transient JsonNode descriptor;
public WidgetType() {
@ -47,6 +49,8 @@ public class WidgetType extends BaseData<WidgetTypeId> implements HasTenantId {
this.bundleAlias = widgetType.getBundleAlias();
this.alias = widgetType.getAlias();
this.name = widgetType.getName();
this.image = widgetType.getImage();
this.description = widgetType.getDescription();
this.descriptor = widgetType.getDescriptor();
}
@ -82,6 +86,14 @@ public class WidgetType extends BaseData<WidgetTypeId> implements HasTenantId {
this.name = name;
}
public String getImage() { return image; }
public void setImage(String image) { this.image = image; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public JsonNode getDescriptor() {
return descriptor;
}
@ -97,9 +109,10 @@ public class WidgetType extends BaseData<WidgetTypeId> implements HasTenantId {
sb.append(", bundleAlias='").append(bundleAlias).append('\'');
sb.append(", alias='").append(alias).append('\'');
sb.append(", name='").append(name).append('\'');
sb.append(", image='").append(image).append('\'');
sb.append(", description='").append(description).append('\'');
sb.append(", descriptor=").append(descriptor);
sb.append('}');
return sb.toString();
}
}

22
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java

@ -29,7 +29,8 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
private TenantId tenantId;
private String alias;
private String title;
private byte[] image;
private String image;
private String description;
public WidgetsBundle() {
super();
@ -45,6 +46,7 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
this.alias = widgetsBundle.getAlias();
this.title = widgetsBundle.getTitle();
this.image = widgetsBundle.getImage();
this.description = widgetsBundle.getDescription();
}
public TenantId getTenantId() {
@ -71,14 +73,18 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
this.title = title;
}
public byte[] getImage() {
public String getImage() {
return image;
}
public void setImage(byte[] image) {
public void setImage(String image) {
this.image = image;
}
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
@Override
public String getSearchText() {
return getTitle();
@ -90,7 +96,8 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0);
result = 31 * result + (alias != null ? alias.hashCode() : 0);
result = 31 * result + (title != null ? title.hashCode() : 0);
result = 31 * result + Arrays.hashCode(image);
result = 31 * result + (image != null ? image.hashCode() : 0);
result = 31 * result + (description != null ? description.hashCode() : 0);
return result;
}
@ -105,7 +112,9 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
if (tenantId != null ? !tenantId.equals(that.tenantId) : that.tenantId != null) return false;
if (alias != null ? !alias.equals(that.alias) : that.alias != null) return false;
if (title != null ? !title.equals(that.title) : that.title != null) return false;
return Arrays.equals(image, that.image);
if (image != null ? !image.equals(that.image) : that.image != null) return false;
if (description != null ? !description.equals(that.image) : that.description != null) return false;
return true;
}
@Override
@ -114,7 +123,8 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
sb.append("tenantId=").append(tenantId);
sb.append(", alias='").append(alias).append('\'');
sb.append(", title='").append(title).append('\'');
sb.append(", image=").append(Arrays.toString(image));
sb.append(", image='").append(image).append('\'');
sb.append(", description='").append(description).append('\'');
sb.append('}');
return sb.toString();
}

3
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -304,6 +304,7 @@ public class ModelConstants {
public static final String WIDGETS_BUNDLE_ALIAS_PROPERTY = ALIAS_PROPERTY;
public static final String WIDGETS_BUNDLE_TITLE_PROPERTY = TITLE_PROPERTY;
public static final String WIDGETS_BUNDLE_IMAGE_PROPERTY = "image";
public static final String WIDGETS_BUNDLE_DESCRIPTION = "description";
public static final String WIDGETS_BUNDLE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "widgets_bundle_by_tenant_and_search_text";
public static final String WIDGETS_BUNDLE_BY_TENANT_AND_ALIAS_COLUMN_FAMILY_NAME = "widgets_bundle_by_tenant_and_alias";
@ -316,6 +317,8 @@ public class ModelConstants {
public static final String WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY = "bundle_alias";
public static final String WIDGET_TYPE_ALIAS_PROPERTY = ALIAS_PROPERTY;
public static final String WIDGET_TYPE_NAME_PROPERTY = "name";
public static final String WIDGET_TYPE_IMAGE_PROPERTY = "image";
public static final String WIDGET_TYPE_DESCRIPTION_PROPERTY = "description";
public static final String WIDGET_TYPE_DESCRIPTOR_PROPERTY = "descriptor";
public static final String WIDGET_TYPE_BY_TENANT_AND_ALIASES_COLUMN_FAMILY_NAME = "widget_type_by_tenant_and_aliases";

10
dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeEntity.java

@ -52,6 +52,12 @@ public final class WidgetTypeEntity extends BaseSqlEntity<WidgetType> implement
@Column(name = ModelConstants.WIDGET_TYPE_NAME_PROPERTY)
private String name;
@Column(name = ModelConstants.WIDGET_TYPE_IMAGE_PROPERTY)
private String image;
@Column(name = ModelConstants.WIDGET_TYPE_DESCRIPTION_PROPERTY)
private String description;
@Type(type="json")
@Column(name = ModelConstants.WIDGET_TYPE_DESCRIPTOR_PROPERTY)
private JsonNode descriptor;
@ -71,6 +77,8 @@ public final class WidgetTypeEntity extends BaseSqlEntity<WidgetType> implement
this.bundleAlias = widgetType.getBundleAlias();
this.alias = widgetType.getAlias();
this.name = widgetType.getName();
this.image = widgetType.getImage();
this.description = widgetType.getDescription();
this.descriptor = widgetType.getDescriptor();
}
@ -84,6 +92,8 @@ public final class WidgetTypeEntity extends BaseSqlEntity<WidgetType> implement
widgetType.setBundleAlias(bundleAlias);
widgetType.setAlias(alias);
widgetType.setName(name);
widgetType.setImage(image);
widgetType.setDescription(description);
widgetType.setDescriptor(descriptor);
return widgetType;
}

10
dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java

@ -48,6 +48,12 @@ public final class WidgetsBundleEntity extends BaseSqlEntity<WidgetsBundle> impl
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
@Column(name = ModelConstants.WIDGETS_BUNDLE_IMAGE_PROPERTY)
private String image;
@Column(name = ModelConstants.WIDGETS_BUNDLE_DESCRIPTION)
private String description;
public WidgetsBundleEntity() {
super();
}
@ -62,6 +68,8 @@ public final class WidgetsBundleEntity extends BaseSqlEntity<WidgetsBundle> impl
}
this.alias = widgetsBundle.getAlias();
this.title = widgetsBundle.getTitle();
this.image = widgetsBundle.getImage();
this.description = widgetsBundle.getDescription();
}
@Override
@ -83,6 +91,8 @@ public final class WidgetsBundleEntity extends BaseSqlEntity<WidgetsBundle> impl
}
widgetsBundle.setAlias(alias);
widgetsBundle.setTitle(title);
widgetsBundle.setImage(image);
widgetsBundle.setDescription(description);
return widgetsBundle;
}
}

8
dao/src/main/resources/sql/schema-entities-hsql.sql

@ -290,7 +290,9 @@ CREATE TABLE IF NOT EXISTS widget_type (
bundle_alias varchar(255),
descriptor varchar(1000000),
name varchar(255),
tenant_id uuid
tenant_id uuid,
image varchar(1000000),
description varchar(255)
);
CREATE TABLE IF NOT EXISTS widgets_bundle (
@ -299,7 +301,9 @@ CREATE TABLE IF NOT EXISTS widgets_bundle (
alias varchar(255),
search_text varchar(255),
tenant_id uuid,
title varchar(255)
title varchar(255),
image varchar(1000000),
description varchar(255)
);
CREATE TABLE IF NOT EXISTS entity_view (

8
dao/src/main/resources/sql/schema-entities.sql

@ -315,7 +315,9 @@ CREATE TABLE IF NOT EXISTS widget_type (
bundle_alias varchar(255),
descriptor varchar(1000000),
name varchar(255),
tenant_id uuid
tenant_id uuid,
image varchar(1000000),
description varchar(255)
);
CREATE TABLE IF NOT EXISTS widgets_bundle (
@ -324,7 +326,9 @@ CREATE TABLE IF NOT EXISTS widgets_bundle (
alias varchar(255),
search_text varchar(255),
tenant_id uuid,
title varchar(255)
title varchar(255),
image varchar(1000000),
description varchar(255)
);
CREATE TABLE IF NOT EXISTS entity_view (

2
ui-ngx/src/app/core/http/widget.service.ts

@ -144,6 +144,8 @@ export class WidgetService {
typeAlias: widgetTypeInfo.alias,
type: widgetTypeInfo.type,
title: widgetTypeInfo.widgetName,
image: widgetTypeInfo.image,
description: widgetTypeInfo.description,
sizeX,
sizeY,
row: top,

2
ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts

@ -44,6 +44,8 @@ export class WidgetEditorDashboardResolver implements Resolve<Dashboard> {
typeAlias: 'customWidget',
type: editWidgetInfo.type,
title: 'My widget',
image: null,
description: null,
sizeX: editWidgetInfo.sizeX * 2,
sizeY: editWidgetInfo.sizeY * 2,
row: 2,

2
ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts

@ -469,6 +469,8 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
typeAlias: widgetInfo.alias,
type: widgetInfo.type,
title: widgetInfo.widgetName,
image: widgetInfo.image,
description: widgetInfo.description,
sizeX,
sizeY,
row: 0,

3
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -253,7 +253,7 @@
fxFlex
required
[selectFirstBundle]="false"
[ngModel]="widgetsBundle"
[(ngModel)]="widgetsBundle"
(ngModelChange)="widgetsBundle = $event">
</tb-widgets-bundle-select>
</div>
@ -261,6 +261,7 @@
<tb-dashboard-widget-select *ngIf="isAddingWidget"
[aliasController]="dashboardCtx.aliasController"
[widgetsBundle]="widgetsBundle"
(widgetsBundleSelected)="widgetBundleSelected($event)"
(widgetSelected)="addWidgetFromType($event)">
</tb-dashboard-widget-select>
</tb-details-panel>

6
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -865,6 +865,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
typeAlias: widgetTypeInfo.alias,
type: widgetTypeInfo.type,
title: 'New widget',
image: null,
description: null,
sizeX: widgetTypeInfo.sizeX,
sizeY: widgetTypeInfo.sizeY,
config,
@ -1111,4 +1113,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
}
return widgetContextActions;
}
widgetBundleSelected(bundle: WidgetsBundle){
this.widgetsBundle = bundle;
}
}

106
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html

@ -15,75 +15,47 @@
limitations under the License.
-->
<div>
<mat-tab-group *ngIf="hasWidgetTypes()" class="tb-absolute-fill" fxFlex>
<mat-tab *ngIf="timeseriesWidgetTypes.length" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}">
<tb-dashboard [aliasController]="aliasController"
[widgets]="timeseriesWidgetTypes"
[widgetLayouts]="{}"
[isEdit]="false"
[isMobile]="true"
[disableWidgetInteraction]="true"
[isEditActionEnabled]="false"
[isExportActionEnabled]="false"
[isRemoveActionEnabled]="false"
[callbacks]="callbacks"></tb-dashboard>
</mat-tab>
<mat-tab *ngIf="latestWidgetTypes.length" style="height: 100%;" label="{{ 'widget.latest-values' | translate }}">
<tb-dashboard [aliasController]="aliasController"
[widgets]="latestWidgetTypes"
[widgetLayouts]="{}"
[isEdit]="false"
[isMobile]="true"
[disableWidgetInteraction]="true"
[isEditActionEnabled]="false"
[isExportActionEnabled]="false"
[isRemoveActionEnabled]="false"
[callbacks]="callbacks"></tb-dashboard>
</mat-tab>
<mat-tab *ngIf="rpcWidgetTypes.length" style="height: 100%;" label="{{ 'widget.rpc' | translate }}">
<tb-dashboard [aliasController]="aliasController"
[widgets]="rpcWidgetTypes"
[widgetLayouts]="{}"
[isEdit]="false"
[isMobile]="true"
[disableWidgetInteraction]="true"
[isEditActionEnabled]="false"
[isExportActionEnabled]="false"
[isRemoveActionEnabled]="false"
[callbacks]="callbacks"></tb-dashboard>
</mat-tab>
<mat-tab *ngIf="alarmWidgetTypes.length" style="height: 100%;" label="{{ 'widget.alarm' | translate }}">
<tb-dashboard [aliasController]="aliasController"
[widgets]="alarmWidgetTypes"
[widgetLayouts]="{}"
[isEdit]="false"
[isMobile]="true"
[disableWidgetInteraction]="true"
[isEditActionEnabled]="false"
[isExportActionEnabled]="false"
[isRemoveActionEnabled]="false"
[callbacks]="callbacks"></tb-dashboard>
</mat-tab>
<mat-tab *ngIf="staticWidgetTypes.length" style="height: 100%;" label="{{ 'widget.static' | translate }}">
<tb-dashboard [aliasController]="aliasController"
[widgets]="staticWidgetTypes"
[widgetLayouts]="{}"
[isEdit]="false"
[isMobile]="true"
[disableWidgetInteraction]="true"
[isEditActionEnabled]="false"
[isExportActionEnabled]="false"
[isRemoveActionEnabled]="false"
[callbacks]="callbacks"></tb-dashboard>
</mat-tab>
</mat-tab-group>
<div class="widget-select">
<div *ngIf="hasWidgetTypes()">
<button mat-raised-button (click)="backToSelectWidgetsBundle()" style="margin-bottom: 12px">
<mat-icon class="material-icons">undo</mat-icon>
{{ 'widget.all-bundles' | translate }}
</button>
<div fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
<div *ngFor="let widget of widgets" class="mat-card-container">
<mat-card fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="onWidgetClicked($event, widget)">
<div fxFlex="45">
<img class="preview" src="https://material.angular.io/assets/img/examples/shiba2.jpg" alt="{{ widget.title }}">
</div>
<div fxFlex fxLayout="column">
<mat-card-title>{{widget.title}}</mat-card-title>
<mat-card-subtitle>{{ 'widget.' + widget.type | translate }}</mat-card-subtitle>
<mat-card-content *ngIf="widgetsBundle.description">
{{ widget.description }}
</mat-card-content>
</div>
</mat-card>
</div>
</div>
</div>
<span translate *ngIf="widgetsBundle && !hasWidgetTypes()"
style="display: flex;"
fxLayoutAlign="center center"
class="mat-headline tb-absolute-fill">widgets-bundle.empty</span>
<span translate *ngIf="!widgetsBundle"
style="display: flex;"
fxLayoutAlign="center center"
class="mat-headline tb-absolute-fill">widget.select-widgets-bundle</span>
<div *ngIf="!widgetsBundle" fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
<div *ngFor="let widgetsBundle of widgetsBundles$ | async" class="mat-card-container">
<mat-card fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="selectBundle($event, widgetsBundle)">
<div fxFlex="45" *ngIf="isSystem(widgetsBundle)">
<img class="preview" src="https://material.angular.io/assets/img/examples/shiba2.jpg" alt="{{ widgetsBundle.title }}">
</div>
<div fxFlex fxLayout="column">
<mat-card-title>{{ widgetsBundle.title }}</mat-card-title>
<mat-card-subtitle *ngIf="isSystem(widgetsBundle)" translate>widgets-bundle.system</mat-card-subtitle>
<mat-card-content *ngIf="widgetsBundle.description">
{{ widgetsBundle.description }}
</mat-card-content>
</div>
</mat-card>
</div>
</div>
</div>

39
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss

@ -13,10 +13,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host ::ng-deep {
.mat-tab-group {
.mat-tab-body-wrapper {
height: 100%;
@import '../../../../../scss/constants';
:host{
.widget-select {
min-height: 100%;
padding: 12px 0 12px 12px;
background-color: #cfd8dc;
.mat-card-container {
flex: 0 0 100%;
max-width: 100%;
.mat-card {
cursor: pointer;
.preview {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
}
}
@media #{$mat-gt-xs} {
.mat-card-container {
flex: 0 0 50%;
max-width: 50%;
}
}
@media screen and (min-width: 2000px) {
.mat-card-container {
flex: 0 0 33.333333%;
max-width: 33.333333%;
}
}
}
}

73
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts

@ -19,9 +19,10 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { IAliasController } from '@core/api/widget-api.models';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { WidgetService } from '@core/http/widget.service';
import { Widget, widgetType } from '@shared/models/widget.models';
import { Widget } from '@shared/models/widget.models';
import { toWidgetInfo } from '@home/models/widget-component.models';
import { DashboardCallbacks } from '../../models/dashboard-component.models';
import { share } from 'rxjs/operators';
import { Observable } from 'rxjs';
@Component({
selector: 'tb-dashboard-widget-select',
@ -39,20 +40,20 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
@Output()
widgetSelected: EventEmitter<Widget> = new EventEmitter<Widget>();
timeseriesWidgetTypes: Array<Widget> = [];
latestWidgetTypes: Array<Widget> = [];
rpcWidgetTypes: Array<Widget> = [];
alarmWidgetTypes: Array<Widget> = [];
staticWidgetTypes: Array<Widget> = [];
@Output()
widgetsBundleSelected: EventEmitter<WidgetsBundle> = new EventEmitter<WidgetsBundle>();
widgets: Array<Widget> = [];
callbacks: DashboardCallbacks = {
onWidgetClicked: this.onWidgetClicked.bind(this)
};
widgetsBundles$: Observable<Array<WidgetsBundle>>;
constructor(private widgetsService: WidgetService) {
}
ngOnInit(): void {
this.widgetsBundles$ = this.widgetsService.getAllWidgetsBundles().pipe(
share()
);
}
ngOnChanges(changes: SimpleChanges): void {
@ -67,11 +68,7 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
}
private loadLibrary() {
this.timeseriesWidgetTypes.length = 0;
this.latestWidgetTypes.length = 0;
this.rpcWidgetTypes.length = 0;
this.alarmWidgetTypes.length = 0;
this.staticWidgetTypes.length = 0;
this.widgets.length = 0;
const bundleAlias = this.widgetsBundle.alias;
const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID;
this.widgetsService.getBundleWidgetTypes(bundleAlias,
@ -88,6 +85,8 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
typeAlias: widgetTypeInfo.alias,
type: widgetTypeInfo.type,
title: widgetTypeInfo.widgetName,
image: widgetTypeInfo.image,
description: widgetTypeInfo.description,
sizeX: widgetTypeInfo.sizeX,
sizeY: widgetTypeInfo.sizeY,
row: top,
@ -95,39 +94,35 @@ export class DashboardWidgetSelectComponent implements OnInit, OnChanges {
config: JSON.parse(widgetTypeInfo.defaultConfig)
};
widget.config.title = widgetTypeInfo.widgetName;
switch (widgetTypeInfo.type) {
case widgetType.timeseries:
this.timeseriesWidgetTypes.push(widget);
break;
case widgetType.latest:
this.latestWidgetTypes.push(widget);
break;
case widgetType.rpc:
this.rpcWidgetTypes.push(widget);
break;
case widgetType.alarm:
this.alarmWidgetTypes.push(widget);
break;
case widgetType.static:
this.staticWidgetTypes.push(widget);
break;
}
this.widgets.push(widget);
top += widget.sizeY;
});
}
);
}
hasWidgetTypes() {
return this.timeseriesWidgetTypes.length > 0 ||
this.latestWidgetTypes.length > 0 ||
this.rpcWidgetTypes.length > 0 ||
this.alarmWidgetTypes.length > 0 ||
this.staticWidgetTypes.length > 0;
hasWidgetTypes(): boolean {
return this.widgets.length > 0;
}
private onWidgetClicked($event: Event, widget: Widget, index: number): void {
onWidgetClicked($event: Event, widget: Widget): void {
this.widgetSelected.emit(widget);
}
isSystem(item: WidgetsBundle): boolean {
return item && item.tenantId.id === NULL_UUID;
}
selectBundle($event: Event, bundle: WidgetsBundle) {
$event.preventDefault();
this.widgetsBundle = bundle;
this.widgetsBundleSelected.emit(bundle);
}
backToSelectWidgetsBundle() {
this.widgetsBundle = null;
this.widgets.length = 0;
this.widgetsBundleSelected.emit(null);
}
}

7
ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts

@ -25,7 +25,6 @@ import {
Injector,
Input,
OnDestroy,
OnInit,
Output,
QueryList,
ViewChild,
@ -52,7 +51,7 @@ import { deepClone } from '@core/utils';
styleUrls: ['./entity-details-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EntityDetailsPanelComponent extends PageComponent implements OnInit, AfterViewInit, OnDestroy {
export class EntityDetailsPanelComponent extends PageComponent implements AfterViewInit, OnDestroy {
@Output()
closeEntityDetails = new EventEmitter<void>();
@ -140,10 +139,6 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit
return this.isEditValue;
}
ngOnInit(): void {
this.init();
}
private init() {
this.translations = this.entitiesTableConfig.entityTranslations;
this.resources = this.entitiesTableConfig.entityResources;

2
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -104,6 +104,8 @@ export class WidgetComponentService {
controllerScript: this.utils.editWidgetInfo.controllerScript,
settingsSchema: this.utils.editWidgetInfo.settingsSchema,
dataKeySettingsSchema: this.utils.editWidgetInfo.dataKeySettingsSchema,
image: this.utils.editWidgetInfo.image,
description: this.utils.editWidgetInfo.description,
defaultConfig: this.utils.editWidgetInfo.defaultConfig
}, new WidgetTypeId('1'), new TenantId( NULL_UUID ), 'customWidgetBundle'
);

10
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -347,6 +347,8 @@ export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescri
alias: string;
typeSettingsSchema?: string | any;
typeDataKeySettingsSchema?: string | any;
image: string;
description: string;
componentFactory?: ComponentFactory<IDynamicWidgetComponent>;
}
@ -375,6 +377,8 @@ export const MissingWidgetType: WidgetInfo = {
controllerScript: 'self.onInit = function() {}',
settingsSchema: '{}\n',
dataKeySettingsSchema: '{}\n',
image: null,
description: null,
defaultConfig: '{\n' +
'"title": "Widget type not found",\n' +
'"datasources": [],\n' +
@ -398,6 +402,8 @@ export const ErrorWidgetType: WidgetInfo = {
controllerScript: 'self.onInit = function() {}',
settingsSchema: '{}\n',
dataKeySettingsSchema: '{}\n',
image: null,
description: null,
defaultConfig: '{\n' +
'"title": "Widget failed to load",\n' +
'"datasources": [],\n' +
@ -424,6 +430,8 @@ export interface WidgetTypeInstance {
export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo {
return {
widgetName: widgetTypeEntity.name,
image: widgetTypeEntity.image,
description: widgetTypeEntity.description,
alias: widgetTypeEntity.alias,
type: widgetTypeEntity.descriptor.type,
sizeX: widgetTypeEntity.descriptor.sizeX,
@ -457,6 +465,8 @@ export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId:
bundleAlias,
alias: widgetInfo.alias,
name: widgetInfo.widgetName,
image: widgetInfo.image,
description: widgetInfo.description,
descriptor
};
}

29
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html

@ -117,7 +117,7 @@
<div #topPanel class="tb-split tb-split-vertical">
<div #topLeftPanel class="tb-split tb-content">
<mat-tab-group selectedIndex="1" dynamicHeight="true" style="width: 100%; height: 100%;">
<mat-tab label="{{ 'widget.resources' | translate }}" style="width: 100%; height: 100%;">
<mat-tab label="{{ 'widget.resources' | translate }}">
<div class="tb-resize-container" style="background-color: #fff;">
<div class="mat-padding">
<div fxFlex fxLayout="row" style="max-height: 40px;"
@ -153,7 +153,7 @@
</div>
</div>
</mat-tab>
<mat-tab label="{{ 'widget.html' | translate }}" style="width: 100%; height: 100%;">
<mat-tab label="{{ 'widget.html' | translate }}">
<div class="tb-resize-container" tb-fullscreen [fullscreen]="htmlFullscreen">
<div class="tb-editor-area-title-panel">
<button mat-button (click)="beautifyHtml()">
@ -169,7 +169,7 @@
<div #htmlInput></div>
</div>
</mat-tab>
<mat-tab label="{{ 'widget.css' | translate }}" style="width: 100%; height: 100%;">
<mat-tab label="{{ 'widget.css' | translate }}">
<div class="tb-resize-container" tb-fullscreen [fullscreen]="cssFullscreen">
<div class="tb-editor-area-title-panel">
<button mat-button (click)="beautifyCss()">
@ -189,7 +189,7 @@
</div>
<div #topRightPanel class="tb-split tb-content">
<mat-tab-group dynamicHeight="true" style="width: 100%; height: 100%;">
<mat-tab label="{{ 'widget.settings-schema' | translate }}" style="width: 100%; height: 100%;">
<mat-tab label="{{ 'widget.settings-schema' | translate }}">
<div class="tb-resize-container" tb-fullscreen [fullscreen]="jsonSettingsFullscreen">
<div class="tb-editor-area-title-panel">
<button mat-button (click)="beautifyJson()">
@ -205,7 +205,7 @@
<div #settingsJsonInput></div>
</div>
</mat-tab>
<mat-tab label="{{ 'widget.datakey-settings-schema' | translate }}" style="width: 100%; height: 100%;">
<mat-tab label="{{ 'widget.datakey-settings-schema' | translate }}">
<div class="tb-resize-container" tb-fullscreen [fullscreen]="jsonDataKeySettingsFullscreen">
<div class="tb-editor-area-title-panel">
<button mat-button (click)="beautifyDataKeyJson()">
@ -221,6 +221,25 @@
<div #dataKeySettingsJsonInput></div>
</div>
</mat-tab>
<mat-tab label="{{ 'widget.widget-settings' | translate }}">
<div class="tb-resize-container" style="background-color: #fff;">
<div class="mat-padding">
<tb-image-input fxFlex label="{{'widget.image-preview' | translate}}"
maxSizeByte="524288"
[(ngModel)]="widget.image"
(ngModelChange)="isDirty = true" >
</tb-image-input>
<mat-form-field class="mat-block">
<mat-label translate>widget.description</mat-label>
<textarea matInput #descriptionInput
[(ngModel)]="widget.description"
(ngModelChange)="isDirty = true"
rows="2" maxlength="255"></textarea>
<mat-hint align="end">{{descriptionInput.value?.length || 0}}/255</mat-hint>
</mat-form-field>
</div>
</div>
</mat-tab>
</mat-tab-group>
</div>
</div>

10
ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html

@ -45,6 +45,16 @@
{{ 'widgets-bundle.title-required' | translate }}
</mat-error>
</mat-form-field>
<tb-image-input fxFlex
label="{{'widgets-bundle.image-preview' | translate}}"
maxSizeByte="524288"
formControlName="image">
</tb-image-input>
<mat-form-field class="mat-block">
<mat-label translate>widgets-bundle.description</mat-label>
<textarea matInput formControlName="description" rows="2" maxlength="255" #descriptionInput></textarea>
<mat-hint align="end">{{descriptionInput.value?.length || 0}}/255</mat-hint>
</mat-form-field>
</fieldset>
</form>
</div>

10
ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts

@ -47,12 +47,18 @@ export class WidgetsBundleComponent extends EntityComponent<WidgetsBundle> {
buildForm(entity: WidgetsBundle): FormGroup {
return this.fb.group(
{
title: [entity ? entity.title : '', [Validators.required]]
title: [entity ? entity.title : '', [Validators.required]],
image: [entity ? entity.image : ''],
description: [entity ? entity.description : '', Validators.maxLength(255)]
}
);
}
updateForm(entity: WidgetsBundle) {
this.entityForm.patchValue({title: entity.title});
this.entityForm.patchValue({
title: entity.title,
image: entity.image,
description: entity.description
});
}
}

1
ui-ngx/src/app/shared/components/image-input.component.html

@ -42,4 +42,5 @@
</div>
</div>
</ng-container>
<div class="tb-hint" *ngIf="maxSizeByte" translate [translateParams]="{ size: maxSizeByte | fileSize}">dashboard.maximum-upload-file-size</div>
</div>

4
ui-ngx/src/app/shared/components/image-input.component.scss

@ -101,4 +101,8 @@ $previewSize: 100px !default;
}
}
}
.tb-hint{
margin-top: 8px;
}
}

20
ui-ngx/src/app/shared/components/image-input.component.ts

@ -24,6 +24,9 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { FlowDirective } from '@flowjs/ngx-flow';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { UtilsService } from '@core/services/utils.service';
import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core';
import { FileSizePipe } from '@shared/pipe/file-size.pipe';
@Component({
selector: 'tb-image-input',
@ -42,6 +45,9 @@ export class ImageInputComponent extends PageComponent implements AfterViewInit,
@Input()
label: string;
@Input()
maxSizeByte: number;
private requiredValue: boolean;
get required(): boolean {
@ -80,7 +86,10 @@ export class ImageInputComponent extends PageComponent implements AfterViewInit,
constructor(protected store: Store<AppState>,
private utils: UtilsService,
private sanitizer: DomSanitizer) {
private sanitizer: DomSanitizer,
private dialog: DialogService,
private translate: TranslateService,
private fileSize: FileSizePipe) {
super(store);
}
@ -88,6 +97,15 @@ export class ImageInputComponent extends PageComponent implements AfterViewInit,
this.autoUploadSubscription = this.flow.events$.subscribe(event => {
if (event.type === 'fileAdded') {
const file = (event.event[0] as flowjs.FlowFile).file;
if (this.maxSizeByte && this.maxSizeByte < file.size) {
this.dialog.alert(
this.translate.instant('dashboard.cannot-upload-file'),
this.translate.instant('dashboard.maximum-upload-file-size', {size: this.fileSize.transform(this.maxSizeByte)})
).subscribe(
() => { }
);
return false;
}
const reader = new FileReader();
reader.onload = (loadEvent) => {
if (typeof reader.result === 'string' && reader.result.startsWith('data:image/')) {

8
ui-ngx/src/app/shared/models/widget.models.ts

@ -63,7 +63,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
[
widgetType.latest,
{
name: 'widget.latest-values',
name: 'widget.latest',
icon: 'track_changes',
configHelpLinkId: 'widgetsConfigLatest',
template: {
@ -169,6 +169,8 @@ export interface WidgetType extends BaseData<WidgetTypeId> {
bundleAlias: string;
alias: string;
name: string;
image: string;
description: string;
descriptor: WidgetTypeDescriptor;
}
@ -408,6 +410,8 @@ export interface Widget {
sizeY: number;
row: number;
col: number;
image: string;
description: string;
config: WidgetConfig;
}
@ -426,7 +430,7 @@ export interface JsonSchema {
export interface JsonSettingsSchema {
schema?: JsonSchema;
form?: any[];
groupInfoes?: GroupInfo[]
groupInfoes?: GroupInfo[];
}
export interface WidgetPosition {

1
ui-ngx/src/app/shared/models/widgets-bundle.model.ts

@ -23,4 +23,5 @@ export interface WidgetsBundle extends BaseData<WidgetsBundleId> {
alias: string;
title: string;
image: string;
description: string;
}

56
ui-ngx/src/app/shared/pipe/file-size.pipe.ts

@ -0,0 +1,56 @@
///
/// Copyright © 2016-2021 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 { Pipe, PipeTransform } from '@angular/core';
type unit = 'bytes' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB';
type unitPrecisionMap = {
[u in unit]: number;
};
const defaultPrecisionMap: unitPrecisionMap = {
bytes: 0,
KB: 0,
MB: 1,
GB: 1,
TB: 2,
PB: 2
};
@Pipe({ name: 'fileSize' })
export class FileSizePipe implements PipeTransform {
private readonly units: unit[] = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
transform(bytes: number = 0, precision: number | unitPrecisionMap = defaultPrecisionMap): string {
if (isNaN(parseFloat(String(bytes))) || !isFinite(bytes)) {
return '?';
}
let unitIndex = 0;
while (bytes >= 1024) {
bytes /= 1024;
unitIndex++;
}
const unitSymbol = this.units[unitIndex];
if (typeof precision === 'number') {
return `${bytes.toFixed(+precision)} ${unitSymbol}`;
}
return `${bytes.toFixed(precision[unitSymbol])} ${unitSymbol}`;
}
}

1
ui-ngx/src/app/shared/pipe/public-api.ts

@ -20,3 +20,4 @@ export * from './keyboard-shortcut.pipe';
export * from './milliseconds-to-time-string.pipe';
export * from './nospace.pipe';
export * from './truncate.pipe';
export * from './file-size.pipe';

4
ui-ngx/src/app/shared/shared.module.ts

@ -135,6 +135,7 @@ import { EntityGatewaySelectComponent } from '@shared/components/entity/entity-g
import { QueueTypeListComponent } from '@shared/components/queue/queue-type-list.component';
import { ContactComponent } from '@shared/components/contact.component';
import { TimezoneSelectComponent } from '@shared/components/time/timezone-select.component';
import { FileSizePipe } from '@shared/pipe/file-size.pipe';
@NgModule({
providers: [
@ -144,6 +145,7 @@ import { TimezoneSelectComponent } from '@shared/components/time/timezone-select
HighlightPipe,
TruncatePipe,
TbJsonPipe,
FileSizePipe,
{
provide: FlowInjectionToken,
useValue: Flow
@ -217,6 +219,7 @@ import { TimezoneSelectComponent } from '@shared/components/time/timezone-select
HighlightPipe,
TruncatePipe,
TbJsonPipe,
FileSizePipe,
KeyboardShortcutPipe,
TbJsonToStringDirective,
JsonObjectEditDialogComponent,
@ -381,6 +384,7 @@ import { TimezoneSelectComponent } from '@shared/components/time/timezone-select
TruncatePipe,
TbJsonPipe,
KeyboardShortcutPipe,
FileSizePipe,
TranslateModule,
JsonObjectEditDialogComponent,
HistorySelectorComponent,

2
ui-ngx/src/assets/locale/locale.constant-cs_CZ.json

@ -2206,7 +2206,7 @@
"timeseries": "Časové řady",
"search-data": "Vyhledat data",
"no-data-found": "Žádná data nebyla nalezena",
"latest-values": "Poslední hodnoty",
"latest": "Poslední hodnoty",
"rpc": "Ovládací widget",
"alarm": "Widgety alarmu",
"static": "Statické widgety",

2
ui-ngx/src/assets/locale/locale.constant-de_DE.json

@ -1451,7 +1451,7 @@
"timeseries": "Zeitreihe",
"search-data": "Daten suchen",
"no-data-found": "Keine Daten gefunden",
"latest-values": "Neueste Werte",
"latest": "Neueste Werte",
"rpc": "Steuerungswidget",
"alarm": "Alarm-Widget",
"static": "Statisches Widget",

2
ui-ngx/src/assets/locale/locale.constant-el_GR.json

@ -2318,7 +2318,7 @@
"timeseries": "Χρονική σειρά",
"search-data": "Αναζήτηση δεδομένων",
"no-data-found": "Δεν βρέθηκαν δεδομένα",
"latest-values": "Τελευταίες αξίες",
"latest": "Τελευταίες αξίες",
"rpc": "Έλεγχος Widget",
"alarm": "Alarm widget",
"static": "Στατικό widget",

10
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -688,6 +688,8 @@
"background-size-mode": "Background size mode",
"no-image": "No image selected",
"drop-image": "Drop an image or click to select a file to upload.",
"maximum-upload-file-size": "Maximum upload file size: {{ size }}",
"cannot-upload-file": "Cannot upload file",
"settings": "Settings",
"columns-count": "Columns count",
"columns-count-required": "Columns count is required.",
@ -2197,6 +2199,7 @@
"widget": {
"widget-library": "Widgets Library",
"widget-bundle": "Widgets Bundle",
"all-bundles": "All bundles",
"select-widgets-bundle": "Select widgets bundle",
"management": "Widget management",
"editor": "Widget Editor",
@ -2209,7 +2212,7 @@
"timeseries": "Time series",
"search-data": "Search data",
"no-data-found": "No data found",
"latest-values": "Latest values",
"latest": "Latest values",
"rpc": "Control widget",
"alarm": "Alarm widget",
"static": "Static widget",
@ -2236,6 +2239,9 @@
"css": "CSS",
"settings-schema": "Settings schema",
"datakey-settings-schema": "Data key settings schema",
"widget-settings": "Widget settings",
"description": "Description",
"image-preview": "Image preview",
"javascript": "Javascript",
"js": "JS",
"remove-widget-type-title": "Are you sure you want to remove the widget type '{{widgetName}}'?",
@ -2278,6 +2284,8 @@
"delete": "Delete widgets bundle",
"title": "Title",
"title-required": "Title is required.",
"description": "Description",
"image-preview": "Image preview",
"add-widgets-bundle-text": "Add new widgets bundle",
"no-widgets-bundles-text": "No widgets bundles found",
"empty": "Widgets bundle is empty",

2
ui-ngx/src/assets/locale/locale.constant-es_ES.json

@ -2206,7 +2206,7 @@
"timeseries": "Series de tiempo",
"search-data": "Buscar datos",
"no-data-found": "No se han encontrado datos",
"latest-values": "Últimos valores",
"latest": "Últimos valores",
"rpc": "Widget de control",
"alarm": "Widget de Alarma",
"static": "Widget estático",

2
ui-ngx/src/assets/locale/locale.constant-fa_IR.json

@ -1420,7 +1420,7 @@
"timeseries": "سري هاي زماني",
"search-data": "جستجوي داده",
"no-data-found": "هيچ داده اي يافت نشد",
"latest-values": "آخرين مقادير",
"latest": "آخرين مقادير",
"rpc": "ويجت کنترل",
"alarm": "ويجت هشدار",
"static": "ويجت ايستا",

2
ui-ngx/src/assets/locale/locale.constant-fr_FR.json

@ -1494,7 +1494,7 @@
"export": "Exporter widget",
"html": "HTML",
"javascript": "Javascript",
"latest-values": "Dernières valeurs",
"latest": "Dernières valeurs",
"management": "Gestion des widgets",
"missing-widget-title-error": "Le titre du widget doit être spécifié!",
"no-data-found": "Aucune donnée trouvée",

2
ui-ngx/src/assets/locale/locale.constant-it_IT.json

@ -1463,7 +1463,7 @@
"timeseries": "Time series",
"search-data": "Cerca dati",
"no-data-found": "Nessun dato trovato",
"latest-values": "Ultimi valori",
"latest": "Ultimi valori",
"rpc": "Control widget",
"alarm": "Alarm widget",
"static": "Static widget",

2
ui-ngx/src/assets/locale/locale.constant-ja_JA.json

@ -1305,7 +1305,7 @@
"timeseries": "時系列",
"search-data": "検索データ",
"no-data-found": "何もデータが見つかりませんでした",
"latest-values": "最新の値",
"latest": "最新の値",
"rpc": "コントロールウィジェット",
"alarm": "アラームウィジェット",
"static": "静的ウィジェット",

2
ui-ngx/src/assets/locale/locale.constant-ka_GE.json

@ -1551,7 +1551,7 @@
"timeseries": "მონაცემთა სერია",
"search-data": "საძიებო მონაცემები",
"no-data-found": "მონაცემი ვერ მოიძებნა",
"latest-values": "უახლესი მნიშვნელობები",
"latest": "უახლესი მნიშვნელობები",
"rpc": "ვიჯეტის კონტროლი",
"alarm": "ვიჯეტის განგაში",
"static": "სტატიკური ვიჯეტი",

2
ui-ngx/src/assets/locale/locale.constant-ko_KR.json

@ -2202,7 +2202,7 @@
"timeseries": "시계열",
"search-data": "데이터 검색",
"no-data-found": "아무 데이터도 없습니다",
"latest-values": "최근 값",
"latest": "최근 값",
"rpc": "컨트롤 위젯",
"alarm": "알람 위젯",
"static": "상태 위젯",

2
ui-ngx/src/assets/locale/locale.constant-lv_LV.json

@ -1468,7 +1468,7 @@
"timeseries": "Laika sērijas",
"search-data": "Meklēt datus",
"no-data-found": "Nav datu atrasti",
"latest-values": "Pedējās vērtības",
"latest": "Pedējās vērtības",
"rpc": "Kontroles logrīks",
"alarm": "Trauksmes logrīks",
"static": "Statiskais logrīks",

2
ui-ngx/src/assets/locale/locale.constant-pt_BR.json

@ -1777,7 +1777,7 @@
"timeseries": "Intervalos de tempo",
"search-data": "Pesquisar dados",
"no-data-found": "Nenhum dado encontrado",
"latest-values": "Últimos valores",
"latest": "Últimos valores",
"rpc": "Widget de controle",
"alarm": "Widget de alarme",
"static": "Widget estático",

2
ui-ngx/src/assets/locale/locale.constant-ro_RO.json

@ -1535,7 +1535,7 @@
"timeseries": "Serii Temporale",
"search-data": "Caută Date",
"no-data-found": "Nu Au Fost Găsite Date",
"latest-values": "Ultimele Valori",
"latest": "Ultimele Valori",
"rpc": "Widget Control ",
"alarm": "Widget Alarmă",
"static": "Widget Static",

2
ui-ngx/src/assets/locale/locale.constant-ru_RU.json

@ -1541,7 +1541,7 @@
"timeseries": "Телеметрия",
"search-data": "Поиск данных",
"no-data-found": "Данные не найдено",
"latest-values": "Последние значения",
"latest": "Последние значения",
"rpc": "Управляющий виджет",
"alarm": "Виджет оповещений",
"static": "Статический виджет",

2
ui-ngx/src/assets/locale/locale.constant-sl_SI.json

@ -2202,7 +2202,7 @@
"timeseries": "Časovne serije",
"search-data": "Iskanje podatkov",
"no-data-found": "Podatkov ni mogoče najti",
"latest-values": "Najnovejše vrednosti",
"latest": "Najnovejše vrednosti",
"rpc": "Nadzorni pripomoček",
"alarm": "Pripomoček za alarm",
"static": "Statični pripomoček",

2
ui-ngx/src/assets/locale/locale.constant-tr_TR.json

@ -1464,7 +1464,7 @@
"timeseries": "Zaman serisi",
"search-data": "Arama verileri",
"no-data-found": "Veri bulunamadı",
"latest-values": "Son değerler",
"latest": "Son değerler",
"rpc": "Kontrol göstergesi",
"alarm": "Alarm göstergesi",
"static": "Statik gösterge",

2
ui-ngx/src/assets/locale/locale.constant-uk_UA.json

@ -2111,7 +2111,7 @@
"timeseries": "Телеметрія",
"search-data": "Пошук даних",
"no-data-found": "Даних не знайдено",
"latest-values": "Останні значення",
"latest": "Останні значення",
"rpc": "Керуючий віджет",
"alarm": "Віджет сигнала тривоги",
"static": "Статичний віджет",

4
ui-ngx/src/assets/locale/locale.constant-zh_CN.json

@ -2323,7 +2323,7 @@
"html": "HTML",
"javascript": "Javascript",
"js": "JS",
"latest-values": "最新值",
"latest": "最新值",
"management": "管理部件",
"missing-widget-title-error": "部件标题必须指定!",
"no-data": "小部件上没有要显示的数据",
@ -2500,4 +2500,4 @@
"value": "价值"
}
}
}
}

2
ui-ngx/src/assets/locale/locale.constant-zh_TW.json

@ -1396,7 +1396,7 @@
"timeseries": "時間序列",
"search-data": "搜尋資料",
"no-data-found": "沒有找到資料",
"latest-values": "最新值",
"latest": "最新值",
"rpc": "控件部件",
"alarm": "警告部件",
"static": "靜態部件",

Loading…
Cancel
Save