Browse Source

merge with develop/3.5

pull/8304/head
YevhenBondarenko 4 years ago
parent
commit
059fad1421
  1. 77
      application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
  2. 18
      application/src/main/data/json/system/widget_bundles/maps.json
  3. 17
      application/src/main/data/upgrade/3.4.4/schema_update.sql
  4. 13
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  5. 103
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  6. 23
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  7. 70
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java
  8. 28
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java
  9. 66
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
  10. 202
      application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java
  11. 42
      application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java
  12. 23
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  13. 23
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  14. 4
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java
  15. 27
      application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java
  16. 18
      application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java
  17. 3
      application/src/main/resources/thingsboard.yml
  18. 5
      application/src/test/java/org/thingsboard/server/controller/BaseHomePageApiTest.java
  19. 188
      application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
  20. 23
      application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java
  21. 9
      common/cluster-api/src/main/proto/queue.proto
  22. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  23. 9
      common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java
  24. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  25. 18
      common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java
  26. 18
      common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java
  27. 8
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java
  28. 7
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java
  29. 37
      common/data/src/main/java/org/thingsboard/server/common/data/settings/AbstractUserDashboardInfo.java
  30. 37
      common/data/src/main/java/org/thingsboard/server/common/data/settings/LastVisitedDashboardInfo.java
  31. 36
      common/data/src/main/java/org/thingsboard/server/common/data/settings/StarredDashboardInfo.java
  32. 22
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardAction.java
  33. 46
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardsInfo.java
  34. 9
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java
  35. 44
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsCompositeKey.java
  36. 34
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java
  37. 17
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java
  38. 46
      common/util/src/main/java/org/thingsboard/common/util/SystemUtil.java
  39. 3
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java
  40. 42
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  41. 25
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitleEvictEvent.java
  42. 33
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesCaffeineCache.java
  43. 35
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java
  44. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  45. 13
      dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java
  46. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  47. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java
  48. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java
  49. 12
      dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java
  50. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java
  51. 7
      dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsCaffeineCache.java
  52. 7
      dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java
  53. 3
      dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsEvictEvent.java
  54. 7
      dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java
  55. 43
      dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java
  56. 14
      dao/src/main/resources/sql/schema-entities.sql
  57. 3
      dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java
  58. 11
      dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java
  59. 3
      dao/src/test/resources/application-test.properties
  60. 1
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CustomerPageHelper.java
  61. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/SideBarMenuViewElements.java
  62. 2
      pom.xml
  63. 17
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java
  64. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  65. 86
      ui-ngx/package.json
  66. 2
      ui-ngx/src/app/core/api/widget-api.models.ts
  67. 9
      ui-ngx/src/app/core/services/menu.service.ts
  68. 13
      ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts
  69. 7
      ui-ngx/src/app/modules/home/components/dashboard-page/states/entity-state-controller.component.ts
  70. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts
  71. 14
      ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts
  72. 6
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
  73. 7
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts
  74. 28
      ui-ngx/src/app/modules/home/components/widget/lib/settings/map/here-map-provider-settings.component.html
  75. 35
      ui-ngx/src/app/modules/home/components/widget/lib/settings/map/here-map-provider-settings.component.ts
  76. 4
      ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts
  77. 4
      ui-ngx/src/app/shared/models/notification.models.ts
  78. 10
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  79. 6
      ui-ngx/src/styles.scss
  80. 54
      ui-ngx/src/theme-overwrites.scss
  81. 18
      ui-ngx/src/theme.scss
  82. 2
      ui-ngx/src/tsconfig.app.json
  83. 685
      ui-ngx/src/typings/jquery.jstree.typings.d.ts
  84. 8
      ui-ngx/tsconfig.json
  85. 656
      ui-ngx/yarn.lock

77
application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json → application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json

@ -6,7 +6,8 @@
"firstRuleNodeId": null,
"root": true,
"debugMode": false,
"configuration": null
"configuration": null,
"externalId": null
},
"metadata": {
"firstNodeIndex": 0,
@ -23,7 +24,8 @@
"configuration": {
"persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false
}
},
"externalId": null
},
{
"additionalInfo": {
@ -35,7 +37,8 @@
"debugMode": false,
"configuration": {
"defaultTTL": 0
}
},
"externalId": null
},
{
"additionalInfo": {
@ -46,8 +49,10 @@
"name": "Save Client Attributes",
"debugMode": false,
"configuration": {
"scope": "CLIENT_SCOPE"
}
"scope": "CLIENT_SCOPE",
"notifyDevice": "false"
},
"externalId": null
},
{
"additionalInfo": {
@ -59,7 +64,8 @@
"debugMode": false,
"configuration": {
"version": 0
}
},
"externalId": null
},
{
"additionalInfo": {
@ -73,7 +79,8 @@
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
"externalId": null
},
{
"additionalInfo": {
@ -87,7 +94,8 @@
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
"externalId": null
},
{
"additionalInfo": {
@ -99,19 +107,34 @@
"debugMode": false,
"configuration": {
"timeoutInSeconds": 60
}
},
"externalId": null
},
{
"additionalInfo": {
"layoutX": 1129,
"layoutY": 52
"layoutX": 1126,
"layoutY": 104
},
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud",
"debugMode": false,
"configuration": {
"scope": "SERVER_SCOPE"
}
},
"externalId": null
},
{
"additionalInfo": {
"layoutX": 826,
"layoutY": 601
},
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud",
"debugMode": false,
"configuration": {
"scope": "SERVER_SCOPE"
},
"externalId": null
}
],
"connections": [
@ -132,24 +155,14 @@
},
{
"fromIndex": 3,
"toIndex": 6,
"type": "RPC Request to Device"
},
{
"fromIndex": 3,
"toIndex": 5,
"type": "Other"
"toIndex": 1,
"type": "Post telemetry"
},
{
"fromIndex": 3,
"toIndex": 2,
"type": "Post attributes"
},
{
"fromIndex": 3,
"toIndex": 1,
"type": "Post telemetry"
},
{
"fromIndex": 3,
"toIndex": 4,
@ -157,23 +170,23 @@
},
{
"fromIndex": 3,
"toIndex": 7,
"type": "Attributes Updated"
"toIndex": 5,
"type": "Other"
},
{
"fromIndex": 3,
"toIndex": 7,
"type": "Attributes Deleted"
"toIndex": 6,
"type": "RPC Request to Device"
},
{
"fromIndex": 3,
"toIndex": 7,
"type": "Timeseries Deleted"
"toIndex": 8,
"type": "Attributes Deleted"
},
{
"fromIndex": 3,
"toIndex": 7,
"type": "Timeseries Updated"
"toIndex": 8,
"type": "Attributes Updated"
}
],
"ruleChainConnections": null

18
application/src/main/data/json/system/widget_bundles/maps.json

File diff suppressed because one or more lines are too long

17
application/src/main/data/upgrade/3.4.4/schema_update.sql

@ -167,11 +167,12 @@ CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notific
ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255);
CREATE TABLE IF NOT EXISTS user_settings (
user_id uuid NOT NULL CONSTRAINT user_settings_pkey PRIMARY KEY,
settings varchar(100000),
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE
user_id uuid NOT NULL,
type VARCHAR(50) NOT NULL,
settings varchar(10000),
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE,
CONSTRAINT user_settings_pkey PRIMARY KEY (user_id, type)
);
-- ALARM INFO VIEW
DROP VIEW IF EXISTS alarm_info CASCADE;
@ -193,15 +194,15 @@ COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id
WHEN a.originator_type = 18 THEN (select name from edge where id = a.originator_id) END
, 'Deleted') originator_name,
COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id)
WHEN a.originator_type = 1 THEN (select COALESCE(title, email) from customer where id = a.originator_id)
WHEN a.originator_type = 1 THEN (select COALESCE(NULLIF(title, ''), email) from customer where id = a.originator_id)
WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id)
WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id)
WHEN a.originator_type = 4 THEN (select COALESCE(label, name) from asset where id = a.originator_id)
WHEN a.originator_type = 5 THEN (select COALESCE(label, name) from device where id = a.originator_id)
WHEN a.originator_type = 4 THEN (select COALESCE(NULLIF(label, ''), name) from asset where id = a.originator_id)
WHEN a.originator_type = 5 THEN (select COALESCE(NULLIF(label, ''), name) from device where id = a.originator_id)
WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id)
WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id)
WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id)
WHEN a.originator_type = 18 THEN (select COALESCE(label, name) from edge where id = a.originator_id) END
WHEN a.originator_type = 18 THEN (select COALESCE(NULLIF(label, ''), name) from edge where id = a.originator_id) END
, 'Deleted') as originator_label,
u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email as assignee_email
FROM alarm a

13
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -103,6 +103,7 @@ import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.settings.UserDashboardAction;
import org.thingsboard.server.common.data.util.ThrowingBiFunction;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
@ -146,6 +147,7 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.instructions.EdgeInstallService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.entitiy.TbNotificationEntityService;
import org.thingsboard.server.service.entitiy.user.TbUserSettingsService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
@ -168,6 +170,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.StringUtils.isNotEmpty;
@ -202,7 +205,7 @@ public abstract class BaseController {
protected UserService userService;
@Autowired
protected UserSettingsService userSettingsService;
protected TbUserSettingsService userSettingsService;
@Autowired
protected DeviceService deviceService;
@ -445,6 +448,14 @@ public abstract class BaseController {
}
}
protected <T> T checkEnumParameter(String name, String param, Function<String, T> valueOf) throws ThingsboardException {
try {
return valueOf.apply(param.toUpperCase());
} catch (IllegalArgumentException e) {
throw new ThingsboardException(name + " \"" + param + "\" is not supported!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
}
UUID toUUID(String id) throws ThingsboardException {
try {
return UUID.fromString(id);

103
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -38,12 +38,14 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.UserEmailInfo;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
@ -55,9 +57,13 @@ import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.UserSettings;
import org.thingsboard.server.common.data.settings.LastVisitedDashboardInfo;
import org.thingsboard.server.common.data.settings.UserDashboardAction;
import org.thingsboard.server.common.data.settings.UserDashboardsInfo;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.user.TbUserService;
import org.thingsboard.server.service.query.EntityQueryService;
@ -76,6 +82,7 @@ import java.util.Map;
import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.DEFAULT_DASHBOARD;
import static org.thingsboard.server.controller.ControllerConstants.HOME_DASHBOARD;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
@ -438,13 +445,14 @@ public class UserController extends BaseController {
}
@ApiOperation(value = "Save user settings (saveUserSettings)",
notes = "Save user settings represented in json format for authorized user. " )
notes = "Save user settings represented in json format for authorized user. ")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@PostMapping(value = "/user/settings")
public JsonNode saveUserSettings(@RequestBody JsonNode settings) throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
UserSettings userSettings = new UserSettings();
userSettings.setType(UserSettingsType.GENERAL);
userSettings.setSettings(settings);
userSettings.setUserId(currentUser.getId());
return userSettingsService.saveUserSettings(currentUser.getTenantId(), userSettings).getSettings();
@ -458,31 +466,108 @@ public class UserController extends BaseController {
@PutMapping(value = "/user/settings")
public void putUserSettings(@RequestBody JsonNode settings) throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
userSettingsService.updateUserSettings(currentUser.getTenantId(), currentUser.getId(), settings);
userSettingsService.updateUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL, settings);
}
@ApiOperation(value = "Get user settings (getUserSettings)",
notes = "Fetch the User settings based on authorized user. " )
notes = "Fetch the User settings based on authorized user. ")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/user/settings")
public JsonNode getUserSettings() throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId());
return userSettings == null ? JacksonUtil.newObjectNode(): userSettings.getSettings();
UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL);
return userSettings == null ? JacksonUtil.newObjectNode() : userSettings.getSettings();
}
@ApiOperation(value = "Delete user settings (deleteUserSettings)",
notes = "Delete user settings by specifying list of json element xpaths. \n " +
"Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter" )
"Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/user/settings/{paths}", method = RequestMethod.DELETE)
public void deleteUserSettings(@ApiParam(value = PATHS)
@PathVariable(PATHS) String paths) throws ThingsboardException {
@PathVariable(PATHS) String paths) throws ThingsboardException {
checkParameter(USER_ID, paths);
SecurityUser currentUser = getCurrentUser();
userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), Arrays.asList(paths.split(",")));
userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL, Arrays.asList(paths.split(",")));
}
@ApiOperation(value = "Update user settings (saveUserSettings)",
notes = "Update user settings for authorized user. Only specified json elements will be updated." +
"Example: you have such settings: {A:5, B:{C:10, D:20}}. Updating it with {B:{C:10, D:30}} will result in" +
"{A:5, B:{C:10, D:30}}. The same could be achieved by putting {B.D:30}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@PutMapping(value = "/user/settings/{type}")
public void putUserSettings(@ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".")
@PathVariable("type") String strType, @RequestBody JsonNode settings) throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf);
checkNotReserved(strType, type);
userSettingsService.updateUserSettings(currentUser.getTenantId(), currentUser.getId(), type, settings);
}
@ApiOperation(value = "Get user settings (getUserSettings)",
notes = "Fetch the User settings based on authorized user. ")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/user/settings/{type}")
public JsonNode getUserSettings(@ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".")
@PathVariable("type") String strType) throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf);
checkNotReserved(strType, type);
UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId(), type);
return userSettings == null ? JacksonUtil.newObjectNode() : userSettings.getSettings();
}
@ApiOperation(value = "Delete user settings (deleteUserSettings)",
notes = "Delete user settings by specifying list of json element xpaths. \n " +
"Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/user/settings/{type}/{paths}", method = RequestMethod.DELETE)
public void deleteUserSettings(@ApiParam(value = PATHS)
@PathVariable(PATHS) String paths,
@ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".")
@PathVariable("type") String strType) throws ThingsboardException {
checkParameter(USER_ID, paths);
UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf);
checkNotReserved(strType, type);
SecurityUser currentUser = getCurrentUser();
userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), type, Arrays.asList(paths.split(",")));
}
@ApiOperation(value = "Get information about last visited and starred dashboards (getLastVisitedDashboards)",
notes = "Fetch the list of last visited and starred dashboards. Both lists are limited to 10 items." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/user/dashboards")
public UserDashboardsInfo getUserDashboardsInfo() throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
return userSettingsService.findUserDashboardsInfo(currentUser.getTenantId(), currentUser.getId());
}
@ApiOperation(value = "Report action of User over the dashboard (reportUserDashboardAction)",
notes = "Report action of User over the dashboard. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/user/dashboards/{dashboardId}/{action}", method = RequestMethod.GET)
@ResponseBody
public UserDashboardsInfo reportUserDashboardAction(
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DashboardController.DASHBOARD_ID) String strDashboardId,
@ApiParam(value = "Dashboard action, one of: \"visit\", \"star\" or \"unstar\".")
@PathVariable("action") String strAction) throws ThingsboardException {
checkParameter(DashboardController.DASHBOARD_ID, strDashboardId);
checkParameter("action", strAction);
UserDashboardAction action = checkEnumParameter("Action", strAction, UserDashboardAction::valueOf);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
checkDashboardInfoId(dashboardId, Operation.READ);
SecurityUser currentUser = getCurrentUser();
return userSettingsService.reportUserDashboardAction(currentUser.getTenantId(), currentUser.getId(), dashboardId, action);
}
private void checkNotReserved(String strType, UserSettingsType type) throws ThingsboardException {
if (type.isReserved()) {
throw new ThingsboardException("Settings with type: " + strType + " are reserved for internal use!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
}
}

23
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -485,4 +485,27 @@ public abstract class BaseEdgeProcessor {
}
return customerId;
}
protected boolean isEntityExists(TenantId tenantId, EntityId entityId) {
switch (entityId.getEntityType()) {
case TENANT:
return tenantService.findTenantById(tenantId) != null;
case DEVICE:
return deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null;
case ASSET:
return assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null;
case ENTITY_VIEW:
return entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null;
case CUSTOMER:
return customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null;
case USER:
return userService.findUserById(tenantId, new UserId(entityId.getId())) != null;
case DASHBOARD:
return dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null;
case EDGE:
return edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())) != null;
default:
return false;
}
}
}

70
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java

@ -19,19 +19,19 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import java.util.UUID;
@ -43,51 +43,59 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor {
log.trace("[{}] processAlarmMsg [{}]", tenantId, alarmUpdateMsg);
EntityId originatorId = getAlarmOriginator(tenantId, alarmUpdateMsg.getOriginatorName(),
EntityType.valueOf(alarmUpdateMsg.getOriginatorType()));
AlarmId alarmId = new AlarmId(new UUID(alarmUpdateMsg.getIdMSB(), alarmUpdateMsg.getIdLSB()));
if (originatorId == null) {
log.warn("Originator not found for the alarm msg {}", alarmUpdateMsg);
return Futures.immediateFuture(null);
}
try {
Alarm existentAlarm = alarmService.findLatestActiveByOriginatorAndType(tenantId, originatorId, alarmUpdateMsg.getType());
switch (alarmUpdateMsg.getMsgType()) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
if (existentAlarm == null || existentAlarm.getStatus().isCleared()) {
existentAlarm = new Alarm();
existentAlarm.setTenantId(tenantId);
existentAlarm.setType(alarmUpdateMsg.getName());
existentAlarm.setOriginator(originatorId);
existentAlarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity()));
existentAlarm.setStartTs(alarmUpdateMsg.getStartTs());
existentAlarm.setClearTs(alarmUpdateMsg.getClearTs());
existentAlarm.setPropagate(alarmUpdateMsg.getPropagate());
}
Alarm alarm = new Alarm();
alarm.setId(alarmId);
alarm.setTenantId(tenantId);
alarm.setType(alarmUpdateMsg.getName());
alarm.setOriginator(originatorId);
alarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity()));
alarm.setStartTs(alarmUpdateMsg.getStartTs());
var alarmStatus = AlarmStatus.valueOf(alarmUpdateMsg.getStatus());
existentAlarm.setCleared(alarmStatus.isCleared());
existentAlarm.setAcknowledged(alarmStatus.isAck());
existentAlarm.setAckTs(alarmUpdateMsg.getAckTs());
existentAlarm.setEndTs(alarmUpdateMsg.getEndTs());
existentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()));
alarmService.createOrUpdateAlarm(existentAlarm);
break;
alarm.setClearTs(alarmUpdateMsg.getClearTs());
alarm.setPropagate(alarmUpdateMsg.getPropagate());
alarm.setCleared(alarmStatus.isCleared());
alarm.setAcknowledged(alarmStatus.isAck());
alarm.setAckTs(alarmUpdateMsg.getAckTs());
alarm.setEndTs(alarmUpdateMsg.getEndTs());
alarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()));
if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(alarmUpdateMsg.getMsgType())) {
alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm, null, alarmId));
} else {
alarmService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm));
}
return Futures.immediateFuture(null);
case ALARM_ACK_RPC_MESSAGE:
if (existentAlarm != null) {
alarmService.acknowledgeAlarm(tenantId, existentAlarm.getId(), alarmUpdateMsg.getAckTs());
Alarm alarmToAck = alarmService.findAlarmById(tenantId, alarmId);
if (alarmToAck != null) {
alarmService.acknowledgeAlarm(tenantId, alarmId, alarmUpdateMsg.getAckTs());
}
break;
return Futures.immediateFuture(null);
case ALARM_CLEAR_RPC_MESSAGE:
if (existentAlarm != null) {
alarmService.clearAlarm(tenantId, existentAlarm.getId(),
alarmUpdateMsg.getAckTs(), JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()));
Alarm alarmToClear = alarmService.findAlarmById(tenantId, alarmId);
if (alarmToClear != null) {
alarmService.clearAlarm(tenantId, alarmId, alarmUpdateMsg.getClearTs(),
JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()));
}
break;
return Futures.immediateFuture(null);
case ENTITY_DELETED_RPC_MESSAGE:
if (existentAlarm != null) {
alarmService.delAlarm(tenantId, existentAlarm.getId());
Alarm alarmToDelete = alarmService.findAlarmById(tenantId, alarmId);
if (alarmToDelete != null) {
alarmService.delAlarm(tenantId, alarmId);
}
break;
return Futures.immediateFuture(null);
case UNRECOGNIZED:
default:
return handleUnsupportedMsgType(alarmUpdateMsg.getMsgType());
}
return Futures.immediateFuture(null);
} catch (Exception e) {
log.error("[{}] Failed to process alarm update msg [{}]", tenantId, alarmUpdateMsg, e);
return Futures.immediateFailedFuture(e);

28
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java

@ -20,16 +20,9 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
@ -80,25 +73,4 @@ public abstract class BaseRelationProcessor extends BaseEdgeProcessor {
return Futures.immediateFailedFuture(e);
}
}
private boolean isEntityExists(TenantId tenantId, EntityId entityId) {
switch (entityId.getEntityType()) {
case DEVICE:
return deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null;
case ASSET:
return assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null;
case ENTITY_VIEW:
return entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null;
case CUSTOMER:
return customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null;
case USER:
return userService.findUserById(tenantId, new UserId(entityId.getId())) != null;
case DASHBOARD:
return dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null;
case EDGE:
return edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())) != null;
default:
return false;
}
}
}

66
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java

@ -90,42 +90,46 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
log.trace("[{}] processTelemetryMsg [{}]", tenantId, entityData);
List<ListenableFuture<Void>> result = new ArrayList<>();
EntityId entityId = constructEntityId(entityData.getEntityType(), entityData.getEntityIdMSB(), entityData.getEntityIdLSB());
if ((entityData.hasPostAttributesMsg() || entityData.hasPostTelemetryMsg() || entityData.hasAttributesUpdatedMsg()) && entityId != null) {
Pair<TbMsgMetaData, CustomerId> pair = getBaseMsgMetadataAndCustomerId(tenantId, entityId);
TbMsgMetaData metaData = pair.getKey();
CustomerId customerId = pair.getValue();
metaData.putValue(DataConstants.MSG_SOURCE_KEY, getMsgSourceKey());
if (entityData.hasPostAttributesMsg()) {
result.add(processPostAttributes(tenantId, customerId, entityId, entityData.getPostAttributesMsg(), metaData));
}
if (entityData.hasAttributesUpdatedMsg()) {
metaData.putValue("scope", entityData.getPostAttributeScope());
result.add(processAttributesUpdate(tenantId, customerId, entityId, entityData.getAttributesUpdatedMsg(), metaData));
}
if (entityData.hasPostTelemetryMsg()) {
result.add(processPostTelemetry(tenantId, customerId, entityId, entityData.getPostTelemetryMsg(), metaData));
}
if (EntityType.DEVICE.equals(entityId.getEntityType())) {
DeviceId deviceId = new DeviceId(entityId.getId());
if (entityId != null && isEntityExists(tenantId, entityId)) {
if ((entityData.hasPostAttributesMsg() || entityData.hasPostTelemetryMsg() || entityData.hasAttributesUpdatedMsg())) {
Pair<TbMsgMetaData, CustomerId> pair = getBaseMsgMetadataAndCustomerId(tenantId, entityId);
TbMsgMetaData metaData = pair.getKey();
CustomerId customerId = pair.getValue();
metaData.putValue(DataConstants.MSG_SOURCE_KEY, getMsgSourceKey());
if (entityData.hasPostAttributesMsg()) {
result.add(processPostAttributes(tenantId, customerId, entityId, entityData.getPostAttributesMsg(), metaData));
}
if (entityData.hasAttributesUpdatedMsg()) {
metaData.putValue("scope", entityData.getPostAttributeScope());
result.add(processAttributesUpdate(tenantId, customerId, entityId, entityData.getAttributesUpdatedMsg(), metaData));
}
if (entityData.hasPostTelemetryMsg()) {
result.add(processPostTelemetry(tenantId, customerId, entityId, entityData.getPostTelemetryMsg(), metaData));
}
if (EntityType.DEVICE.equals(entityId.getEntityType())) {
DeviceId deviceId = new DeviceId(entityId.getId());
long currentTs = System.currentTimeMillis();
long currentTs = System.currentTimeMillis();
TransportProtos.DeviceActivityProto deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setDeviceIdMSB(deviceId.getId().getMostSignificantBits())
.setDeviceIdLSB(deviceId.getId().getLeastSignificantBits())
.setLastActivityTime(currentTs).build();
TransportProtos.DeviceActivityProto deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setDeviceIdMSB(deviceId.getId().getMostSignificantBits())
.setDeviceIdLSB(deviceId.getId().getLeastSignificantBits())
.setLastActivityTime(currentTs).build();
log.trace("[{}][{}] device activity time is going to be updated, ts {}", tenantId, deviceId, currentTs);
log.trace("[{}][{}] device activity time is going to be updated, ts {}", tenantId, deviceId, currentTs);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(deviceId.getId(),
TransportProtos.ToCoreMsg.newBuilder().setDeviceActivityMsg(deviceActivityMsg).build()), null);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(deviceId.getId(),
TransportProtos.ToCoreMsg.newBuilder().setDeviceActivityMsg(deviceActivityMsg).build()), null);
}
}
}
if (entityData.hasAttributeDeleteMsg()) {
result.add(processAttributeDeleteMsg(tenantId, entityId, entityData.getAttributeDeleteMsg(), entityData.getEntityType()));
if (entityData.hasAttributeDeleteMsg()) {
result.add(processAttributeDeleteMsg(tenantId, entityId, entityData.getAttributeDeleteMsg(), entityData.getEntityType()));
}
} else {
log.warn("Skipping telemetry update msg because entity doesn't exists on edge, {}", entityData);
}
return result;
}

202
application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java

@ -0,0 +1,202 @@
/**
* 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.
*/
package org.thingsboard.server.service.entitiy.user;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.HasTitle;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.settings.AbstractUserDashboardInfo;
import org.thingsboard.server.common.data.settings.LastVisitedDashboardInfo;
import org.thingsboard.server.common.data.settings.StarredDashboardInfo;
import org.thingsboard.server.common.data.settings.UserDashboardAction;
import org.thingsboard.server.common.data.settings.UserDashboardsInfo;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.user.UserSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
@AllArgsConstructor
@Slf4j
public class DefaultTbUserSettingsService implements TbUserSettingsService {
private static final int MAX_DASHBOARD_INFO_LIST_SIZE = 10;
private static final Predicate<HasTitle> EMPTY_TITLE = i -> StringUtils.isEmpty(i.getTitle());
private final UserSettingsService settingsService;
private final DashboardService dashboardService;
@Override
public UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings) {
return settingsService.saveUserSettings(tenantId, userSettings);
}
@Override
public void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings) {
settingsService.updateUserSettings(tenantId, userId, type, settings);
}
@Override
public UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type) {
return settingsService.findUserSettings(tenantId, userId, type);
}
@Override
public void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List<String> jsonPaths) {
settingsService.deleteUserSettings(tenantId, userId, type, jsonPaths);
}
@Override
public UserDashboardsInfo findUserDashboardsInfo(TenantId tenantId, UserId id) {
UserSettings us = findUserSettings(tenantId, id, UserSettingsType.VISITED_DASHBOARDS);
if (us == null) {
return UserDashboardsInfo.EMPTY;
}
UserDashboardsInfo stored = JacksonUtil.convertValue(us.getSettings(), UserDashboardsInfo.class);
return refreshDashboardTitles(tenantId, stored);
}
@Override
public UserDashboardsInfo reportUserDashboardAction(TenantId tenantId, UserId id, DashboardId dashboardId, UserDashboardAction action) {
UserSettings us = findUserSettings(tenantId, id, UserSettingsType.VISITED_DASHBOARDS);
UserDashboardsInfo stored = null;
if (us != null) {
stored = JacksonUtil.convertValue(us.getSettings(), UserDashboardsInfo.class);
}
if (stored == null) {
stored = new UserDashboardsInfo();
}
switch (action) {
case STAR:
addToStarred(stored, dashboardId);
break;
case UNSTAR:
removeFromStarred(stored, dashboardId);
break;
case VISIT:
addToVisited(stored, dashboardId);
break;
}
stored = refreshDashboardTitles(tenantId, stored);
us = new UserSettings();
us.setUserId(id);
us.setType(UserSettingsType.VISITED_DASHBOARDS);
us.setSettings(JacksonUtil.valueToTree(stored));
saveUserSettings(tenantId, us);
return stored;
}
private void addToVisited(UserDashboardsInfo stored, DashboardId dashboardId) {
UUID id = dashboardId.getId();
long ts = System.currentTimeMillis();
var opt = stored.getLast().stream().filter(filterById(id)).findFirst();
if (opt.isPresent()) {
opt.get().setLastVisited(ts);
} else {
var newInfo = new LastVisitedDashboardInfo();
newInfo.setId(id);
newInfo.setStarred(stored.getStarred().stream().anyMatch(filterById(id)));
newInfo.setLastVisited(System.currentTimeMillis());
stored.getLast().add(newInfo);
}
stored.getLast().sort(Comparator.comparing(LastVisitedDashboardInfo::getLastVisited).reversed());
if (stored.getLast().size() > MAX_DASHBOARD_INFO_LIST_SIZE) {
stored.setLast(stored.getLast().stream().limit(MAX_DASHBOARD_INFO_LIST_SIZE).collect(Collectors.toList()));
}
}
private void removeFromStarred(UserDashboardsInfo stored, DashboardId dashboardId) {
UUID id = dashboardId.getId();
stored.getStarred().removeIf(filterById(id));
stored.getLast().stream().filter(d -> id.equals(d.getId())).findFirst().ifPresent(d -> d.setStarred(false));
}
private void addToStarred(UserDashboardsInfo stored, DashboardId dashboardId) {
UUID id = dashboardId.getId();
long ts = System.currentTimeMillis();
var opt = stored.getStarred().stream().filter(filterById(id)).findFirst();
if (opt.isPresent()) {
opt.get().setStarredAt(ts);
} else {
var newInfo = new StarredDashboardInfo();
newInfo.setId(id);
newInfo.setStarredAt(System.currentTimeMillis());
stored.getStarred().add(newInfo);
}
stored.getStarred().sort(Comparator.comparing(StarredDashboardInfo::getStarredAt).reversed());
if (stored.getStarred().size() > MAX_DASHBOARD_INFO_LIST_SIZE) {
stored.setStarred(stored.getStarred().stream().limit(MAX_DASHBOARD_INFO_LIST_SIZE).collect(Collectors.toList()));
}
Set<UUID> starredMap =
stored.getStarred().stream().map(AbstractUserDashboardInfo::getId).collect(Collectors.toSet());
stored.getLast().forEach(d -> d.setStarred(starredMap.contains(d.getId())));
}
private Predicate<AbstractUserDashboardInfo> filterById(UUID id) {
return d -> id.equals(d.getId());
}
private UserDashboardsInfo refreshDashboardTitles(TenantId tenantId, UserDashboardsInfo stored) {
if (stored == null) {
return UserDashboardsInfo.EMPTY;
}
stored.getLast().forEach(i -> i.setTitle(null));
stored.getStarred().forEach(i -> i.setTitle(null));
Set<UUID> uniqueIds = new HashSet<>();
stored.getLast().stream().map(AbstractUserDashboardInfo::getId).forEach(uniqueIds::add);
stored.getStarred().stream().map(AbstractUserDashboardInfo::getId).forEach(uniqueIds::add);
Map<UUID, String> dashboardTitles = new HashMap<>();
uniqueIds.forEach(id -> {
var title = dashboardService.findDashboardTitleById(tenantId, new DashboardId(id));
if (StringUtils.isNotEmpty(title)) {
dashboardTitles.put(id, title);
}
}
);
stored.getLast().forEach(i -> i.setTitle(dashboardTitles.get(i.getId())));
stored.getLast().removeIf(EMPTY_TITLE);
stored.getStarred().forEach(i -> i.setTitle(dashboardTitles.get(i.getId())));
stored.getStarred().removeIf(EMPTY_TITLE);
return stored;
}
}

42
application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java

@ -0,0 +1,42 @@
/**
* 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.
*/
package org.thingsboard.server.service.entitiy.user;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.settings.UserDashboardAction;
import org.thingsboard.server.common.data.settings.UserDashboardsInfo;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import java.util.List;
public interface TbUserSettingsService {
void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings);
UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings);
UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type);
void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List<String> jsonPaths);
UserDashboardsInfo findUserDashboardsInfo(TenantId tenantId, UserId id);
UserDashboardsInfo reportUserDashboardAction(TenantId tenantId, UserId id, DashboardId dashboardId, UserDashboardAction action);
}

23
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -22,7 +22,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
@ -91,6 +90,7 @@ import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
@ -177,6 +177,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Autowired
private NotificationSettingsService notificationSettingsService;
@Autowired
private NotificationTargetService notificationTargetService;
@Bean
protected BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
@ -679,27 +682,15 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Override
public void createDefaultNotificationConfigs() {
try {
log.info("Creating default notification configs for system admin");
log.info("Creating default notification configs for system admin");
if (notificationTargetService.findNotificationTargetsByTenantId(TenantId.SYS_TENANT_ID, new PageLink(1)).getTotalElements() == 0) {
notificationSettingsService.createDefaultNotificationConfigs(TenantId.SYS_TENANT_ID);
} catch (Exception e) {
if (StringUtils.contains(e.getMessage(), "already exists")) {
log.info("Default notification configs are already present for system admin, skipping");
} else {
throw e;
}
}
PageDataIterable<TenantId> tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500);
log.info("Creating default notification configs for all tenants");
for (TenantId tenantId : tenants) {
try {
if (notificationTargetService.findNotificationTargetsByTenantId(tenantId, new PageLink(1)).getTotalElements() == 0) {
notificationSettingsService.createDefaultNotificationConfigs(tenantId);
} catch (Exception e) {
if (StringUtils.contains(e.getMessage(), "already exists")) {
log.info("Default notification configs are already present for tenant {}, skipping", tenantId);
} else {
throw e;
}
}
}
}

23
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -65,6 +65,7 @@ public class InstallScripts {
public static final String JSON_DIR = "json";
public static final String SYSTEM_DIR = "system";
public static final String TENANT_DIR = "tenant";
public static final String EDGE_DIR = "edge";
public static final String DEVICE_PROFILE_DIR = "device_profile";
public static final String DEMO_DIR = "demo";
public static final String RULE_CHAINS_DIR = "rule_chains";
@ -74,8 +75,6 @@ public class InstallScripts {
public static final String MODELS_LWM2M_DIR = "lwm2m-registry";
public static final String CREDENTIALS_DIR = "credentials";
public static final String EDGE_MANAGEMENT = "edge_management";
public static final String JSON_EXT = ".json";
public static final String XML_EXT = ".xml";
@ -109,7 +108,7 @@ public class InstallScripts {
}
private Path getEdgeRuleChainsDir() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, EDGE_MANAGEMENT, RULE_CHAINS_DIR);
return Paths.get(getDataDir(), JSON_DIR, EDGE_DIR, RULE_CHAINS_DIR);
}
public String getDataDir() {
@ -293,17 +292,15 @@ public class InstallScripts {
}
private void doSaveLwm2mResource(TbResource resource) throws ThingsboardException {
try {
log.trace("Executing saveResource [{}]", resource);
if (StringUtils.isEmpty(resource.getData())) {
throw new DataValidationException("Resource data should be specified!");
}
toLwm2mResource(resource);
log.trace("Executing saveResource [{}]", resource);
if (StringUtils.isEmpty(resource.getData())) {
throw new DataValidationException("Resource data should be specified!");
}
toLwm2mResource(resource);
TbResource foundResource =
resourceService.getResource(TenantId.SYS_TENANT_ID, ResourceType.LWM2M_MODEL, resource.getResourceKey());
if (foundResource == null) {
resourceService.saveResource(resource);
} catch (DataValidationException e) {
log.debug("[{}] {}", resource.getFileName(), e.getMessage());
} catch (Exception ex) {
throw ex;
}
}
}

4
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java

@ -15,8 +15,10 @@
*/
package org.thingsboard.server.service.notification.rule.trigger;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
@ -44,7 +46,7 @@ public class NewPlatformVersionTriggerProcessor implements NotificationRuleTrigg
@Override
public NotificationInfo constructNotificationInfo(NewPlatformVersionTrigger trigger, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) {
return NewPlatformVersionNotificationInfo.builder()
.message(trigger.getMessage().getMessage())
.message(JacksonUtil.convertValue(trigger.getMessage(), new TypeReference<>() {}))
.build();
}

27
application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java

@ -30,7 +30,6 @@ import org.thingsboard.server.common.data.SystemInfo;
import org.thingsboard.server.common.data.SystemInfoData;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
@ -58,10 +57,9 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.thingsboard.common.util.SystemUtil.getCpuUsage;
import static org.thingsboard.common.util.SystemUtil.getFreeDiscSpace;
import static org.thingsboard.common.util.SystemUtil.getFreeMemory;
import static org.thingsboard.common.util.SystemUtil.getMemoryUsage;
import static org.thingsboard.common.util.SystemUtil.getTotalCpuUsage;
import static org.thingsboard.common.util.SystemUtil.getDiscSpaceUsage;
import static org.thingsboard.common.util.SystemUtil.getCpuCount;
import static org.thingsboard.common.util.SystemUtil.getTotalDiscSpace;
import static org.thingsboard.common.util.SystemUtil.getTotalMemory;
@ -162,12 +160,13 @@ public class DefaultSystemInfoService extends TbApplicationEventListener<Partiti
private void saveCurrentMonolithSystemInfo() {
long ts = System.currentTimeMillis();
List<TsKvEntry> tsList = new ArrayList<>();
getMemoryUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("memoryUsage", v))));
getCpuUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuUsage", (long) v))));
getMemoryUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("memoryUsage", (long) v))));
getDiscSpaceUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("discUsage", (long) v))));
getCpuCount().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuCount", (long) v))));
getTotalMemory().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("totalMemory", v))));
getFreeMemory().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("freeMemory", v))));
getCpuUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new DoubleDataEntry("cpuUsage", v))));
getTotalCpuUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new DoubleDataEntry("totalCpuUsage", v))));
getFreeDiscSpace().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("freeDiscSpace", v))));
getTotalDiscSpace().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("totalDiscSpace", v))));
doSave(tsList);
@ -193,13 +192,15 @@ public class DefaultSystemInfoService extends TbApplicationEventListener<Partiti
SystemInfoData infoData = new SystemInfoData();
infoData.setServiceId(serviceInfo.getServiceId());
infoData.setServiceType(serviceTypes.size() > 1 ? "MONOLITH" : serviceTypes.get(0));
infoData.setCpuUsage(serviceInfo.getSystemInfo().getCpuUsage());
infoData.setMemoryUsage(serviceInfo.getSystemInfo().getMemoryUsage());
infoData.setDiscUsage(serviceInfo.getSystemInfo().getDiskUsage());
infoData.setCpuCount(serviceInfo.getSystemInfo().getCpuCount());
infoData.setTotalMemory(serviceInfo.getSystemInfo().getTotalMemory());
infoData.setFreeMemory(serviceInfo.getSystemInfo().getFreeMemory());
infoData.setCpuUsage(serviceInfo.getSystemInfo().getCpuUsage());
infoData.setTotalCpuUsage(serviceInfo.getSystemInfo().getTotalCpuUsage());
infoData.setFreeDiscSpace(serviceInfo.getSystemInfo().getFreeDiscSpace());
infoData.setTotalDiscSpace(serviceInfo.getSystemInfo().getTotalDiscSpace());
return infoData;
}

18
application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java

@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.info.BuildProperties;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
@ -57,6 +58,9 @@ public class DefaultUpdateService implements UpdateService {
@Value("${updates.enabled}")
private boolean updatesEnabled;
@Autowired(required = false)
private BuildProperties buildProperties;
@Autowired
private NotificationRuleProcessingService notificationRuleProcessingService;
@ -73,14 +77,11 @@ public class DefaultUpdateService implements UpdateService {
@PostConstruct
private void init() {
updateMessage = new UpdateMessage("", false, "");
version = buildProperties != null ? buildProperties.getVersion() : "unknown";
updateMessage = new UpdateMessage(false, version, "", "", "", "");
if (updatesEnabled) {
try {
platform = System.getProperty("platform", "unknown");
version = getClass().getPackage().getImplementationVersion();
if (version == null) {
version = "unknown";
}
instanceId = parseInstanceId();
checkUpdatesFuture = scheduler.scheduleAtFixedRate(checkUpdatesRunnable, 0, 1, TimeUnit.HOURS);
} catch (Exception e) {
@ -128,13 +129,8 @@ public class DefaultUpdateService implements UpdateService {
request.put(PLATFORM_PARAM, platform);
request.put(VERSION_PARAM, version);
request.put(INSTANCE_ID_PARAM, instanceId.toString());
JsonNode response = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/thingsboard/updates", request, JsonNode.class);
UpdateMessage prevUpdateMessage = updateMessage;
updateMessage = new UpdateMessage(
response.get("message").asText(),
response.get("updateAvailable").asBoolean(),
version
);
updateMessage = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/v2/thingsboard/updates", request, UpdateMessage.class);
if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) {
notificationRuleProcessingService.process(TenantId.SYS_TENANT_ID, NewPlatformVersionTrigger.builder()
.message(updateMessage)

3
application/src/main/resources/thingsboard.yml

@ -474,6 +474,9 @@ cache:
userSettings:
timeToLiveInMinutes: "${CACHE_SPECS_USER_SETTINGS_TTL:1440}"
maxSize: "${CACHE_SPECS_USER_SETTINGS_MAX_SIZE:100000}"
dashboardTitles:
timeToLiveInMinutes: "${CACHE_SPECS_DASHBOARD_TITLES_TTL:1440}"
maxSize: "${CACHE_SPECS_DASHBOARD_TITLES_MAX_SIZE:100000}"
entityCount:
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_COUNT_TTL:1440}"
maxSize: "${CACHE_SPECS_ENTITY_COUNT_MAX_SIZE:100000}"

5
application/src/test/java/org/thingsboard/server/controller/BaseHomePageApiTest.java

@ -245,15 +245,16 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(apiUsageState.getId(), pageData.getData().get(0).getEntityId());
List<String> metrics = List.of("cpuUsage", "memoryUsage", "discUsage", "cpuCount", "totalMemory", "totalDiscSpace");
update = getWsClient().subscribeTsUpdate(
List.of("memoryUsage", "totalMemory", "freeMemory", "cpuUsage", "totalCpuUsage", "freeDiscSpace", "totalDiscSpace"),
metrics,
now, TimeUnit.HOURS.toMillis(1));
Assert.assertEquals(1, update.getCmdId());
List<EntityData> listData = update.getUpdate();
Assert.assertNotNull(listData);
Assert.assertEquals(1, listData.size());
Assert.assertEquals(apiUsageState.getId(), listData.get(0).getEntityId());
Assert.assertEquals(7, listData.get(0).getTimeseries().size());
Assert.assertEquals(metrics.size(), listData.get(0).getTimeseries().size());
for (TsValue[] tsv : listData.get(0).getTimeseries().values()) {
Assert.assertTrue(tsv.length > 0);

188
application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java

@ -18,6 +18,7 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Ignore;
@ -31,6 +32,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
@ -42,6 +44,8 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.settings.StarredDashboardInfo;
import org.thingsboard.server.common.data.settings.UserDashboardsInfo;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.user.UserDao;
@ -356,7 +360,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
String userIdStr = savedUser.getId().getId().toString();
doGet("/api/user/" + userIdStr)
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString( msgErrorNoFound("User",userIdStr))));
.andExpect(statusReason(containsString(msgErrorNoFound("User", userIdStr))));
}
@Test
@ -574,9 +578,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
String email1 = "testEmail1";
String email2 = "testEmail2";
List<User> customerUsersEmail1 = new ArrayList<>();
List<User> customerUsersEmail2= new ArrayList<>();
List<User> customerUsersEmail2 = new ArrayList<>();
for (int i = 0; i < 45; i++) {
User customerUser = createCustomerUser( customerId);
User customerUser = createCustomerUser(customerId);
customerUser.setEmail(email1 + StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)) + "@thingsboard.org");
customerUsersEmail1.add(doPost("/api/user", customerUser, User.class));
@ -685,7 +689,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
JsonNode retrievedSettings = doGet("/api/user/settings", JsonNode.class);
Assert.assertEquals(retrievedSettings, userSettings);
}
}
@Test
public void testShouldNotSaveJsonWithRestrictedSymbols() throws Exception {
@ -860,7 +864,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> expectedUserInfos = customerUsersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(),
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName()))
customerUser.getLastName() == null ? "" : customerUser.getLastName()))
.sorted(userDataIdComparator).collect(Collectors.toList());
usersInfo.sort(userDataIdComparator);
@ -912,8 +916,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> usersInfo = getUsersInfo(pageLink);
List<UserEmailInfo> expectedUserInfos = usersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(),
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName()))
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName()))
.sorted(userDataIdComparator).collect(Collectors.toList());
usersInfo.sort(userDataIdComparator);
@ -922,7 +926,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
// find user by full last name
pageLink = new PageLink(10, 0, searchText + "3");
usersInfo = getUsersInfo(pageLink);
Assert.assertEquals(2, usersInfo.size());
Assert.assertEquals(2, usersInfo.size());
//clear users
doDelete("/api/customer/" + customerId.getId().toString())
@ -941,6 +945,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
private static User createCustomerUser(CustomerId customerId) {
return createCustomerUser(null, null, customerId);
}
private static User createCustomerUser(String firstName, String lastName, CustomerId customerId) {
String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
return createCustomerUser(firstName, lastName, "testMail" + suffix + "@thingsboard.org", customerId);
@ -959,6 +964,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
private User createTenantAdminUser() {
return createTenantAdminUser(null, null);
}
private User createTenantAdminUser(String firstName, String lastName) {
String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
@ -975,7 +981,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> loadedCustomerUsers = new ArrayList<>();
PageData<UserEmailInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/users/info?", new TypeReference<>() {}, pageLink);
pageData = doGetTypedWithPageLink("/api/users/info?", new TypeReference<>() {
}, pageLink);
loadedCustomerUsers.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -984,4 +991,167 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
return loadedCustomerUsers;
}
@Test
public void testEmptyDashboardSettings() throws Exception {
loginCustomerUser();
UserDashboardsInfo retrievedSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class);
Assert.assertNotNull(retrievedSettings);
Assert.assertNotNull(retrievedSettings.getLast());
Assert.assertTrue(retrievedSettings.getLast().isEmpty());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertTrue(retrievedSettings.getStarred().isEmpty());
}
@Test
public void testDashboardSettingsFlow() throws Exception {
loginTenantAdmin();
Dashboard dashboard1 = new Dashboard();
dashboard1.setTitle("My dashboard 1");
Dashboard savedDashboard1 = doPost("/api/dashboard", dashboard1, Dashboard.class);
Dashboard dashboard2 = new Dashboard();
dashboard2.setTitle("My dashboard 2");
Dashboard savedDashboard2 = doPost("/api/dashboard", dashboard2, Dashboard.class);
UserDashboardsInfo retrievedSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class);
Assert.assertNotNull(retrievedSettings);
Assert.assertNotNull(retrievedSettings.getLast());
Assert.assertTrue(retrievedSettings.getLast().isEmpty());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertTrue(retrievedSettings.getStarred().isEmpty());
UserDashboardsInfo newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/visit", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(1, newSettings.getLast().size());
var lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertTrue(retrievedSettings.getStarred().isEmpty());
newSettings = doGet("/api/user/dashboards/" + savedDashboard2.getId().getId() + "/visit", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertTrue(retrievedSettings.getStarred().isEmpty());
newSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertTrue(retrievedSettings.getStarred().isEmpty());
newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/star", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertFalse(lastVisited.isStarred());
lastVisited = newSettings.getLast().get(1);
Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertEquals(1, newSettings.getStarred().size());
StarredDashboardInfo starred = newSettings.getStarred().get(0);
Assert.assertEquals(savedDashboard1.getId().getId(), starred.getId());
Assert.assertEquals(savedDashboard1.getTitle(), starred.getTitle());
newSettings = doGet("/api/user/dashboards/" + savedDashboard2.getId().getId() + "/star", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
lastVisited = newSettings.getLast().get(1);
Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertEquals(2, newSettings.getStarred().size());
starred = newSettings.getStarred().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId());
Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle());
newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/unstar", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
lastVisited = newSettings.getLast().get(1);
Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle());
Assert.assertFalse(lastVisited.isStarred());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertEquals(1, newSettings.getStarred().size());
starred = newSettings.getStarred().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId());
Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle());
//TEST renaming in the cache.
savedDashboard1.setTitle(RandomStringUtils.randomAlphanumeric(10));
savedDashboard1 = doPost("/api/dashboard", savedDashboard1, Dashboard.class);
savedDashboard2.setTitle(RandomStringUtils.randomAlphanumeric(10));
savedDashboard2 = doPost("/api/dashboard", savedDashboard2, Dashboard.class);
newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/unstar", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
lastVisited = newSettings.getLast().get(1);
Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle());
Assert.assertFalse(lastVisited.isStarred());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertEquals(1, newSettings.getStarred().size());
starred = newSettings.getStarred().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId());
Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle());
doDelete("/api/dashboard/" + savedDashboard1.getId().getId().toString()).andExpect(status().isOk());
newSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(1, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
Assert.assertEquals(1, newSettings.getStarred().size());
starred = newSettings.getStarred().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId());
Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle());
doDelete("/api/dashboard/" + savedDashboard2.getId().getId().toString()).andExpect(status().isOk());
retrievedSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class);
Assert.assertNotNull(retrievedSettings);
Assert.assertNotNull(retrievedSettings.getLast());
Assert.assertTrue(retrievedSettings.getLast().isEmpty());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertTrue(retrievedSettings.getStarred().isEmpty());
}
}

23
application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java

@ -19,12 +19,14 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
@ -33,6 +35,7 @@ import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -42,8 +45,11 @@ abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest {
public void testSendAlarmToCloud() throws Exception {
Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge();
UUID alarmUUID = UUID.randomUUID();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
AlarmUpdateMsg.Builder alarmUpdateMgBuilder = AlarmUpdateMsg.newBuilder();
alarmUpdateMgBuilder.setIdMSB(alarmUUID.getMostSignificantBits());
alarmUpdateMgBuilder.setIdLSB(alarmUUID.getLeastSignificantBits());
alarmUpdateMgBuilder.setName("alarm from edge");
alarmUpdateMgBuilder.setStatus(AlarmStatus.ACTIVE_UNACK.name());
alarmUpdateMgBuilder.setSeverity(AlarmSeverity.CRITICAL.name());
@ -65,6 +71,7 @@ abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest {
Optional<AlarmInfo> foundAlarm = alarms.stream().filter(alarm -> alarm.getType().equals("alarm from edge")).findAny();
Assert.assertTrue(foundAlarm.isPresent());
AlarmInfo alarmInfo = foundAlarm.get();
Assert.assertEquals(new AlarmId(alarmUUID), alarmInfo.getId());
Assert.assertEquals(device.getId(), alarmInfo.getOriginator());
Assert.assertEquals(AlarmStatus.ACTIVE_UNACK, alarmInfo.getStatus());
Assert.assertEquals(AlarmSeverity.CRITICAL, alarmInfo.getSeverity());
@ -85,12 +92,28 @@ abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest {
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
AlarmUpdateMsg alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, alarmUpdateMsg.getMsgType());
Assert.assertEquals(savedAlarm.getUuidId().getMostSignificantBits(), alarmUpdateMsg.getIdMSB());
Assert.assertEquals(savedAlarm.getUuidId().getLeastSignificantBits(), alarmUpdateMsg.getIdLSB());
Assert.assertEquals(savedAlarm.getType(), alarmUpdateMsg.getType());
Assert.assertEquals(savedAlarm.getName(), alarmUpdateMsg.getName());
Assert.assertEquals(device.getName(), alarmUpdateMsg.getOriginatorName());
Assert.assertEquals(savedAlarm.getStatus().name(), alarmUpdateMsg.getStatus());
Assert.assertEquals(savedAlarm.getSeverity().name(), alarmUpdateMsg.getSeverity());
// update alarm
String updatedDetails = "{\"testKey\":\"testValue\"}";
savedAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(updatedDetails));
edgeImitator.expectMessageAmount(1);
savedAlarm = doPost("/api/alarm", savedAlarm, Alarm.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, alarmUpdateMsg.getMsgType());
Assert.assertEquals(savedAlarm.getUuidId().getMostSignificantBits(), alarmUpdateMsg.getIdMSB());
Assert.assertEquals(savedAlarm.getUuidId().getLeastSignificantBits(), alarmUpdateMsg.getIdLSB());
Assert.assertEquals(updatedDetails, alarmUpdateMsg.getDetails());
// ack alarm
edgeImitator.expectMessageAmount(1);
doPost("/api/alarm/" + savedAlarm.getUuidId() + "/ack");

9
common/cluster-api/src/main/proto/queue.proto

@ -31,13 +31,12 @@ message ServiceInfo {
}
message SystemInfoProto {
double cpuUsage = 1;
double totalCpuUsage = 2;
int64 cpuUsage = 1;
int64 cpuCount = 2;
int64 memoryUsage = 3;
int64 totalMemory = 4;
int64 freeMemory = 5;
int64 freeDiscSpace = 6;
int64 totalDiscSpace = 7;
int64 diskUsage = 5;
int64 totalDiscSpace = 6;
}
/**

2
common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -36,6 +36,8 @@ public interface DashboardService extends EntityDaoService {
DashboardInfo findDashboardInfoById(TenantId tenantId, DashboardId dashboardId);
String findDashboardTitleById(TenantId tenantId, DashboardId dashboardId);
ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(TenantId tenantId, DashboardId dashboardId);
Dashboard saveDashboard(Dashboard dashboard);

9
common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java

@ -18,18 +18,19 @@ package org.thingsboard.server.dao.user;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import java.util.List;
public interface UserSettingsService {
void updateUserSettings(TenantId tenantId, UserId userId, JsonNode settings);
void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings);
UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings);
UserSettings findUserSettings(TenantId tenantId, UserId userId);
UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type);
void deleteUserSettings(TenantId tenantId, UserId userId, List<String> jsonPaths);
void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List<String> jsonPaths);
}

1
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -42,5 +42,6 @@ public class CacheConstants {
public static final String TWO_FA_VERIFICATION_CODES_CACHE = "twoFaVerificationCodes";
public static final String VERSION_CONTROL_TASK_CACHE = "versionControlTask";
public static final String USER_SETTINGS_CACHE = "userSettings";
public static final String DASHBOARD_TITLES_CACHE = "dashboardTitles";
public static final String ENTITY_COUNT_CACHE = "entityCount";
}

18
common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java

@ -17,6 +17,7 @@ package org.thingsboard.server.common.data;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import java.util.Map;
@ -26,18 +27,17 @@ public class SystemInfoData {
private String serviceId;
@ApiModelProperty(position = 2, value = "Service type.")
private String serviceType;
@ApiModelProperty(position = 3, value = "CPU usage.")
private Double cpuUsage;
@ApiModelProperty(position = 3, value = "CPU usage, in percent.")
private Long cpuUsage;
@ApiModelProperty(position = 4, value = "Total CPU usage.")
private Double totalCpuUsage;
@ApiModelProperty(position = 5, value = "Memory usage in bytes.")
private Long cpuCount;
@ApiModelProperty(position = 5, value = "Memory usage, in percent.")
private Long memoryUsage;
@ApiModelProperty(position = 6, value = "Total memory in bytes.")
private Long totalMemory;
@ApiModelProperty(position = 6, value = "Free memory in bytes.")
private Long freeMemory;
@ApiModelProperty(position = 7, value = "Free disc space in bytes.")
private Long freeDiscSpace;
@ApiModelProperty(position = 7, value = "Total disc space in bytes.")
@ApiModelProperty(position = 7, value = "Disk usage, in percent.")
private Long discUsage;
@ApiModelProperty(position = 8, value = "Total disc space in bytes.")
private Long totalDiscSpace;
}

18
common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java

@ -23,10 +23,18 @@ import lombok.Data;
@Data
public class UpdateMessage {
@ApiModelProperty(position = 1, value = "The message about new platform update available.")
private final String message;
@ApiModelProperty(position = 2, value = "'True' if new platform update is available.")
private final boolean isUpdateAvailable;
@ApiModelProperty(position = 3, value = "Current ThingsBoard version.")
@ApiModelProperty(position = 1, value = "'True' if new platform update is available.")
private final boolean updateAvailable;
@ApiModelProperty(position = 2, value = "Current ThingsBoard version.")
private final String currentVersion;
@ApiModelProperty(position = 3, value = "Latest ThingsBoard version.")
private final String latestVersion;
@ApiModelProperty(position = 4, value = "Upgrade instructions URL.")
private final String upgradeInstructionsUrl;
@ApiModelProperty(position = 5, value = "Current ThingsBoard version release notes URL.")
private final String currentVersionReleaseNotesUrl;
@ApiModelProperty(position = 6, value = "Latest ThingsBoard version release notes URL.")
private final String latestVersionReleaseNotesUrl;
}

8
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java

@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@ -61,11 +62,17 @@ public class AlarmCreateOrUpdateActiveRequest implements AlarmModificationReques
private UserId userId;
private AlarmId edgeAlarmId;
public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a) {
return fromAlarm(a, null);
}
public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a, UserId userId) {
return fromAlarm(a, userId, null);
}
public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a, UserId userId, AlarmId edgeAlarmId) {
return AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(a.getTenantId())
.customerId(a.getCustomerId())
@ -81,6 +88,7 @@ public class AlarmCreateOrUpdateActiveRequest implements AlarmModificationReques
.propagateToTenant(a.isPropagateToTenant())
.propagateRelationTypes(a.getPropagateRelationTypes()).build())
.userId(userId)
.edgeAlarmId(edgeAlarmId)
.build();
}

7
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java

@ -19,6 +19,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.UpdateMessage;
import java.util.Map;
@ -30,13 +31,11 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Builder
public class NewPlatformVersionNotificationInfo implements NotificationInfo {
private String message;
private Map<String, String> message;
@Override
public Map<String, String> getTemplateData() {
return mapOf(
"message", message
);
return message;
}
}

37
common/data/src/main/java/org/thingsboard/server/common/data/settings/AbstractUserDashboardInfo.java

@ -0,0 +1,37 @@
/**
* 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.
*/
package org.thingsboard.server.common.data.settings;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.thingsboard.server.common.data.HasTitle;
import java.io.Serializable;
import java.util.UUID;
@ApiModel
@Data
public abstract class AbstractUserDashboardInfo implements HasTitle, Serializable {
private static final long serialVersionUID = -6461562426034242608L;
@ApiModelProperty(position = 1, value = "JSON object with Dashboard id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private UUID id;
@ApiModelProperty(position = 2, value = "Title of the dashboard.")
private String title;
}

37
common/data/src/main/java/org/thingsboard/server/common/data/settings/LastVisitedDashboardInfo.java

@ -0,0 +1,37 @@
/**
* 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.
*/
package org.thingsboard.server.common.data.settings;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@ApiModel
@Data
public class LastVisitedDashboardInfo extends AbstractUserDashboardInfo implements Serializable {
private static final long serialVersionUID = -6461562426034242608L;
@ApiModelProperty(position = 3, value = "Starred flag")
private boolean starred;
@ApiModelProperty(position = 4, value = "Last visit timestamp")
private long lastVisited;
}

36
common/data/src/main/java/org/thingsboard/server/common/data/settings/StarredDashboardInfo.java

@ -0,0 +1,36 @@
/**
* 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.
*/
package org.thingsboard.server.common.data.settings;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.HasTitle;
import org.thingsboard.server.common.data.id.DashboardId;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@ApiModel
@Data
public class StarredDashboardInfo extends AbstractUserDashboardInfo implements Serializable {
private static final long serialVersionUID = -7830828696329673361L;
@ApiModelProperty(position = 4, value = "Starred timestamp")
private long starredAt;
}

22
common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardAction.java

@ -0,0 +1,22 @@
/**
* 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.
*/
package org.thingsboard.server.common.data.settings;
public enum UserDashboardAction {
VISIT, STAR, UNSTAR
}

46
common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardsInfo.java

@ -0,0 +1,46 @@
/**
* 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.
*/
package org.thingsboard.server.common.data.settings;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ApiModel
@Data
@AllArgsConstructor
public class UserDashboardsInfo implements Serializable {
private static final long serialVersionUID = 2628320657987010348L;
public static final UserDashboardsInfo EMPTY = new UserDashboardsInfo(Collections.emptyList(), Collections.emptyList());
@ApiModelProperty(position = 1, value = "List of last visited dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private List<LastVisitedDashboardInfo> last;
@ApiModelProperty(position = 2, value = "List of starred dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private List<StarredDashboardInfo> starred;
public UserDashboardsInfo() {
this(new ArrayList<>(), new ArrayList<>());
}
}

9
common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java → common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.security;
package org.thingsboard.server.common.data.settings;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
@ -43,7 +43,12 @@ public class UserSettings implements Serializable {
@ApiModelProperty(position = 1, value = "JSON object with User id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private UserId userId;
@ApiModelProperty(position = 2, value = "JSON object with user settings.", dataType = "com.fasterxml.jackson.databind.JsonNode")
@ApiModelProperty(position = 2, value = "Type of the settings.")
@NoXss
@Length(fieldName = "type", max = 50)
private UserSettingsType type;
@ApiModelProperty(position = 3, value = "JSON object with user settings.", dataType = "com.fasterxml.jackson.databind.JsonNode")
@NoXss
@Length(fieldName = "settings", max = 100000)
private transient JsonNode settings;

44
common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsCompositeKey.java

@ -0,0 +1,44 @@
/**
* 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.
*/
package org.thingsboard.server.common.data.settings;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.UUID;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class UserSettingsCompositeKey implements Serializable {
private static final long serialVersionUID = -7883642552545291489L;
private UUID userId;
private String type;
public UserSettingsCompositeKey(UserSettings userSettings) {
this.userId = userSettings.getUserId().getId();
this.type = userSettings.getType().name();
}
@Override
public String toString() {
return userId.toString() + "_" + type;
}
}

34
common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java

@ -0,0 +1,34 @@
/**
* 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.
*/
package org.thingsboard.server.common.data.settings;
import lombok.Getter;
public enum UserSettingsType {
GENERAL, VISITED_DASHBOARDS(true), QUICK_LINKS, DOC_LINKS, DASHBOARDS;
@Getter
private final boolean reserved;
UserSettingsType() {
this.reserved = false;
}
UserSettingsType(boolean reserved) {
this.reserved = reserved;
}
}

17
common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java

@ -38,12 +38,13 @@ import java.util.List;
import java.util.stream.Collectors;
import static org.thingsboard.common.util.SystemUtil.getCpuUsage;
import static org.thingsboard.common.util.SystemUtil.getFreeDiscSpace;
import static org.thingsboard.common.util.SystemUtil.getFreeMemory;
import static org.thingsboard.common.util.SystemUtil.getMemoryUsage;
import static org.thingsboard.common.util.SystemUtil.getTotalCpuUsage;
import static org.thingsboard.common.util.SystemUtil.getTotalDiscSpace;
import static org.thingsboard.common.util.SystemUtil.getDiscSpaceUsage;
import static org.thingsboard.common.util.SystemUtil.getCpuCount;
import static org.thingsboard.common.util.SystemUtil.getTotalMemory;
import static org.thingsboard.common.util.SystemUtil.getTotalDiscSpace;
@Component
@Slf4j
@ -118,12 +119,12 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider {
private TransportProtos.SystemInfoProto getCurrentSystemInfoProto() {
TransportProtos.SystemInfoProto.Builder builder = TransportProtos.SystemInfoProto.newBuilder();
getCpuUsage().ifPresent(builder::setCpuUsage);
getMemoryUsage().ifPresent(builder::setMemoryUsage);
getDiscSpaceUsage().ifPresent(builder::setDiskUsage);
getCpuCount().ifPresent(builder::setCpuCount);
getTotalMemory().ifPresent(builder::setTotalMemory);
getFreeMemory().ifPresent(builder::setFreeMemory);
getCpuUsage().ifPresent(builder::setCpuUsage);
getTotalCpuUsage().ifPresent(builder::setTotalCpuUsage);
getFreeDiscSpace().ifPresent(builder::setFreeDiscSpace);
getTotalDiscSpace().ifPresent(builder::setTotalDiscSpace);
return builder.build();

46
common/util/src/main/java/org/thingsboard/common/util/SystemUtil.java

@ -17,10 +17,13 @@ package org.thingsboard.common.util;
import lombok.extern.slf4j.Slf4j;
import oshi.SystemInfo;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Paths;
@ -32,14 +35,15 @@ public class SystemUtil {
private static final HardwareAbstractionLayer HARDWARE;
static {
SystemInfo si = new SystemInfo();
HARDWARE = si.getHardware();
HARDWARE = new SystemInfo().getHardware();
}
public static Optional<Long> getMemoryUsage() {
public static Optional<Integer> getMemoryUsage() {
try {
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
return Optional.of(memoryMXBean.getHeapMemoryUsage().getUsed());
GlobalMemory memory = HARDWARE.getMemory();
long total = memory.getTotal();
long available = memory.getAvailable();
return Optional.of(toPercent(total - available, total));
} catch (Exception e) {
log.debug("Failed to get memory usage!!!", e);
}
@ -55,37 +59,30 @@ public class SystemUtil {
return Optional.empty();
}
public static Optional<Long> getFreeMemory() {
public static Optional<Integer> getCpuUsage() {
try {
return Optional.of(HARDWARE.getMemory().getAvailable());
} catch (Exception e) {
log.debug("Failed to get free memory!!!", e);
}
return Optional.empty();
}
public static Optional<Double> getCpuUsage() {
try {
return Optional.of(prepare(HARDWARE.getProcessor().getSystemLoadAverage()));
return Optional.of((int) (HARDWARE.getProcessor().getSystemCpuLoad() * 100.0));
} catch (Exception e) {
log.debug("Failed to get cpu usage!!!", e);
}
return Optional.empty();
}
public static Optional<Double> getTotalCpuUsage() {
public static Optional<Integer> getCpuCount() {
try {
return Optional.of(prepare(HARDWARE.getProcessor().getSystemCpuLoad() * 100));
return Optional.of(HARDWARE.getProcessor().getLogicalProcessorCount());
} catch (Exception e) {
log.debug("Failed to get total cpu usage!!!", e);
log.debug("Failed to get total cpu count!!!", e);
}
return Optional.empty();
}
public static Optional<Long> getFreeDiscSpace() {
public static Optional<Integer> getDiscSpaceUsage() {
try {
FileStore store = Files.getFileStore(Paths.get("/"));
return Optional.of(store.getUsableSpace());
long total = store.getTotalSpace();
long available = store.getUsableSpace();
return Optional.of(toPercent(total - available, total));
} catch (Exception e) {
log.debug("Failed to get free disc space!!!", e);
}
@ -102,7 +99,10 @@ public class SystemUtil {
return Optional.empty();
}
private static Double prepare(Double d) {
return (int) (d * 100) / 100.0;
private static int toPercent(long used, long total) {
BigDecimal u = new BigDecimal(used);
BigDecimal t = new BigDecimal(total);
BigDecimal i = new BigDecimal(100);
return u.multiply(i).divide(t, RoundingMode.HALF_UP).intValue();
}
}

3
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java

@ -16,6 +16,8 @@
package org.thingsboard.server.dao.dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
@ -77,4 +79,5 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
DashboardInfo findFirstByTenantIdAndName(UUID tenantId, String name);
String findTitleById(UUID tenantId, UUID dashboardId);
}

42
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -20,8 +20,12 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
@ -37,6 +41,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.edge.EdgeDao;
import org.thingsboard.server.dao.entity.AbstractEntityService;
@ -45,6 +50,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.user.UserSettingsEvictEvent;
import java.util.List;
import java.util.Optional;
@ -73,9 +79,28 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired
private DataValidator<Dashboard> dashboardValidator;
@Autowired
protected TbTransactionalCache<DashboardId, String> cache;
@Autowired
private EntityCountService countService;
@Autowired
private ApplicationEventPublisher eventPublisher;
protected void publishEvictEvent(DashboardTitleEvictEvent event) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
eventPublisher.publishEvent(event);
} else {
handleEvictEvent(event);
}
}
@TransactionalEventListener(classes = DashboardTitleEvictEvent.class)
public void handleEvictEvent(DashboardTitleEvictEvent event) {
cache.evict(event.getKey());
}
@Override
public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) {
log.trace("Executing findDashboardById [{}]", dashboardId);
@ -97,6 +122,12 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
return dashboardInfoDao.findById(tenantId, dashboardId.getId());
}
@Override
public String findDashboardTitleById(TenantId tenantId, DashboardId dashboardId) {
return cache.getAndPutInTransaction(dashboardId,
() -> dashboardInfoDao.findTitleById(tenantId.getId(), dashboardId.getId()), true);
}
@Override
public ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(TenantId tenantId, DashboardId dashboardId) {
log.trace("Executing findDashboardInfoByIdAsync [{}]", dashboardId);
@ -109,12 +140,16 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
log.trace("Executing saveDashboard [{}]", dashboard);
dashboardValidator.validate(dashboard, DashboardInfo::getTenantId);
try {
Dashboard savedDashboard = dashboardDao.save(dashboard.getTenantId(), dashboard);
var saved = dashboardDao.save(dashboard.getTenantId(), dashboard);
publishEvictEvent(new DashboardTitleEvictEvent(saved.getId()));
if (dashboard.getId() == null) {
countService.publishCountEntityEvictEvent(savedDashboard.getTenantId(), EntityType.DASHBOARD);
countService.publishCountEntityEvictEvent(saved.getTenantId(), EntityType.DASHBOARD);
}
return dashboard;
return saved;
} catch (Exception e) {
if (dashboard.getId() != null) {
publishEvictEvent(new DashboardTitleEvictEvent(dashboard.getId()));
}
checkConstraintViolation(e, "dashboard_external_id_unq_key", "Dashboard with such external id already exists!");
throw e;
}
@ -180,6 +215,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
deleteEntityRelations(tenantId, dashboardId);
try {
dashboardDao.removeById(tenantId, dashboardId.getId());
publishEvictEvent(new DashboardTitleEvictEvent(dashboardId));
countService.publishCountEntityEvictEvent(tenantId, EntityType.DASHBOARD);
} catch (Exception t) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);

25
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitleEvictEvent.java

@ -0,0 +1,25 @@
/**
* 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.
*/
package org.thingsboard.server.dao.dashboard;
import lombok.Data;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
@Data
public class DashboardTitleEvictEvent {
private final DashboardId key;
}

33
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesCaffeineCache.java

@ -0,0 +1,33 @@
/**
* 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.
*/
package org.thingsboard.server.dao.dashboard;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.DashboardId;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("DashboardTitlesCache")
public class DashboardTitlesCaffeineCache extends CaffeineTbTransactionalCache<DashboardId, String> {
public DashboardTitlesCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.DASHBOARD_TITLES_CACHE);
}
}

35
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java

@ -0,0 +1,35 @@
/**
* 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.
*/
package org.thingsboard.server.dao.dashboard;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbFSTRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.DashboardId;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("DashboardTitlesCache")
public class DashboardTitlesRedisCache extends RedisTbTransactionalCache<DashboardId, String> {
public DashboardTitlesRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.DASHBOARD_TITLES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());
}
}

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

@ -94,6 +94,7 @@ public class ModelConstants {
*/
public static final String USER_SETTINGS_COLUMN_FAMILY_NAME = "user_settings";
public static final String USER_SETTINGS_USER_ID_PROPERTY = USER_ID_PROPERTY;
public static final String USER_SETTINGS_TYPE_PROPERTY = "type";
public static final String USER_SETTINGS_SETTINGS = "settings";
/**

13
dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java

@ -21,7 +21,9 @@ import lombok.NoArgsConstructor;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.ToData;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
@ -29,6 +31,7 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
import java.util.UUID;
@ -37,19 +40,24 @@ import java.util.UUID;
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Entity
@Table(name = ModelConstants.USER_SETTINGS_COLUMN_FAMILY_NAME)
@IdClass(UserSettingsCompositeKey.class)
public class UserSettingsEntity implements ToData<UserSettings> {
@Id
@Column(name = ModelConstants.USER_SETTINGS_USER_ID_PROPERTY)
private UUID userId;
@Id
@Column(name = ModelConstants.USER_SETTINGS_TYPE_PROPERTY)
private String type;
@Type(type = "json")
@Column(name = ModelConstants.USER_SETTINGS_SETTINGS)
private JsonNode settings;
public UserSettingsEntity(UserSettings userSettings) {
this.userId = userSettings.getUserId().getId();
this.type = userSettings.getType().name();
if (userSettings.getSettings() != null) {
this.settings= userSettings.getSettings();
this.settings = userSettings.getSettings();
}
}
@ -57,6 +65,7 @@ public class UserSettingsEntity implements ToData<UserSettings> {
public UserSettings toData() {
UserSettings userSettings = new UserSettings();
userSettings.setUserId(new UserId(userId));
userSettings.setType(UserSettingsType.valueOf(type));
if (settings != null) {
userSettings.setSettings(settings);
}

2
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -245,7 +245,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
return toAlarmApiResult(alarmRepository.createOrUpdateActiveAlarm(
request.getTenantId().getId(),
request.getCustomerId() != null ? request.getCustomerId().getId() : CustomerId.NULL_UUID,
UUID.randomUUID(),
request.getEdgeAlarmId() != null ? request.getEdgeAlarmId().getId() : UUID.randomUUID(),
System.currentTimeMillis(),
request.getOriginator().getId(),
request.getOriginator().getEntityType().ordinal(),

2
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java

@ -72,4 +72,6 @@ public interface DashboardInfoRepository extends JpaRepository<DashboardInfoEnti
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT di.title FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId AND di.id = :dashboardId")
String findTitleByTenantIdAndId(@Param("tenantId") UUID tenantId, @Param("dashboardId") UUID dashboardId);
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java

@ -118,4 +118,9 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
public DashboardInfo findFirstByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(dashboardInfoRepository.findFirstByTenantIdAndTitle(tenantId, name));
}
@Override
public String findTitleById(UUID tenantId, UUID dashboardId) {
return dashboardInfoRepository.findTitleByTenantIdAndId(tenantId, dashboardId);
}
}

12
dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java

@ -19,8 +19,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.UserSettingsEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
@ -41,13 +41,13 @@ public class JpaUserSettingsDao extends JpaAbstractDaoListeningExecutorService i
}
@Override
public UserSettings findById(TenantId tenantId, UserId userId) {
return DaoUtil.getData(userSettingsRepository.findById(userId.getId()));
public UserSettings findById(TenantId tenantId, UserSettingsCompositeKey id) {
return DaoUtil.getData(userSettingsRepository.findById(id));
}
@Override
public void removeById(TenantId tenantId, UserId userId) {
userSettingsRepository.deleteById(userId.getId());
public void removeById(TenantId tenantId, UserSettingsCompositeKey id) {
userSettingsRepository.deleteById(id);
}
}

3
dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java

@ -16,10 +16,11 @@
package org.thingsboard.server.dao.sql.user;
import org.springframework.data.jpa.repository.JpaRepository;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
import org.thingsboard.server.dao.model.sql.UserSettingsEntity;
import java.util.UUID;
public interface UserSettingsRepository extends JpaRepository<UserSettingsEntity, UUID> {
public interface UserSettingsRepository extends JpaRepository<UserSettingsEntity, UserSettingsCompositeKey> {
}

7
dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsCaffeineCache.java

@ -20,14 +20,13 @@ import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserSettings;
import org.thingsboard.server.dao.asset.AssetCacheKey;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("UserSettingsCache")
public class UserSettingsCaffeineCache extends CaffeineTbTransactionalCache<UserId, UserSettings> {
public class UserSettingsCaffeineCache extends CaffeineTbTransactionalCache<UserSettingsCompositeKey, UserSettings> {
public UserSettingsCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.USER_SETTINGS_CACHE);

7
dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java

@ -17,14 +17,15 @@ package org.thingsboard.server.dao.user;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
public interface UserSettingsDao {
UserSettings save(TenantId tenantId, UserSettings userSettings);
UserSettings findById(TenantId tenantId, UserId userId);
UserSettings findById(TenantId tenantId, UserSettingsCompositeKey key);
void removeById(TenantId tenantId, UserId userId);
void removeById(TenantId tenantId, UserSettingsCompositeKey key);
}

3
dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsEvictEvent.java

@ -17,8 +17,9 @@ package org.thingsboard.server.dao.user;
import lombok.Data;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
@Data
public class UserSettingsEvictEvent {
private final UserId userId;
private final UserSettingsCompositeKey key;
}

7
dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java

@ -23,14 +23,13 @@ import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbFSTRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserSettings;
import org.thingsboard.server.dao.asset.AssetCacheKey;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("UserSettingsCache")
public class UserSettingsRedisCache extends RedisTbTransactionalCache<UserId, UserSettings> {
public class UserSettingsRedisCache extends RedisTbTransactionalCache<UserSettingsCompositeKey, UserSettings> {
public UserSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.USER_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());

43
dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java

@ -15,38 +15,36 @@
*/
package org.thingsboard.server.dao.user;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.fge.jackson.NodeType;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import org.thingsboard.server.dao.entity.AbstractCachedService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.ConstraintValidator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Service("UserSettingsDaoService")
@Slf4j
@RequiredArgsConstructor
public class UserSettingsServiceImpl extends AbstractCachedService<UserId, UserSettings, UserSettingsEvictEvent> implements UserSettingsService {
public class UserSettingsServiceImpl extends AbstractCachedService<UserSettingsCompositeKey, UserSettings, UserSettingsEvictEvent> implements UserSettingsService {
public static final String INCORRECT_USER_ID = "Incorrect userId ";
private final UserSettingsDao userSettingsDao;
@ -58,33 +56,37 @@ public class UserSettingsServiceImpl extends AbstractCachedService<UserId, UserS
}
@Override
public void updateUserSettings(TenantId tenantId, UserId userId, JsonNode settings) {
public void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings) {
log.trace("Executing updateUserSettings for user [{}], [{}]", userId, settings);
validateId(userId, INCORRECT_USER_ID + userId);
UserSettings oldSettings = userSettingsDao.findById(tenantId, userId);
var key = new UserSettingsCompositeKey(userId.getId(), type.name());
UserSettings oldSettings = userSettingsDao.findById(tenantId, key);
JsonNode oldSettingsJson = oldSettings != null ? oldSettings.getSettings() : JacksonUtil.newObjectNode();
UserSettings newUserSettings = new UserSettings();
newUserSettings.setUserId(userId);
newUserSettings.setType(type);
newUserSettings.setSettings(update(oldSettingsJson, settings));
doSaveUserSettings(tenantId, newUserSettings);
}
@Override
public UserSettings findUserSettings(TenantId tenantId, UserId userId) {
public UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type) {
log.trace("Executing findUserSettings for user [{}]", userId);
validateId(userId, INCORRECT_USER_ID + userId);
return cache.getAndPutInTransaction(userId,
() -> userSettingsDao.findById(tenantId, userId), true);
var key = new UserSettingsCompositeKey(userId.getId(), type.name());
return cache.getAndPutInTransaction(key,
() -> userSettingsDao.findById(tenantId, key), true);
}
@Override
public void deleteUserSettings(TenantId tenantId, UserId userId, List<String> jsonPaths) {
public void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List<String> jsonPaths) {
log.trace("Executing deleteUserSettings for user [{}]", userId);
validateId(userId, INCORRECT_USER_ID + userId);
UserSettings userSettings = userSettingsDao.findById(tenantId, userId);
var key = new UserSettingsCompositeKey(userId.getId(), type.name());
UserSettings userSettings = userSettingsDao.findById(tenantId, key);
if (userSettings == null) {
return;
}
@ -95,7 +97,7 @@ public class UserSettingsServiceImpl extends AbstractCachedService<UserId, UserS
}
userSettings.setSettings(new ObjectMapper().readValue(dcSettings.jsonString(), ObjectNode.class));
} catch (Exception t) {
handleEvictEvent(new UserSettingsEvictEvent(userSettings.getUserId()));
handleEvictEvent(new UserSettingsEvictEvent(key));
throw new RuntimeException(t);
}
doSaveUserSettings(tenantId, userSettings);
@ -103,12 +105,13 @@ public class UserSettingsServiceImpl extends AbstractCachedService<UserId, UserS
private UserSettings doSaveUserSettings(TenantId tenantId, UserSettings userSettings) {
try {
ConstraintValidator.validateFields(userSettings);
validateJsonKeys(userSettings.getSettings());
UserSettings saved = userSettingsDao.save(tenantId, userSettings);
publishEvictEvent(new UserSettingsEvictEvent(userSettings.getUserId()));
publishEvictEvent(new UserSettingsEvictEvent(new UserSettingsCompositeKey(userSettings)));
return saved;
} catch (Exception t) {
handleEvictEvent(new UserSettingsEvictEvent(userSettings.getUserId()));
handleEvictEvent(new UserSettingsEvictEvent(new UserSettingsCompositeKey(userSettings)));
throw t;
}
}
@ -116,9 +119,7 @@ public class UserSettingsServiceImpl extends AbstractCachedService<UserId, UserS
@TransactionalEventListener(classes = UserSettingsEvictEvent.class)
@Override
public void handleEvictEvent(UserSettingsEvictEvent event) {
List<UserId> keys = new ArrayList<>();
keys.add(event.getUserId());
cache.evict(keys);
cache.evict(event.getKey());
}
private void validateJsonKeys(JsonNode userSettings) {

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

@ -850,9 +850,11 @@ CREATE TABLE IF NOT EXISTS notification (
) PARTITION BY RANGE (created_time);
CREATE TABLE IF NOT EXISTS user_settings (
user_id uuid NOT NULL CONSTRAINT user_settings_pkey PRIMARY KEY,
user_id uuid NOT NULL,
type VARCHAR(50) NOT NULL,
settings varchar(10000),
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE,
CONSTRAINT user_settings_pkey PRIMARY KEY (user_id, type)
);
DROP VIEW IF EXISTS alarm_info CASCADE;
@ -874,15 +876,15 @@ COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id
WHEN a.originator_type = 18 THEN (select name from edge where id = a.originator_id) END
, 'Deleted') originator_name,
COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id)
WHEN a.originator_type = 1 THEN (select COALESCE(title, email) from customer where id = a.originator_id)
WHEN a.originator_type = 1 THEN (select COALESCE(NULLIF(title, ''), email) from customer where id = a.originator_id)
WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id)
WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id)
WHEN a.originator_type = 4 THEN (select COALESCE(label, name) from asset where id = a.originator_id)
WHEN a.originator_type = 5 THEN (select COALESCE(label, name) from device where id = a.originator_id)
WHEN a.originator_type = 4 THEN (select COALESCE(NULLIF(label, ''), name) from asset where id = a.originator_id)
WHEN a.originator_type = 5 THEN (select COALESCE(NULLIF(label, ''), name) from device where id = a.originator_id)
WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id)
WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id)
WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id)
WHEN a.originator_type = 18 THEN (select COALESCE(label, name) from edge where id = a.originator_id) END
WHEN a.originator_type = 18 THEN (select COALESCE(NULLIF(label, ''), name) from edge where id = a.originator_id) END
, 'Deleted') as originator_label,
u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email as assignee_email
FROM alarm a

3
dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java

@ -23,7 +23,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -32,7 +31,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.user.UserService;

11
dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java

@ -27,7 +27,9 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import org.thingsboard.server.dao.AbstractJpaDaoTest;
import org.thingsboard.server.dao.service.AbstractServiceTest;
import org.thingsboard.server.dao.user.UserDao;
@ -66,17 +68,18 @@ public class JpaUserSettingsDaoTest extends AbstractJpaDaoTest {
public void testFindSettingsByUserId() {
UserSettings userSettings = createUserSettings(user.getId());
UserSettings retrievedUserSettings = userSettingsDao.findById(SYSTEM_TENANT_ID, user.getId());
UserSettings retrievedUserSettings = userSettingsDao.findById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettingsType.GENERAL.name()));
assertEquals(retrievedUserSettings.getSettings(), userSettings.getSettings());
userSettingsDao.removeById(SYSTEM_TENANT_ID, user.getId());
userSettingsDao.removeById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettingsType.GENERAL.name()));
UserSettings retrievedUserSettings2 = userSettingsDao.findById(SYSTEM_TENANT_ID, user.getId());
UserSettings retrievedUserSettings2 = userSettingsDao.findById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettingsType.GENERAL.name()));
assertNull(retrievedUserSettings2);
}
private UserSettings createUserSettings(UserId userId) {
UserSettings userSettings = new UserSettings();
userSettings.setType(UserSettingsType.GENERAL);
userSettings.setSettings(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10)));
userSettings.setUserId(userId);
return userSettingsDao.save(SYSTEM_TENANT_ID, userSettings);

3
dao/src/test/resources/application-test.properties

@ -68,6 +68,9 @@ cache.specs.edges.maxSize=100000
cache.specs.notificationRules.timeToLiveInMinutes=1440
cache.specs.notificationRules.maxSize=10000
cache.specs.dashboardTitles.timeToLiveInMinutes=1440
cache.specs.dashboardTitles.maxSize=10000
redis.connection.host=localhost
redis.connection.port=6379
redis.connection.db=0

1
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CustomerPageHelper.java

@ -141,6 +141,7 @@ public class CustomerPageHelper extends CustomerPageElements {
assignedField().click();
setDashboard();
listOfEntity().get(0).click();
assignedField().sendKeys(Keys.ESCAPE);
submitAssignedBtn().click();
}

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/SideBarMenuViewElements.java

@ -27,7 +27,7 @@ public class SideBarMenuViewElements extends AbstractBasePage {
private static final String RULE_CHAINS_BTN = "//mat-toolbar//a[@href='/ruleChains']";
private static final String CUSTOMER_BTN = "//mat-toolbar//a[@href='/customers']";
private static final String DASHBOARD_BTN = "//mat-toolbar//a[@href='/dashboards']";
private static final String PROFILES_BTN = "//mat-toolbar//a[@href='/profiles']";
private static final String PROFILES_BTN = "//mat-toolbar//mat-icon[text()='badge']/ancestor::tb-menu-toggle";
private static final String DEVICE_PROFILE_BTN = "//mat-toolbar//a[@href='/profiles/deviceProfiles']";
private static final String ASSET_PROFILE_BTN = "//mat-toolbar//a[@href='/profiles/assetProfiles']";

2
pom.xml

@ -78,7 +78,7 @@
<zookeeper.version>3.5.5</zookeeper.version>
<protobuf.version>3.21.9</protobuf.version>
<grpc.version>1.42.1</grpc.version>
<tbel.version>1.0.5</tbel.version>
<tbel.version>1.0.6</tbel.version>
<lombok.version>1.18.18</lombok.version>
<paho.client.version>1.2.4</paho.client.version>
<paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version>

17
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java

@ -74,7 +74,8 @@ public abstract class AbstractTbMsgPushNode<T extends BaseTbMsgPushNodeConfigura
protected S buildEvent(TbMsg msg, TbContext ctx) {
String msgType = msg.getType();
if (DataConstants.ALARM.equals(msgType)) {
return buildEvent(ctx.getTenantId(), EdgeEventActionType.ADDED, getUUIDFromMsgData(msg), getAlarmEventType(), null);
EdgeEventActionType actionType = getAlarmActionType(msg);
return buildEvent(ctx.getTenantId(), actionType, getUUIDFromMsgData(msg), getAlarmEventType(), null);
} else {
EdgeEventActionType actionType = getEdgeEventActionTypeByMsgType(msgType);
Map<String, Object> entityBody = new HashMap<>();
@ -107,6 +108,20 @@ public abstract class AbstractTbMsgPushNode<T extends BaseTbMsgPushNodeConfigura
}
}
private static EdgeEventActionType getAlarmActionType(TbMsg msg) {
boolean isNewAlarm = Boolean.parseBoolean(msg.getMetaData().getValue(DataConstants.IS_NEW_ALARM));
boolean isClearedAlarm = Boolean.parseBoolean(msg.getMetaData().getValue(DataConstants.IS_CLEARED_ALARM));
EdgeEventActionType eventAction;
if (isNewAlarm) {
eventAction = EdgeEventActionType.ADDED;
} else if (isClearedAlarm) {
eventAction = EdgeEventActionType.ALARM_CLEAR;
} else {
eventAction = EdgeEventActionType.UPDATED;
}
return eventAction;
}
abstract S buildEvent(TenantId tenantId, EdgeEventActionType eventAction, UUID entityId, U eventType, JsonNode entityBody);
abstract U getEventTypeByEntityType(EntityType entityType);

2
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

86
ui-ngx/package.json

@ -14,53 +14,53 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^15.2.0",
"@angular/cdk": "^15.2.0",
"@angular/common": "^15.2.0",
"@angular/compiler": "^15.2.0",
"@angular/core": "^15.2.0",
"@angular/animations": "^15.2.5",
"@angular/cdk": "^15.2.5",
"@angular/common": "^15.2.5",
"@angular/compiler": "^15.2.5",
"@angular/core": "^15.2.5",
"@angular/flex-layout": "^15.0.0-beta.42",
"@angular/forms": "^15.2.0",
"@angular/material": "^15.2.0",
"@angular/platform-browser": "^15.2.0",
"@angular/platform-browser-dynamic": "^15.2.0",
"@angular/router": "^15.2.0",
"@angular/forms": "^15.2.5",
"@angular/material": "^15.2.5",
"@angular/platform-browser": "^15.2.5",
"@angular/platform-browser-dynamic": "^15.2.5",
"@angular/router": "^15.2.5",
"@auth0/angular-jwt": "^5.1.2",
"@date-io/core": "1.3.7",
"@date-io/date-fns": "1.3.7",
"@flowjs/flow.js": "^2.14.1",
"@flowjs/ngx-flow": "~0.6.0",
"@geoman-io/leaflet-geoman-free": "^2.13.0",
"@juggle/resize-observer": "^3.3.1",
"@juggle/resize-observer": "^3.4.0",
"@mat-datetimepicker/core": "~11.0.3",
"@material-ui/core": "4.12.3",
"@material-ui/icons": "4.11.2",
"@material-ui/pickers": "3.3.10",
"@mdi/svg": "^7.1.96",
"@messageformat/core": "^3.0.1",
"@ngrx/effects": "^15.3.0",
"@ngrx/store": "^15.3.0",
"@ngrx/store-devtools": "^15.3.0",
"@mdi/svg": "^7.2.96",
"@messageformat/core": "^3.1.0",
"@ngrx/effects": "^15.4.0",
"@ngrx/store": "^15.4.0",
"@ngrx/store-devtools": "^15.4.0",
"@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0",
"@tinymce/tinymce-angular": "^7.0.0",
"ace-builds": "1.4.13",
"ace-diff": "^3.0.3",
"angular-gridster2": "~15.0.3",
"angular-gridster2": "~15.0.4",
"angular2-hotkeys": "^13.1.0",
"canvas-gauges": "^2.1.7",
"core-js": "^3.26.1",
"core-js": "^3.29.1",
"date-fns": "2.0.0-alpha.27",
"dayjs": "1.11.4",
"flot": "https://github.com/thingsboard/flot.git#0.9-work",
"flot.curvedlines": "https://github.com/MichaelZinsmaier/CurvedLines.git#master",
"font-awesome": "^4.7.0",
"html2canvas": "^1.4.1",
"jquery": "^3.6.0",
"jquery.terminal": "^2.29.4",
"js-beautify": "^1.14.0",
"jquery": "^3.6.3",
"jquery.terminal": "^2.35.3",
"js-beautify": "^1.14.7",
"json-schema-defaults": "^0.4.0",
"jstree": "^3.3.12",
"jstree": "^3.3.15",
"jstree-bootstrap-theme": "^1.0.1",
"jszip": "^3.10.1",
"leaflet": "~1.8.0",
@ -71,15 +71,15 @@
"libphonenumber-js": "^1.10.4",
"marked": "^4.0.17",
"moment": "^2.29.4",
"moment-timezone": "^0.5.41",
"moment-timezone": "^0.5.42",
"ngx-clipboard": "^15.1.0",
"ngx-color-picker": "^13.0.0",
"ngx-color-picker": "^14.0.0",
"ngx-daterangepicker-material": "^6.0.4",
"ngx-drag-drop": "^15.0.1",
"ngx-flowchart": "https://github.com/thingsboard/ngx-flowchart.git#release/2.0.0",
"ngx-hm-carousel": "^3.0.0",
"ngx-markdown": "^15.1.1",
"ngx-sharebuttons": "^11.0.0",
"ngx-markdown": "^15.1.2",
"ngx-sharebuttons": "^12.0.0",
"ngx-translate-messageformat-compiler": "^6.2.0",
"objectpath": "^2.0.0",
"prettier": "^2.8.3",
@ -101,22 +101,22 @@
"tinymce": "~5.10.7",
"tooltipster": "^4.2.8",
"ts-transformer-keys": "^0.4.4",
"tslib": "^2.4.1",
"tslib": "^2.5.0",
"tv4": "^1.3.0",
"typeface-roboto": "^1.1.13",
"zone.js": "~0.11.8"
"zone.js": "~0.13.0"
},
"devDependencies": {
"@angular-builders/custom-webpack": "~15.0.0",
"@angular-devkit/build-angular": "^15.2.0",
"@angular-devkit/build-angular": "^15.2.4",
"@angular-eslint/builder": "15.2.1",
"@angular-eslint/eslint-plugin": "15.2.1",
"@angular-eslint/eslint-plugin-template": "15.2.1",
"@angular-eslint/schematics": "15.2.1",
"@angular-eslint/template-parser": "15.2.1",
"@angular/cli": "^15.2.0",
"@angular/compiler-cli": "^15.2.0",
"@angular/language-service": "^15.2.0",
"@angular/cli": "^15.2.4",
"@angular/compiler-cli": "^15.2.5",
"@angular/language-service": "^15.2.5",
"@ngtools/webpack": "^15.1.6",
"@types/ace-diff": "^2.1.1",
"@types/canvas-gauges": "^2.1.4",
@ -124,29 +124,27 @@
"@types/flowjs": "^2.13.9",
"@types/jasmine": "~3.10.2",
"@types/jasminewd2": "^2.0.10",
"@types/jquery": "^3.5.9",
"@types/jquery": "^3.5.16",
"@types/js-beautify": "^1.13.3",
"@types/jstree": "^3.3.41",
"@types/leaflet": "^1.7.11",
"@types/leaflet": "^1.8.0",
"@types/leaflet-polylinedecorator": "^1.6.1",
"@types/leaflet-providers": "^1.2.1",
"@types/leaflet.gridlayer.googlemutant": "^0.4.6",
"@types/leaflet.markercluster": "^1.5.1",
"@types/lodash": "^4.14.177",
"@types/marked": "^4.0.3",
"@types/mousetrap": "^1.6.0",
"@types/node": "~15.14.9",
"@types/lodash": "^4.14.192",
"@types/marked": "^4.0.8",
"@types/node": "~18.15.11",
"@types/raphael": "^2.3.2",
"@types/react": "17.0.37",
"@types/react-dom": "17.0.11",
"@types/systemjs": "6.1.1",
"@types/tinycolor2": "^1.4.3",
"@types/tooltipster": "^0.0.31",
"@typescript-eslint/eslint-plugin": "5.43.0",
"@typescript-eslint/parser": "5.43.0",
"@typescript-eslint/eslint-plugin": "5.57.0",
"@typescript-eslint/parser": "5.57.0",
"compression-webpack-plugin": "^10.0.0",
"directory-tree": "^3.5.1",
"eslint": "^8.28.0",
"eslint": "^8.37.0",
"eslint-plugin-import": "latest",
"eslint-plugin-jsdoc": "latest",
"eslint-plugin-prefer-arrow": "latest",
@ -163,8 +161,8 @@
"protractor": "~7.0.0",
"raw-loader": "^4.0.2",
"ts-node": "^10.9.1",
"typescript": "~4.8.4",
"webpack": "^5.75.0"
"typescript": "~4.9.5",
"webpack": "^5.77.0"
},
"resolutions": {
"@types/react": "17.0.37",

2
ui-ngx/src/app/core/api/widget-api.models.ts

@ -160,7 +160,7 @@ export interface IStateController {
openRightLayout(): void;
preserveState(): void;
cleanupPreservedStates(): void;
navigatePrevState(index: number): void;
navigatePrevState(index: number, params?: StateParams): void;
getStateId(): string;
getStateIndex(): number;
getStateIdAtIndex(index: number): string;

9
ui-ngx/src/app/core/services/menu.service.ts

@ -835,15 +835,6 @@ export class MenuService {
}
);
}
sections.push(
{
id: guid(),
name: 'dashboard.dashboards',
type: 'link',
path: '/dashboards',
icon: 'dashboard'
}
);
sections.push(
{
id: guid(),

13
ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts

@ -168,10 +168,15 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
}
}
public navigatePrevState(index: number): void {
if (index < this.stateObject.length - 1) {
this.stateObject.splice(index + 1, this.stateObject.length - index - 1);
this.gotoState(this.stateObject[this.stateObject.length - 1].id, true);
public navigatePrevState(index: number, params?: StateParams): void {
const lastStateIndex = this.stateObject.length - 1;
if (index < lastStateIndex) {
this.stateObject.splice(index + 1, lastStateIndex - index);
const selectedStateIndex = this.stateObject.length - 1;
if (params) {
this.stateObject[selectedStateIndex].params = params;
}
this.gotoState(this.stateObject[selectedStateIndex].id, true);
}
}

7
ui-ngx/src/app/modules/home/components/dashboard-page/states/entity-state-controller.component.ts

@ -195,11 +195,14 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
}
}
public navigatePrevState(index: number): void {
public navigatePrevState(index: number, params?: StateParams): void {
if (index < this.stateObject.length - 1) {
this.stateObject.splice(index + 1, this.stateObject.length - index - 1);
this.selectedStateIndex = this.stateObject.length - 1;
this.gotoState(this.stateObject[this.stateObject.length - 1].id, true);
if (params) {
this.stateObject[this.selectedStateIndex].params = params;
}
this.gotoState(this.stateObject[this.selectedStateIndex].id, true);
}
}

2
ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts

@ -199,7 +199,7 @@ export abstract class StateControllerComponent implements IStateControllerCompon
public abstract getStateParamsByStateId(stateId: string): StateParams;
public abstract navigatePrevState(index: number): void;
public abstract navigatePrevState(index: number, params?: StateParams): void;
public abstract openState(id: string, params?: StateParams, openRightLayout?: boolean): void;

14
ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts

@ -16,7 +16,6 @@
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import {
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
@ -85,7 +84,7 @@ import { moveItemInArray } from '@angular/cdk/drag-drop';
],
encapsulation: ViewEncapsulation.None
})
export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges, ErrorStateMatcher {
export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChanges, ErrorStateMatcher {
datasourceTypes = DatasourceType;
widgetTypes = widgetType;
@ -330,17 +329,6 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
return originalErrorState || customErrorState;
}
ngAfterViewInit(): void {
this.chipList._chips.forEach((chip) => {
chip._mousedown = () => {};
});
this.chipList._chips.changes.subscribe(() => {
this.chipList._chips.forEach((chip) => {
chip._mousedown = () => {};
});
});
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {

6
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts

@ -133,16 +133,20 @@ export const hereMapProviderTranslationMap = new Map<HereMapProvider, string>(
export interface HereMapProviderSettings {
mapProviderHere: HereMapProvider;
credentials: {
useV3: boolean;
app_id: string;
app_code: string;
apiKey: string;
};
}
export const defaultHereMapProviderSettings: HereMapProviderSettings = {
mapProviderHere: HereMapProvider.hereNormalDay,
credentials: {
useV3: true,
app_id: 'AhM6TzD9ThyK78CT3ptx',
app_code: 'p6NPiITB3Vv0GMUFnkLOOg'
app_code: 'p6NPiITB3Vv0GMUFnkLOOg',
apiKey: 'kVXykxAfZ6LS4EbCTO02soFVfjA7HoBzNVVH9u7nzoE'
}
};

7
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts

@ -18,6 +18,7 @@ import L from 'leaflet';
import LeafletMap from '../leaflet-map';
import { DEFAULT_ZOOM_LEVEL, WidgetUnitedMapSettings } from '../map-models';
import { WidgetContext } from '@home/models/widget-component.models';
import { isDefinedAndNotNull } from '@core/utils';
export class HEREMap extends LeafletMap {
constructor(ctx: WidgetContext, $container, options: WidgetUnitedMapSettings) {
@ -26,7 +27,11 @@ export class HEREMap extends LeafletMap {
doubleClickZoom: !this.options.disableDoubleClickZooming,
zoomControl: !this.options.disableZoomControl
}).setView(options?.parsedDefaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);
const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials);
let provider = options.mapProviderHere || 'HERE.normalDay';
if (options.credentials.useV3 && isDefinedAndNotNull(options.credentials.apiKey)) {
provider = options.mapProviderHere?.replace('HERE', 'HEREv3') || 'HEREv3.normalDay';
}
const tileLayer = (L.tileLayer as any).provider(provider, options.credentials);
tileLayer.addTo(map);
super.setMap(map);
}

28
ui-ngx/src/app/modules/home/components/widget/lib/settings/map/here-map-provider-settings.component.html

@ -27,14 +27,26 @@
<section class="tb-widget-settings" formGroupName="credentials">
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.maps.credentials</legend>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.maps.here-app-id</mat-label>
<input required matInput formControlName="app_id">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.maps.here-app-code</mat-label>
<input required matInput formControlName="app_code">
</mat-form-field>
<mat-slide-toggle formControlName="useV3" style="margin-bottom: 16px;">
{{ 'widgets.maps.here-use-new-version-api-3' | translate }}
</mat-slide-toggle>
<div *ngIf="!providerSettingsFormGroup.get('credentials.useV3').value; else apiKey" fxLayout="row" fxLayoutGap="8px"
fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.maps.here-app-id</mat-label>
<input required matInput formControlName="app_id">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.maps.here-app-code</mat-label>
<input required matInput formControlName="app_code">
</mat-form-field>
</div>
<ng-template #apiKey>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.maps.here-api-key</mat-label>
<input required matInput formControlName="apiKey">
</mat-form-field>
</ng-template>
</fieldset>
</section>
</section>

35
ui-ngx/src/app/modules/home/components/widget/lib/settings/map/here-map-provider-settings.component.ts

@ -17,9 +17,9 @@
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator,
@ -34,6 +34,7 @@ import {
HereMapProviderSettings,
hereMapProviderTranslationMap
} from '@home/components/widget/lib/maps/map-models';
import { isDefinedAndNotNull } from '@core/utils';
@Component({
selector: 'tb-here-map-provider-settings',
@ -61,7 +62,7 @@ export class HereMapProviderSettingsComponent extends PageComponent implements O
private propagateChange = null;
public providerSettingsFormGroup: UntypedFormGroup;
public providerSettingsFormGroup: FormGroup;
hereMapProviders = Object.values(HereMapProvider);
@ -69,7 +70,7 @@ export class HereMapProviderSettingsComponent extends PageComponent implements O
constructor(protected store: Store<AppState>,
private translate: TranslateService,
private fb: UntypedFormBuilder) {
private fb: FormBuilder) {
super(store);
}
@ -77,10 +78,23 @@ export class HereMapProviderSettingsComponent extends PageComponent implements O
this.providerSettingsFormGroup = this.fb.group({
mapProviderHere: [null, [Validators.required]],
credentials: this.fb.group({
useV3: [true],
app_id: [null, [Validators.required]],
app_code: [null, [Validators.required]]
app_code: [null, [Validators.required]],
apiKey: [null, [Validators.required]]
})
});
this.providerSettingsFormGroup.get('credentials.useV3').valueChanges.subscribe(value => {
if (value) {
this.providerSettingsFormGroup.get('credentials.apiKey').enable({emitEvent: false});
this.providerSettingsFormGroup.get('credentials.app_id').disable({emitEvent: false});
this.providerSettingsFormGroup.get('credentials.app_code').disable({emitEvent: false});
} else {
this.providerSettingsFormGroup.get('credentials.apiKey').disable({emitEvent: false});
this.providerSettingsFormGroup.get('credentials.app_id').enable({emitEvent: false});
this.providerSettingsFormGroup.get('credentials.app_code').enable({emitEvent: false});
}
});
this.providerSettingsFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
@ -99,17 +113,24 @@ export class HereMapProviderSettingsComponent extends PageComponent implements O
this.providerSettingsFormGroup.disable({emitEvent: false});
} else {
this.providerSettingsFormGroup.enable({emitEvent: false});
this.providerSettingsFormGroup.get('credentials.useV3').updateValueAndValidity({onlySelf: true});
}
}
writeValue(value: HereMapProviderSettings): void {
if (!isDefinedAndNotNull(value.credentials.useV3)) {
if (isDefinedAndNotNull(value.credentials.app_id) && isDefinedAndNotNull(value.credentials.app_code)) {
value.credentials.useV3 = false;
}
}
this.modelValue = value;
this.providerSettingsFormGroup.patchValue(
value, {emitEvent: false}
);
this.providerSettingsFormGroup.get('credentials.useV3').updateValueAndValidity({onlySelf: true});
}
public validate(c: UntypedFormControl) {
public validate(c: FormControl) {
return this.providerSettingsFormGroup.valid ? null : {
hereMapProviderSettings: {
valid: false,

4
ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts

@ -142,11 +142,11 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
);
} else if (ruleChainScope === 'edges') {
columns.push(
new EntityTableColumn<RuleChain>('root', 'rulechain.edge-template-root', '60px',
new EntityTableColumn<RuleChain>('root', 'rulechain.edge-template-root', '70px',
entity => {
return checkBoxCell(entity.root);
}),
new EntityTableColumn<RuleChain>('assignToEdge', 'rulechain.assign-to-edge', '60px',
new EntityTableColumn<RuleChain>('assignToEdge', 'rulechain.assign-to-edge', '70px',
entity => {
return checkBoxCell(this.isAutoAssignToEdgeRuleChain(entity));
})

4
ui-ngx/src/app/shared/models/notification.models.ts

@ -362,7 +362,7 @@ export enum NotificationTargetConfigType {
AFFECTED_TENANT_ADMINISTRATORS = 'AFFECTED_TENANT_ADMINISTRATORS'
}
interface NotificationTargetConfigTypeInfo {
export interface NotificationTargetConfigTypeInfo {
name: string;
hint?: string;
}
@ -453,7 +453,7 @@ export const ActionButtonLinkTypeTranslateMap = new Map<ActionButtonLinkType, st
[ActionButtonLinkType.DASHBOARD, 'notification.link-type.dashboard']
]);
interface NotificationTemplateTypeTranslate {
export interface NotificationTemplateTypeTranslate {
name: string;
helpId?: string;
}

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

@ -1396,10 +1396,10 @@
"create-new-device-profile": "Create a new one!",
"mqtt-device-topic-filters": "MQTT device topic filters",
"mqtt-device-topic-filters-unique": "MQTT device topic filters need to be unique.",
"mqtt-device-topic-filters-spark-plug": "MQTT device topic filters SparkPlug.",
"mqtt-device-topic-filters-spark-plug-hint": "Default - telemetry. Example: namespace/group_id/message_type/edge_node_id/[device_id].",
"mqtt-device-topic-filters-spark-plug-attribute-metric-names": "SparkPlug attributes metric names",
"mqtt-device-topic-filters-spark-plug-attribute-metric-names-hint": "Names of SparkPlug metrics that will be stored as device attributes. All other metrics will be stored as device telemetry",
"mqtt-device-topic-filters-spark-plug": "MQTT Sparkplug B Edge of Network (EoN) node.",
"mqtt-device-topic-filters-spark-plug-hint": "Allow connections from EoN nodes with Sparkplug B payload and topic format.",
"mqtt-device-topic-filters-spark-plug-attribute-metric-names": "SparkPlug metrics to store as attributes.",
"mqtt-device-topic-filters-spark-plug-attribute-metric-names-hint": "Names of SparkPlug metrics that will be stored as device attributes. All other metrics will be stored as device telemetry.",
"mqtt-device-payload-type": "MQTT device payload",
"mqtt-device-payload-type-json": "JSON",
"mqtt-device-payload-type-proto": "Protobuf",
@ -4818,6 +4818,8 @@
"credentials": "Credentials",
"here-app-id": "HERE app id",
"here-app-code": "HERE app code",
"here-api-key": "HERE API key",
"here-use-new-version-api-3": "Use API version 3",
"tencent-maps-api-key": "Tencent Maps API Key",
"tencent-map-type-roadmap": "Roadmap",
"tencent-map-type-satelite": "Satellite",

6
ui-ngx/src/styles.scss

@ -568,10 +568,16 @@ mat-label {
.mat-toolbar.mat-mdc-table-toolbar .mat-mdc-form-field.mat-form-field-appearance-fill,
.mat-mdc-form-field.mat-form-field-appearance-fill.tb-appearance-transparent {
.mdc-text-field--filled {
&:before {
content: none;
}
background-color: transparent;
padding: 0;
}
.mat-mdc-form-field-focus-overlay {
&:before {
content: none;
}
background-color: transparent;
}
.mat-mdc-select-arrow-wrapper {

54
ui-ngx/src/theme-overwrites.scss

@ -0,0 +1,54 @@
/**
* 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.
*/
@use '@angular/material' as mat;
@mixin theme-overwrites($primary, $config-or-theme) {
@include _mat-form-field-overwrites($primary, $config-or-theme);
}
@mixin _mat-form-field-overwrites($primary, $config-or-theme) {
.mat-mdc-form-field.mat-form-field-appearance-fill {
.mdc-text-field--filled, .mat-mdc-form-field-focus-overlay {
&:before {
content: "";
display: block;
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-color: mat.get-color-from-palette($primary);
}
background-color: transparent;
}
.mdc-text-field--filled {
&:before {
opacity: 0.03;
}
&:not(.mdc-text-field--disabled) {
&:before {
opacity: 0.06;
}
background-color: transparent;
}
}
.mat-mdc-form-field-focus-overlay {
&:before {
opacity: 0.87;
}
}
}
}

18
ui-ngx/src/theme.scss

@ -14,6 +14,7 @@
* limitations under the License.
*/
@use '@angular/material' as mat;
@use 'theme-overwrites' as overwrites;
@import '../theme/datepicker-theme';
@import './scss/constants';
@ -242,12 +243,29 @@ $tb-dark-theme: map_merge($tb-dark-theme, $color);
div.tb-dashboard-page.mobile-app {
@include mat-fab-toolbar-inverse-theme($tb-theme);
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(203, 203, 203, 0.8);
&:hover {
background: rgba(165, 165, 165, 0.65);
}
}
}
.tb-default {
@include mat.all-component-themes($tb-theme);
@include mat-datetimepicker-theme($tb-theme);
@include tb-components-theme($tb-theme);
@include overwrites.theme-overwrites($tb-primary, $tb-theme);
@include mat.typography-hierarchy($tb-theme);
}

2
ui-ngx/src/tsconfig.app.json

@ -3,7 +3,7 @@
"compilerOptions": {
"outDir": "../out-tsc/app",
"types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify",
"react", "react-dom", "jstree", "raphael", "canvas-gauges", "systemjs"]
"react", "react-dom", "raphael", "canvas-gauges", "systemjs"]
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true

685
ui-ngx/src/typings/jquery.jstree.typings.d.ts

@ -14,6 +14,17 @@
/// limitations under the License.
///
interface JQueryStatic {
jstree?: JSTreeStatic | undefined;
}
interface JQuery {
jstree(optionsOrArgs?: JSTreeStaticDefaults | boolean): JSTree;
jstree(...args: any[]): any;
on(events: 'changed.jstree', handler: (e: Event, data: JSTreeEventData) => void): this;
on(events: 'model.jstree', handler: (e: Event, data: JSTreeModelEventData) => void): this;
}
interface JSTreeEventData {
instance: JSTree;
}
@ -23,7 +34,675 @@ interface JSTreeModelEventData extends JSTreeEventData {
parent: string;
}
interface JQuery {
on(events: 'changed.jstree', handler: (e: Event, data: JSTreeEventData) => void): this;
on(events: 'model.jstree', handler: (e: Event, data: JSTreeModelEventData) => void): this;
interface JSTreeStatic {
version: string;
defaults: JSTreeStaticDefaults;
plugins: any[];
path: string;
idregex: any;
root: string;
create(el: HTMLElement|JQuery|string, options?: JSTreeStaticDefaults): JSTree;
destroy(): void;
core(id: number): void;
reference(needle: HTMLElement|JQuery|string): JSTree;
}
interface JSTreeStaticDefaults {
plugins: string[];
core: JSTreeStaticDefaultsCore;
checkbox?: JSTreeStaticDefaultsCheckbox | undefined;
contextmenu?: JSTreeStaticDefaultsContextMenu | undefined;
dnd?: JSTreeStaticDefaultsDragNDrop | undefined;
massload?: JSTreeStaticDefaultsMassload | undefined;
search?: JSTreeStaticDefaultsSearch | undefined;
sort?: ((x: any, y: any) => number) | undefined;
state?: JSTreeStaticDefaultsState | undefined;
types?: any;
unique?: JSTreeStaticDefaultsUnique | undefined;
conditionalselect?: (() => boolean) | undefined;
conditionalclose?: (() => boolean) | undefined;
}
interface JSTreeStaticDefaultsCore {
data?: any;
strings?: any;
check_callback?: any;
error: () => any;
animation?: any;
multiple?: boolean | undefined;
themes?: JSTreeStaticDefaultsCoreThemes | undefined;
expand_selected_onload?: boolean | undefined;
worker?: boolean | undefined;
force_text?: boolean | undefined;
dblclick_toggle?: boolean | undefined;
loaded_state?: boolean | undefined;
restore_focus?: boolean | undefined;
keyboard?: JSTreeStaticDefaultsCoreKeyboard | undefined;
}
interface JSTreeStaticDefaultsCoreThemes {
name?: string | undefined;
url?: string | undefined;
dir?: string | undefined;
dots?: boolean | undefined;
icons?: boolean | undefined;
ellipsis?: boolean | undefined;
stripes?: boolean | undefined;
variant?: any;
responsive?: boolean | undefined;
}
interface JSTreeStaticDefaultsCoreKeyboard {
'ctrl-space': (e: Event) => void;
'enter': (e: Event) => void;
'left': (e: Event) => void;
'up': (e: Event) => void;
'right': (e: Event) => void;
'down': (e: Event) => void;
'*': (e: Event) => void;
'home': (e: Event) => void;
'end': (e: Event) => void;
'f2': (e: Event) => void;
}
interface JSTreeStaticDefaultsCheckbox {
visible: boolean;
three_state: boolean;
whole_node: boolean;
keep_selected_style: boolean;
cascade: string;
tie_selection: boolean;
cascade_to_disabled: boolean;
cascade_to_hidden: boolean;
}
interface JSTreeStaticDefaultsContextMenu {
select_node: boolean;
show_at_node: boolean;
items: any;
}
interface JSTreeStaticDefaultsDragNDrop {
copy: boolean;
open_timeout: number;
is_draggable: boolean;
check_while_dragging: boolean;
always_copy: boolean;
inside_pos: any;
drag_selection: boolean;
touch: boolean;
large_drop_target: boolean;
large_drag_target: boolean;
use_html5: boolean;
}
interface JSTreeStaticDefaultsMassload {
url: any;
data: any;
}
interface JSTreeStaticDefaultsSearch {
ajax: any;
fuzzy: boolean;
case_sensitive: boolean;
show_only_matches: boolean;
show_only_matches_children: boolean;
close_opened_onclear: boolean;
search_leaves_only: boolean;
search_callback: any;
}
interface JSTreeStaticDefaultsState {
key: string;
events: string;
ttl: any;
filter: any;
preserve_loaded: boolean;
}
interface JSTreeStaticDefaultsUnique {
case_sensitive: boolean;
trim_whitespace: boolean;
duplicate: (name: string, counter: number) => string;
}
interface JSTree extends JQuery {
plugin: (deco: string, opts?: any) => JSTree;
init: (el: HTMLElement|JQuery|string, options: any) => void;
destroy: (keep_html?: boolean) => void;
teardown: () => void;
_create_prototype_node: () => HTMLElement;
bind: () => any;
_kbevent_to_func: (e: Event) => ((e: Event) => void);
unbind: () => any;
/*
* defined in JQuery
*/
// trigger: (ev: string, data?: Object) => any;
get_container: () => JQuery;
get_container_ul: () => JQuery;
get_string: (key: string) => string;
_firstChild: (dom: HTMLElement) => HTMLElement;
_nextSibling: (dom: HTMLElement) => HTMLElement;
_previousSibling: (dom: HTMLElement) => HTMLElement;
get_node: (obj: any, as_dom?: boolean) => any;
get_path: (obj: any, glue?: string, ids?: boolean) => any;
get_next_dom: (obj: any, strict?: boolean) => JQuery;
get_prev_dom: (obj: any, strict?: boolean) => JQuery;
get_parent: (obj: any) => string;
get_children_dom: (obj: any) => JQuery|boolean;
is_parent: (obj: any) => boolean;
is_loaded: (obj: any) => boolean;
is_loading: (obj: any) => boolean;
is_open: (obj: any) => boolean;
is_closed: (obj: any) => boolean;
is_leaf: (obj: any) => boolean;
load_node: (obj: any, callback: (node: any, status: boolean) => void) => boolean;
_load_nodes: (nodes: any[], callback?: (nodes: any[]) => void, is_callback?: boolean, force_reload?: boolean) => void;
load_all: (obj: any, callback: () => void) => void;
_load_node: (obj: any, callback?: (status: boolean) => void) => boolean;
_node_changed: (obj: any) => void;
_append_html_data: (dom: any, data: string, cb: (flag: boolean) => void) => void;
_append_json_data: (dom: any, data: string, cb: (flag: boolean) => void, force_processing: boolean) => void;
_parse_model_from_html: (d: JQuery, p?: string, ps?: any[]) => string;
_parse_model_from_flat_json: (d: any, p?: string, ps?: any[]) => string;
_parse_model_from_json: (d: any, p?: string, ps?: any[]) => string;
_redraw: () => void ;
redraw: (full?: boolean) => void;
draw_children: (node: any) => void;
redraw_node: (node: any, deep: boolean, is_callback: boolean, force_render: boolean) => void;
open_node: (obj: any, callback?: any, animation?: any) => void;
_open_to: (obj: any) => void;
close_node: (obj: any, animation?: any) => void;
toggle_node: (obj: any) => void;
open_all: (obj?: any, animation?: number, original_obj?: any) => void;
close_all: (obj?: any, animation?: number) => void;
is_disabled: (obj: any) => boolean;
enable_node: (obj: any) => boolean;
disable_node: (obj: any) => boolean;
is_hidden: (obj: any) => boolean;
hide_node: (obj: any, skip_redraw: boolean) => boolean;
show_node: (obj: any, skip_redraw: boolean) => boolean;
hide_all: (skip_redraw: boolean) => boolean;
show_all: (skip_redraw: boolean) => boolean;
activate_node: (obj: any, e: any) => void;
hover_node: (obj: any) => void;
dehover_node: (obj: any) => void;
select_node: (obj: any, supress_event?: boolean, prevent_open?: boolean, e?: any) => any;
deselect_node: (obj: any, supress_event?: boolean, e?: any) => void;
select_all: (supress_event?: boolean) => void;
deselect_all: (supress_event?: boolean) => void;
is_selected: (obj: any) => boolean;
get_selected: (full?: any) => any[];
get_top_selected: (full?: any) => any[];
get_bottom_selected: (full?: any) => any[];
get_state: () => any;
set_state: (state: any, callback: () => void) => void;
refresh: (skip_loading?: boolean, forget_state?: any) => void;
refresh_node: (obj: any) => void;
set_id: (obj: any, id: string) => boolean;
get_text: (obj: any) => string;
set_text: (obj: any, val: string) => boolean;
get_json: (obj?: any, options?: JSTreeGetJsonOptions, flat?: boolean) => any;
create_node: (par?: any, node?: any, pos?: any, callback?: any, is_loaded?: boolean) => string;
rename_node: (obj: any, val: string) => boolean;
delete_node: (obj: any) => boolean;
check: (chk: string, obj: any, par: any, pos: any, more: any) => boolean;
last_error: () => any;
move_node: (obj: any, par: any, pos?: any, callback?: (node: any, new_par: any, pos: any) => void,
is_loaded?: boolean, skip_redraw?: boolean, origin?: boolean) => void;
copy_node: (obj: any, par: any, pos?: any, callback?: (node: any, new_par: any, pos: any) => void,
is_loaded?: boolean, skip_redraw?: boolean, origin?: boolean) => void;
cut: (obj: any) => void;
copy: (obj: any) => void;
get_buffer: () => JsTreeBuffer;
can_paste: () => boolean;
paste: (obj: any, pos: any) => void;
clear_buffer: () => void;
edit: (obj: any, default_text?: string, callback?: (node: any, status: boolean, canceled: boolean) => void) => void;
set_theme: (theme_name: string, theme_url?: any) => void;
get_theme: () => string;
set_theme_variant: (variant_name: any) => void;
get_theme_variant: () => string;
show_stripes: () => void;
hide_stripes: () => void;
toggle_stripes: () => void;
show_dots: () => void;
hide_dots: () => void;
toggle_dots: () => void;
show_icons: () => void;
hide_icons: () => void;
toggle_icons: () => void;
show_ellipsis: () => void;
hide_ellipsis: () => void;
toggle_ellipsis: () => void;
set_icon: (obj: any, icon: string) => void;
get_icon: (obj: any) => string;
hide_icon: (obj: any) => void;
show_icon: (obj: any) => void;
settings?: JSTreeStaticDefaults | undefined;
get_undetermined: (full: boolean) => any[];
_undetermined: () => void;
show_checkboxes: () => void;
hide_checkboxes: () => void;
toggle_checkboxes: () => void;
is_undetermined: (obj: any) => boolean;
disable_checkbox: (obj: any) => boolean;
enable_checkbox: (obj: any) => boolean;
_cascade_new_checked_state: (id: string, checkedState: boolean) => string[];
get_checked_descendants: (id: string) => string[];
check_node: (obj: any, e: any) => any;
uncheck_node: (obj: any, e: any) => any;
check_all: () => any;
uncheck_all: () => any;
is_checked: (obj: any) => boolean;
get_checked: (full: any) => any[];
get_top_checked: (full: any) => any[];
get_bottom_checked: (full: any) => any[];
show_contextmenu: (obj: any, x?: number, y?: number, e?: any) => void;
_show_contextmenu: (obj: any, x: number, y: number, i: number) => void;
search: (str: string, skip_async?: boolean, show_only_matches?: boolean, inside?: any,
append?: boolean, show_only_matches_children?: boolean) => void;
clear_search: () => void;
_search_open: (d: string[]) => void;
sort: (obj: any, deep?: boolean) => void;
save_state: () => void;
restore_state: () => void;
clear_state: () => void;
get_rules: (obj: any) => any;
get_type: (obj: any, rules?: any) => any;
set_type: (obj: any, type: string) => any;
conditionalselect: (options: any, parent: any) => any;
conditionalclose: (options: any, parent: any) => any;
}
interface JSTreeGetJsonOptions {
no_state?: boolean | undefined;
no_id?: boolean | undefined;
no_children?: boolean | undefined;
no_data?: boolean | undefined;
no_li_attr?: boolean | undefined;
no_a_attr?: boolean | undefined;
flat?: boolean | undefined;
}
interface JsTreeBuffer {
mode: string;
node: any[];
inst: any;
}

8
ui-ngx/tsconfig.json

@ -18,13 +18,7 @@
"jsx": "react",
"typeRoots": [
"node_modules/@types",
"src/typings/rawloader.typings.d.ts",
"src/typings/jquery.typings.d.ts",
"src/typings/jquery.flot.typings.d.ts",
"src/typings/jquery.jstree.typings.d.ts",
"src/typings/split.js.typings.d.ts",
"src/typings/leaflet-geoman-extend.d.ts",
"src/typings/leaflet-extend-tb.d.ts"
"src/typings"
],
"paths": {
"@app/*": [

656
ui-ngx/yarn.lock

@ -31,7 +31,15 @@
"@angular-devkit/core" "15.2.1"
rxjs "6.6.7"
"@angular-devkit/build-angular@^15.0.0", "@angular-devkit/build-angular@^15.2.0":
"@angular-devkit/architect@0.1502.4":
version "0.1502.4"
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1502.4.tgz#a2ceef215f75b03ec275f5ff68e87fc43e44b113"
integrity sha512-bDBcaRMBfXFfK9MpvfNO926F1rL0PEw+mveXxq3/SSql+1XP/hrc5TVGwnoim4g6DqsGmu9upS5DyJ6PnL/hHA==
dependencies:
"@angular-devkit/core" "15.2.4"
rxjs "6.6.7"
"@angular-devkit/build-angular@^15.0.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-15.2.1.tgz#11dacec9f651e4491bb5d914e8b84dc2e245c955"
integrity sha512-EgJqlrig9iuMvqySb4h8h2AAd8OwhBqY2JMMvs+hIASZvJi3n+Qd12BdlqLuAzykRyIAdy3OeLSAaXZrmeSk0A==
@ -99,6 +107,74 @@
optionalDependencies:
esbuild "0.17.8"
"@angular-devkit/build-angular@^15.2.4":
version "15.2.4"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-15.2.4.tgz#ac9230fef0d57d1402796ec0e9be3b3afd90ee51"
integrity sha512-wt0S4oz0vxuW0/Ak5X0vQ7s7TSPynmktVNJblu9SFRgwCD3kplV2B693F+M6t8eLzSy0UCSbZp9h3Ae8gLEiEw==
dependencies:
"@ampproject/remapping" "2.2.0"
"@angular-devkit/architect" "0.1502.4"
"@angular-devkit/build-webpack" "0.1502.4"
"@angular-devkit/core" "15.2.4"
"@babel/core" "7.20.12"
"@babel/generator" "7.20.14"
"@babel/helper-annotate-as-pure" "7.18.6"
"@babel/helper-split-export-declaration" "7.18.6"
"@babel/plugin-proposal-async-generator-functions" "7.20.7"
"@babel/plugin-transform-async-to-generator" "7.20.7"
"@babel/plugin-transform-runtime" "7.19.6"
"@babel/preset-env" "7.20.2"
"@babel/runtime" "7.20.13"
"@babel/template" "7.20.7"
"@discoveryjs/json-ext" "0.5.7"
"@ngtools/webpack" "15.2.4"
ansi-colors "4.1.3"
autoprefixer "10.4.13"
babel-loader "9.1.2"
babel-plugin-istanbul "6.1.1"
browserslist "4.21.5"
cacache "17.0.4"
chokidar "3.5.3"
copy-webpack-plugin "11.0.0"
critters "0.0.16"
css-loader "6.7.3"
esbuild-wasm "0.17.8"
glob "8.1.0"
https-proxy-agent "5.0.1"
inquirer "8.2.4"
jsonc-parser "3.2.0"
karma-source-map-support "1.4.0"
less "4.1.3"
less-loader "11.1.0"
license-webpack-plugin "4.0.2"
loader-utils "3.2.1"
magic-string "0.29.0"
mini-css-extract-plugin "2.7.2"
open "8.4.1"
ora "5.4.1"
parse5-html-rewriting-stream "7.0.0"
piscina "3.2.0"
postcss "8.4.21"
postcss-loader "7.0.2"
resolve-url-loader "5.0.0"
rxjs "6.6.7"
sass "1.58.1"
sass-loader "13.2.0"
semver "7.3.8"
source-map-loader "4.0.1"
source-map-support "0.5.21"
terser "5.16.3"
text-table "0.2.0"
tree-kill "1.2.2"
tslib "2.5.0"
webpack "5.76.1"
webpack-dev-middleware "6.0.1"
webpack-dev-server "4.11.1"
webpack-merge "5.8.0"
webpack-subresource-integrity "5.1.0"
optionalDependencies:
esbuild "0.17.8"
"@angular-devkit/build-webpack@0.1502.1":
version "0.1502.1"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1502.1.tgz#d0332b620a24a37ce132ef3fb771af3f991c6413"
@ -107,6 +183,14 @@
"@angular-devkit/architect" "0.1502.1"
rxjs "6.6.7"
"@angular-devkit/build-webpack@0.1502.4":
version "0.1502.4"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1502.4.tgz#0fa89e684692d2444d4e7996f1d2c974be2b6e2b"
integrity sha512-Bs/pxcY3517QAVyAalDxJgjc93KWQos+dFdgEQrKxj/VTs1BTYnLbb2M8Y7MoxVnfH4S+qqxGe5B57T+TlB3Eg==
dependencies:
"@angular-devkit/architect" "0.1502.4"
rxjs "6.6.7"
"@angular-devkit/core@15.2.1", "@angular-devkit/core@^15.0.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-15.2.1.tgz#78b03a134d354df6646a29c718bef6721ac9365e"
@ -118,12 +202,23 @@
rxjs "6.6.7"
source-map "0.7.4"
"@angular-devkit/schematics@15.2.1":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-15.2.1.tgz#de3e98fd4755878299d7e3a6961aa68854d2bad5"
integrity sha512-XfUAGUC/tjQDzXaEJxkTvC7glJa4vmqGJ8RSCrRQYQOUZUASZtjEr6dIZEkWDN7zllcLuK/ULE4ITdcQ438LMQ==
"@angular-devkit/core@15.2.4":
version "15.2.4"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-15.2.4.tgz#f7696f09c66d01568a07f0e71672e887fdf57280"
integrity sha512-yl+0j1bMwJLKShsyCXw77tbJG8Sd21+itisPLL2MgEpLNAO252kr9zG4TLlFRJyKVftm2l1h78KjqvM5nbOXNg==
dependencies:
"@angular-devkit/core" "15.2.1"
ajv "8.12.0"
ajv-formats "2.1.1"
jsonc-parser "3.2.0"
rxjs "6.6.7"
source-map "0.7.4"
"@angular-devkit/schematics@15.2.4":
version "15.2.4"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-15.2.4.tgz#85129ebabcdb362f4b65a6e290bb2ae846f3d64c"
integrity sha512-/W7/vvn59PAVLzhcvD4/N/E8RDhub8ny1A7I96LTRjC5o+yvVV16YJ4YJzolrRrIEN01KmLVQJ9A58VCaweMgw==
dependencies:
"@angular-devkit/core" "15.2.4"
jsonc-parser "3.2.0"
magic-string "0.29.0"
ora "5.4.1"
@ -186,31 +281,31 @@
"@angular-eslint/bundled-angular-compiler" "15.2.1"
"@typescript-eslint/utils" "5.48.2"
"@angular/animations@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-15.2.1.tgz#7ee4e058bf1234d1780498af0407851bb591c04f"
integrity sha512-oQjTdXLVHzSnsHHPF3CM4bnT4uVQQW1vP4weFTXWhvybQ6vICcq57N9Ri8ZsvB0Sisqug6CPwZoUATCb5a2HXw==
"@angular/animations@^15.2.5":
version "15.2.5"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-15.2.5.tgz#d42e5f185ed3d8044d38b1c938c14044aa0345a8"
integrity sha512-areHbwTrJNEYWe+1xND7SEI8fY7Pi2wckuLtbGonjtoaAc8MPs0ov56TnpXHS7bIXKVszqUlBTBAElQSdtjCyw==
dependencies:
tslib "^2.3.0"
"@angular/cdk@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-15.2.1.tgz#29d3c1d7d85debdfc8de4a802d420f3190c835ea"
integrity sha512-kRwG0ujimOPhqDwQYcQ+pk7hpTr0yvaPkxAx5r1ytA6aQIvHJ1XaPzhtthsr5/PhpwzlH8k723xpejrLKcuR+w==
"@angular/cdk@^15.2.5":
version "15.2.5"
resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-15.2.5.tgz#53208ea5e654b678af011b3ef6034e1382339630"
integrity sha512-6/vA4xATcx//AULwXnRRH0Of0aaNlfrFxdlhvRVfnfmxi/hZAkjjEtil/wv8uBw+dAwXBx5yZMsAol9nUFS/BQ==
dependencies:
tslib "^2.3.0"
optionalDependencies:
parse5 "^7.1.2"
"@angular/cli@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-15.2.1.tgz#727ab23741a86ea381eb09c1973b0957714dc253"
integrity sha512-mLMn+65oojWvu7Ew4na8QgCQb382kZ55xCGjyUDeNNdH/WbqeYC783Vcs7JpMMP1JnndIaeIzvpfjLja1/2UCw==
"@angular/cli@^15.2.4":
version "15.2.4"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-15.2.4.tgz#5163efb3f3e5b2a8ab0598d46d4dc200a51e7197"
integrity sha512-nQFnrt9aWwqkyhJMaZvjHXXuxeR1pZNcpZEzTA5nXtpVeNs8U7EBFCyJ+cYecFX0LHe36SoxXcbfnNEKHt3NVQ==
dependencies:
"@angular-devkit/architect" "0.1502.1"
"@angular-devkit/core" "15.2.1"
"@angular-devkit/schematics" "15.2.1"
"@schematics/angular" "15.2.1"
"@angular-devkit/architect" "0.1502.4"
"@angular-devkit/core" "15.2.4"
"@angular-devkit/schematics" "15.2.4"
"@schematics/angular" "15.2.4"
"@yarnpkg/lockfile" "1.1.0"
ansi-colors "4.1.3"
ini "3.0.1"
@ -226,17 +321,17 @@
symbol-observable "4.0.0"
yargs "17.6.2"
"@angular/common@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/common/-/common-15.2.1.tgz#2ffa669b4e54e05c5fb97181497446e093c466b0"
integrity sha512-nGFrZXrxLMkFyK4VYHHH6+/Hq92BjVls6UWuG1UblkYzGqPpr7dRQZGOXzJc48sEG2WOeVhlhyK+0tHFFaG08g==
"@angular/common@^15.2.5":
version "15.2.5"
resolved "https://registry.yarnpkg.com/@angular/common/-/common-15.2.5.tgz#7ae960f17c260a92e939ad82fb115d908094aeda"
integrity sha512-6Wl1ak96NZvnL8p0eVsxHhaddv0/qYq2pQniKaKBfL9MVOQtAe07sPUDEZ6w0ApkmV63Giu4zFakaQMsmvxU0Q==
dependencies:
tslib "^2.3.0"
"@angular/compiler-cli@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-15.2.1.tgz#fc17dd30d851b5d891a49104d7801537b7c5db44"
integrity sha512-IoS3IovbnY4hLWzeym5Kul/dJuncggMHuPnyrMbAnR6+eCZCLyRqQ5MykXDCsgfnRuVm8+PEZc0voA/5st2Vhw==
"@angular/compiler-cli@^15.2.5":
version "15.2.5"
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-15.2.5.tgz#3c1882cc837a71fd0f93b880bfec6e851ec213e8"
integrity sha512-NNrnvL0JMg6dZMuTgBTb/IVIFJwy2my5XTNLngTNBfDCVu0SL1TKrYnpqp2qRi2ip9XdNqCtNF7JFDEklVbK2A==
dependencies:
"@babel/core" "7.19.3"
"@jridgewell/sourcemap-codec" "^1.4.14"
@ -249,17 +344,17 @@
tslib "^2.3.0"
yargs "^17.2.1"
"@angular/compiler@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-15.2.1.tgz#3724e472cfcded0b95dbb59e7dd8bc23ab3dfe54"
integrity sha512-eOA09iQWPEOJXig6W+out8MyZL2wdVu62Z79PWGQG8RWDSPV8XUFMAPfTOC8B43PT4hn/Avy/aauXl6O9l3vXw==
"@angular/compiler@^15.2.5":
version "15.2.5"
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-15.2.5.tgz#4902db0699fd9ff8af15a3cd0d8be9aacef09b7f"
integrity sha512-0mAFF6Ud4ojsb8qGLQCWYh/LgKKrnn1Fz788LzfYcLYhi7UZPiCFrQJrEcCXBKtQsk8sG335CA9Qk0I/qc35zg==
dependencies:
tslib "^2.3.0"
"@angular/core@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/core/-/core-15.2.1.tgz#c2ec98fac41058d901e5b48ed7ee207d6a10bb95"
integrity sha512-CtN/cYDTGlELRcKBHxqnxmUhI9Euz2R+25dvjNtxB2tLzCehI6Fbmsb3NsC++jcAjL3QAmodpzzgULPtNJqs6A==
"@angular/core@^15.2.5":
version "15.2.5"
resolved "https://registry.yarnpkg.com/@angular/core/-/core-15.2.5.tgz#761d339551a3daf97d9b9786882a5dd42ee19445"
integrity sha512-GtmjJmwtzCuF4FDXIY+9UFMAcfPlJzJDBDF7mgEmD2YKf/HV5PSyU91lfv9yDPnAkdzlDAL9u4YxnGgoURY8/g==
dependencies:
tslib "^2.3.0"
@ -270,22 +365,22 @@
dependencies:
tslib "^2.3.0"
"@angular/forms@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-15.2.1.tgz#77b070cb8397183eb0bf115cc40179b2e52f57e1"
integrity sha512-C3gWbjwcult/RPFJ4RtzwOoIrO2ZYPXF86gQuA1MFpASRsroWl/9HTdvkmLa+UeW8ZoSZt4QNl00dymO+ROUGg==
"@angular/forms@^15.2.5":
version "15.2.5"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-15.2.5.tgz#e40cf0bc27eae6e7ae1409ff618a3fe7cea19cfc"
integrity sha512-F4YLh0S1VyWp4IyfORTpzkZJcg7nB4q/wIirVZocR9sK3FJMEKYcAGwLbkWDYGZ66UCDw7EEqCTXkjr99/6sPg==
dependencies:
tslib "^2.3.0"
"@angular/language-service@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-15.2.1.tgz#ad4de7adfbf42faff1a51f4deaddc5b6d1cfe893"
integrity sha512-G86FZ0kZTXpHoyR1XJVB17whJ+4pvdJwCVnuZGzJ18lRwBJQcNA0r9neXqH+6StjStgLz0Wre+mI+sQMklFOmw==
"@angular/language-service@^15.2.5":
version "15.2.5"
resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-15.2.5.tgz#fff42544e1dad609293a33ae93371a11e2df0c44"
integrity sha512-knXe4D0gc2KiU3Y3LZCRvcIkddxUMHI6/44/QAGZD3UDx6ZnpizS0eb8q8AO1pWiBTUY5ETUAlihaGWqDgQ+Wg==
"@angular/material@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/material/-/material-15.2.1.tgz#e73abd5c9413a4ae6a97b3a1a0f4fa6479bad55d"
integrity sha512-2CdOltJz35yZbcSw9xy5HCQXo2/3hoQDG7YQm0zbd6gq6NnVZdhrb1RT+KonIOO1a3cpH3zKilAsZLh7gfvDUg==
"@angular/material@^15.2.5":
version "15.2.5"
resolved "https://registry.yarnpkg.com/@angular/material/-/material-15.2.5.tgz#a0531b629334dfd2997dd01e549bd0c40680f4e9"
integrity sha512-Lk6l1VsBA2ActVN9YsX2W5lVFYHJEVwswbbUdBPjWaXJzp7TdgghTFczac7vsNA6y3DM3Rd+CvRsJPD2kK4g4A==
dependencies:
"@material/animation" "15.0.0-canary.684e33d25.0"
"@material/auto-init" "15.0.0-canary.684e33d25.0"
@ -336,24 +431,24 @@
"@material/typography" "15.0.0-canary.684e33d25.0"
tslib "^2.3.0"
"@angular/platform-browser-dynamic@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-15.2.1.tgz#92969fff1a4b1881f3eb67fc8fa34c1fd6e4eb6c"
integrity sha512-KeDq7yYmVlYVS3DiNUgI1318eYXxIiZ281Rg9TrLCHSNmzFag821XiDmXs/eug3RsgoYCKZ/j9w+ey8Qqfw6/Q==
"@angular/platform-browser-dynamic@^15.2.5":
version "15.2.5"
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-15.2.5.tgz#f715dafd227606142295fd717a7c3fb0b67ae79f"
integrity sha512-NoGVeaR7K+RMcLpS2gI4hyMVeUqP057rw8Yfk15dHy3cM8icj5zVfyez3AADcO1XthNhE1sI1d+2LD4/GxwIKQ==
dependencies:
tslib "^2.3.0"
"@angular/platform-browser@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-15.2.1.tgz#cef395ca6de1b1ea4e88ce5547aaac7547d1c6ac"
integrity sha512-aUV4XfdeqHTXwMRAjgrmx43F22YwgaCZnU08Ufg4Dx3nfqL9ak3e5NfgUpMlKbj7qX2rGGt6GKhQwoDFR4Dg1g==
"@angular/platform-browser@^15.2.5":
version "15.2.5"
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-15.2.5.tgz#b5e1a4da920577d147f162463b2448588543a172"
integrity sha512-D19HL3UsvPX4s8yC4C4gphTNyQU75VuzcyGZ+6y1o1SQbVjF6UwSrztmb//4MCkpclb+avv669z5AEiTuBTnVQ==
dependencies:
tslib "^2.3.0"
"@angular/router@^15.2.0":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@angular/router/-/router-15.2.1.tgz#313d5f2710f7722367f0aea83438d4cf1a6d0eb1"
integrity sha512-KyCBpj7HXnGSLz1XRP/b3K2WKVgfdY84uJ1CSVsTjADiduIfvAd3LGDVERzkTo+JxAsL1CD/yg1nTiIbFbcuCA==
"@angular/router@^15.2.5":
version "15.2.5"
resolved "https://registry.yarnpkg.com/@angular/router/-/router-15.2.5.tgz#879839960d9a3d52a20cdffc51062e5d288c3440"
integrity sha512-NSEfx2wgPCS8EdXLbKUjNCXC29cMqEGDUrSv8+P/O+tY21Szkqp6uzGTOZJuSTOoKQmDEw9CRmuwxMKm2l6y/Q==
dependencies:
tslib "^2.3.0"
@ -1497,14 +1592,26 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.8.tgz#200a0965cf654ac28b971358ecdca9cc5b44c335"
integrity sha512-1iuezdyDNngPnz8rLRDO2C/ZZ/emJLb72OsZeqQ6gL6Avko/XCXZw+NuxBSNhBAP13Hie418V7VMt9et1FMvpg==
"@eslint/eslintrc@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.0.tgz#943309d8697c52fc82c076e90c1c74fbbe69dbff"
integrity sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==
"@eslint-community/eslint-utils@^4.2.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
dependencies:
eslint-visitor-keys "^3.3.0"
"@eslint-community/regexpp@^4.4.0":
version "4.5.0"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.0.tgz#f6f729b02feee2c749f57e334b7a1b5f40a81724"
integrity sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==
"@eslint/eslintrc@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02"
integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^9.4.0"
espree "^9.5.1"
globals "^13.19.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
@ -1512,10 +1619,10 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@8.35.0":
version "8.35.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.35.0.tgz#b7569632b0b788a0ca0e438235154e45d42813a7"
integrity sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==
"@eslint/js@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.37.0.tgz#cf1b5fa24217fe007f6487a26d765274925efa7d"
integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==
"@flowjs/flow.js@^2.14.1":
version "2.14.1"
@ -1642,7 +1749,7 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
"@juggle/resize-observer@^3.3.1":
"@juggle/resize-observer@^3.4.0":
version "3.4.0"
resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60"
integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==
@ -2443,15 +2550,15 @@
"@material/theme" "15.0.0-canary.684e33d25.0"
tslib "^2.1.0"
"@mdi/svg@^7.1.96":
version "7.1.96"
resolved "https://registry.yarnpkg.com/@mdi/svg/-/svg-7.1.96.tgz#4911ee6d2f3457f016be8b7a454cf1a8e5c361f0"
integrity sha512-QO+CyF7eZsYBJpyb9Q77r1O6PFdp/Ircx8FMV7+cFS7g0p5rF55PA9zrmzuZqi1LyPKANDpr0oULNLHgeQuXZQ==
"@mdi/svg@^7.2.96":
version "7.2.96"
resolved "https://registry.yarnpkg.com/@mdi/svg/-/svg-7.2.96.tgz#6c182628ed722a85055c3795e7d4ca6d4f2f139c"
integrity sha512-rxzuSL2RSt/pWWnFnUFQi5GJArm2tHMhx20Gee3Ydn+xT2bqbR4syfgdPrq2b+j+n5LjC7C8Fb1QDM6LKeF0cA==
"@messageformat/core@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@messageformat/core/-/core-3.0.1.tgz#191e12cf9643704d1fd32e592a3fbdc194dd588e"
integrity sha512-yxj2+0e46hcZqJfNf0ZYbC2q6WlcGoh4g11mCyRtTueR0AD8F9z4JMYAS1aOiFG8Vl1LZg/h5hZHKmWTAyZq8g==
"@messageformat/core@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@messageformat/core/-/core-3.1.0.tgz#d4d2f5c3555228a6b5980b122a02b53dfc6458bd"
integrity sha512-UxAnjecnRG4u2iaggwIyylYPHmk5BTErJcKmWyAKTXqYgSW1bFLp4D7fIzuh6bk17Qfcmf3qtufdrstCB23nBA==
dependencies:
"@messageformat/date-skeleton" "^1.0.0"
"@messageformat/number-skeleton" "^1.0.0"
@ -2484,24 +2591,24 @@
dependencies:
make-plural "^7.0.0"
"@ngrx/effects@^15.3.0":
version "15.3.0"
resolved "https://registry.yarnpkg.com/@ngrx/effects/-/effects-15.3.0.tgz#96af2d8121384d782e278a08b8b3643c1d08ae82"
integrity sha512-L+Ie4XFrzYBJOV7hNQvR5hUvG1PSCDd6niwOOJg5nm9zEjSnAxveJ/a3B52pRwge6EYOnrQne97jyArxOzPCJA==
"@ngrx/effects@^15.4.0":
version "15.4.0"
resolved "https://registry.yarnpkg.com/@ngrx/effects/-/effects-15.4.0.tgz#31e80aa2ef13953772607b9c6dcb12efce2e1da5"
integrity sha512-/8gHhOM9aeGaw8OG2LLwi4I4p84xzG0EU9TqWrvQcW74wn8sFZONjLvUte5YOzJ5502PPFFrfXSOc+lHnVAJUA==
dependencies:
tslib "^2.0.0"
"@ngrx/store-devtools@^15.3.0":
version "15.3.0"
resolved "https://registry.yarnpkg.com/@ngrx/store-devtools/-/store-devtools-15.3.0.tgz#e694a7d5cb3edb974f6a3b385e78581549d0de45"
integrity sha512-o39T11T994j5XzwiNquhRHVQiFYlOg1PJWmV++LdIaXD0zYovc9lzNDnZJy04mt9XPTDPu8QmcmzV44zUtY4vg==
"@ngrx/store-devtools@^15.4.0":
version "15.4.0"
resolved "https://registry.yarnpkg.com/@ngrx/store-devtools/-/store-devtools-15.4.0.tgz#39c1a31f813dc4328ed0fb826843845c83422c2d"
integrity sha512-AqWRprSyS3u/vZlpsBOPMW1/0TZS2LE13/KpZedEY+RMCW+mi53esqgrdNGOB3Wr4vrI82Ar9fgaIm4TQO6LPg==
dependencies:
tslib "^2.0.0"
"@ngrx/store@^15.3.0":
version "15.3.0"
resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-15.3.0.tgz#7feb22134dcea6cf779149850078c98be1d70125"
integrity sha512-8cd0zWkOZ3TedDQHyOzUxZD1HHa0fU8fgzVX/2eIq6wmnleUxHVOKSJvA+DdE4GRoryFqVhAp17L1r5eC2QYHA==
"@ngrx/store@^15.4.0":
version "15.4.0"
resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-15.4.0.tgz#185ee18e5c4147c7db60fd3b223cc429f826d514"
integrity sha512-OvCuNBHL8mAUnRTS6QSFm+IunspsYNu2cCwDovBNn7EGhxRuGihBeNoX47jCqWPHBFtokj4BlatDfpJ/yCh4xQ==
dependencies:
tslib "^2.0.0"
@ -2510,6 +2617,11 @@
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-15.2.1.tgz#439ac075b2dcb9f304f0b7009182cc5049c7988a"
integrity sha512-YtA8rWAglPuf4CSStrFAxaprTSYE+DREGrJFc3WvZLcF5XrwVK+H4CC4Pmz07iYsG1TXShR4bWp1fbGw1cmBKw==
"@ngtools/webpack@15.2.4":
version "15.2.4"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-15.2.4.tgz#bb8afe2800e79120985657180d2b33f64ecd763d"
integrity sha512-cQ7MsRoGJgPOVnpvFgWhygeSe6zJ0ITiUhjmmuOgpNDfYkrgYxN3Ot/qvQefFei+oGZ1JJ9bRb8lcPKL/apoBQ==
"@ngx-translate/core@^14.0.0":
version "14.0.0"
resolved "https://registry.yarnpkg.com/@ngx-translate/core/-/core-14.0.0.tgz#af421d0e1a28376843f0fed375cd2fae7630a5ff"
@ -2614,13 +2726,13 @@
read-package-json-fast "^3.0.0"
which "^3.0.0"
"@schematics/angular@15.2.1":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-15.2.1.tgz#c5e90ff4f3a0aa9470a6f20f71e55208e6ca2a19"
integrity sha512-D0LtMGXxhMwGoK89TG606kY6CfQWrdOQ/bjOg/33xXJQWvGqUK8ZQtD7/lhcu5RWi3jduPh7V8jYWuAoA69Eig==
"@schematics/angular@15.2.4":
version "15.2.4"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-15.2.4.tgz#b61b9cb3becf601e904f60490846c1d95bd63700"
integrity sha512-P9axwKDXEDrGXYcoCuhwv4guhvtIjDCvyZMsQw8IMpfAacTYajka+T3mZrzyTe96Uhu0JwcAW5iUlOZAJhPEdQ==
dependencies:
"@angular-devkit/core" "15.2.1"
"@angular-devkit/schematics" "15.2.1"
"@angular-devkit/core" "15.2.4"
"@angular-devkit/schematics" "15.2.4"
jsonc-parser "3.2.0"
"@socket.io/component-emitter@~3.1.0":
@ -2966,7 +3078,7 @@
dependencies:
"@types/jasmine" "*"
"@types/jquery@*", "@types/jquery@^3.5.14", "@types/jquery@^3.5.9":
"@types/jquery@*", "@types/jquery@^3.5.14", "@types/jquery@^3.5.16":
version "3.5.16"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.16.tgz#632131baf30951915b0317d48c98e9890bdf051d"
integrity sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==
@ -2988,13 +3100,6 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/jstree@^3.3.41":
version "3.3.41"
resolved "https://registry.yarnpkg.com/@types/jstree/-/jstree-3.3.41.tgz#820ce1f82bbb2441eaf9fb76451750e2810f5856"
integrity sha512-M4ia6tSKOAU/8v2Ir1Kyo3/XE4EWxw5ulnzbE/nmvw7YugxWhRfTIc6i2ubvJ3GQpdXzJ11bH+GT3VhJNcJadQ==
dependencies:
"@types/jquery" "*"
"@types/leaflet-polylinedecorator@^1.6.1":
version "1.6.1"
resolved "https://registry.yarnpkg.com/@types/leaflet-polylinedecorator/-/leaflet-polylinedecorator-1.6.1.tgz#b6522f9dae52146bf73da249e4bedfbab200c6e4"
@ -3023,19 +3128,26 @@
dependencies:
"@types/leaflet" "*"
"@types/leaflet@*", "@types/leaflet@^1.7.11":
"@types/leaflet@*":
version "1.9.1"
resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.9.1.tgz#a220706b02c6024951d7fc2959d530c6941c4224"
integrity sha512-lYawM3I3lLO6rmBASaqdGgY6zUL4YHr3H79/axx7FNYyPXuj0P1DZHbkNo8Itbv0i7Y9EryLWtDXXROMygXhRA==
dependencies:
"@types/geojson" "*"
"@types/lodash@^4.14.177":
version "4.14.191"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa"
integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==
"@types/leaflet@^1.8.0":
version "1.9.3"
resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.9.3.tgz#7aac302189eb3aa283f444316167995df42a5467"
integrity sha512-Caa1lYOgKVqDkDZVWkto2Z5JtVo09spEaUt2S69LiugbBpoqQu92HYFMGUbYezZbnBkyOxMNPXHSgRrRY5UyIA==
dependencies:
"@types/geojson" "*"
"@types/lodash@^4.14.192":
version "4.14.192"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.192.tgz#5790406361a2852d332d41635d927f1600811285"
integrity sha512-km+Vyn3BYm5ytMO13k9KTp27O75rbQ0NFw+U//g+PX7VZyjCioXaRFisqSIJRECljcTv73G3i6BpglNGHgUQ5A==
"@types/marked@^4.0.3":
"@types/marked@^4.0.8":
version "4.0.8"
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.0.8.tgz#b316887ab3499d0a8f4c70b7bd8508f92d477955"
integrity sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==
@ -3045,7 +3157,7 @@
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==
"@types/mousetrap@^1.6.0", "@types/mousetrap@^1.6.9":
"@types/mousetrap@^1.6.9":
version "1.6.11"
resolved "https://registry.yarnpkg.com/@types/mousetrap/-/mousetrap-1.6.11.tgz#ef9620160fdcefcb85bccda8aaa3e84d7429376d"
integrity sha512-F0oAily9Q9QQpv9JKxKn0zMKfOo36KHCW7myYsmUyf2t0g+sBTbG3UleTPoguHdE1z3GLFr3p7/wiOio52QFjQ==
@ -3055,10 +3167,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.5.tgz#4a13a6445862159303fc38586598a9396fc408b3"
integrity sha512-CRT4tMK/DHYhw1fcCEBwME9CSaZNclxfzVMe7GsO6ULSwsttbj70wSiX6rZdIjGblu93sTJxLdhNIT85KKI7Qw==
"@types/node@~15.14.9":
version "15.14.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa"
integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==
"@types/node@~18.15.11":
version "18.15.11"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f"
integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==
"@types/parse-json@^4.0.0":
version "4.0.0"
@ -3191,39 +3303,32 @@
dependencies:
"@types/node" "*"
"@typescript-eslint/eslint-plugin@5.43.0":
version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.43.0.tgz#4a5248eb31b454715ddfbf8cfbf497529a0a78bc"
integrity sha512-wNPzG+eDR6+hhW4yobEmpR36jrqqQv1vxBq5LJO3fBAktjkvekfr4BRl+3Fn1CM/A+s8/EiGUbOMDoYqWdbtXA==
"@typescript-eslint/eslint-plugin@5.57.0":
version "5.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.57.0.tgz#52c8a7a4512f10e7249ca1e2e61f81c62c34365c"
integrity sha512-itag0qpN6q2UMM6Xgk6xoHa0D0/P+M17THnr4SVgqn9Rgam5k/He33MA7/D7QoJcdMxHFyX7U9imaBonAX/6qA==
dependencies:
"@typescript-eslint/scope-manager" "5.43.0"
"@typescript-eslint/type-utils" "5.43.0"
"@typescript-eslint/utils" "5.43.0"
"@eslint-community/regexpp" "^4.4.0"
"@typescript-eslint/scope-manager" "5.57.0"
"@typescript-eslint/type-utils" "5.57.0"
"@typescript-eslint/utils" "5.57.0"
debug "^4.3.4"
grapheme-splitter "^1.0.4"
ignore "^5.2.0"
natural-compare-lite "^1.4.0"
regexpp "^3.2.0"
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/parser@5.43.0":
version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.43.0.tgz#9c86581234b88f2ba406f0b99a274a91c11630fd"
integrity sha512-2iHUK2Lh7PwNUlhFxxLI2haSDNyXvebBO9izhjhMoDC+S3XI9qt2DGFUsiJ89m2k7gGYch2aEpYqV5F/+nwZug==
"@typescript-eslint/parser@5.57.0":
version "5.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.57.0.tgz#f675bf2cd1a838949fd0de5683834417b757e4fa"
integrity sha512-orrduvpWYkgLCyAdNtR1QIWovcNZlEm6yL8nwH/eTxWLd8gsP+25pdLHYzL2QdkqrieaDwLpytHqycncv0woUQ==
dependencies:
"@typescript-eslint/scope-manager" "5.43.0"
"@typescript-eslint/types" "5.43.0"
"@typescript-eslint/typescript-estree" "5.43.0"
"@typescript-eslint/scope-manager" "5.57.0"
"@typescript-eslint/types" "5.57.0"
"@typescript-eslint/typescript-estree" "5.57.0"
debug "^4.3.4"
"@typescript-eslint/scope-manager@5.43.0":
version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.43.0.tgz#566e46303392014d5d163704724872e1f2dd3c15"
integrity sha512-XNWnGaqAtTJsUiZaoiGIrdJYHsUOd3BZ3Qj5zKp9w6km6HsrjPk/TGZv0qMTWyWj0+1QOqpHQ2gZOLXaGA9Ekw==
dependencies:
"@typescript-eslint/types" "5.43.0"
"@typescript-eslint/visitor-keys" "5.43.0"
"@typescript-eslint/scope-manager@5.48.2":
version "5.48.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz#bb7676cb78f1e94921eaab637a4b5d596f838abc"
@ -3232,15 +3337,13 @@
"@typescript-eslint/types" "5.48.2"
"@typescript-eslint/visitor-keys" "5.48.2"
"@typescript-eslint/type-utils@5.43.0":
version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.43.0.tgz#91110fb827df5161209ecca06f70d19a96030be6"
integrity sha512-K21f+KY2/VvYggLf5Pk4tgBOPs2otTaIHy2zjclo7UZGLyFH86VfUOm5iq+OtDtxq/Zwu2I3ujDBykVW4Xtmtg==
"@typescript-eslint/scope-manager@5.57.0":
version "5.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.57.0.tgz#79ccd3fa7bde0758059172d44239e871e087ea36"
integrity sha512-NANBNOQvllPlizl9LatX8+MHi7bx7WGIWYjPHDmQe5Si/0YEYfxSljJpoTyTWFTgRy3X8gLYSE4xQ2U+aCozSw==
dependencies:
"@typescript-eslint/typescript-estree" "5.43.0"
"@typescript-eslint/utils" "5.43.0"
debug "^4.3.4"
tsutils "^3.21.0"
"@typescript-eslint/types" "5.57.0"
"@typescript-eslint/visitor-keys" "5.57.0"
"@typescript-eslint/type-utils@5.48.2":
version "5.48.2"
@ -3252,28 +3355,25 @@
debug "^4.3.4"
tsutils "^3.21.0"
"@typescript-eslint/types@5.43.0":
version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.43.0.tgz#e4ddd7846fcbc074325293515fa98e844d8d2578"
integrity sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg==
"@typescript-eslint/type-utils@5.57.0":
version "5.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.57.0.tgz#98e7531c4e927855d45bd362de922a619b4319f2"
integrity sha512-kxXoq9zOTbvqzLbdNKy1yFrxLC6GDJFE2Yuo3KqSwTmDOFjUGeWSakgoXT864WcK5/NAJkkONCiKb1ddsqhLXQ==
dependencies:
"@typescript-eslint/typescript-estree" "5.57.0"
"@typescript-eslint/utils" "5.57.0"
debug "^4.3.4"
tsutils "^3.21.0"
"@typescript-eslint/types@5.48.2":
version "5.48.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.48.2.tgz#635706abb1ec164137f92148f06f794438c97b8e"
integrity sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==
"@typescript-eslint/typescript-estree@5.43.0":
version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.43.0.tgz#b6883e58ba236a602c334be116bfc00b58b3b9f2"
integrity sha512-BZ1WVe+QQ+igWal2tDbNg1j2HWUkAa+CVqdU79L4HP9izQY6CNhXfkNwd1SS4+sSZAP/EthI1uiCSY/+H0pROg==
dependencies:
"@typescript-eslint/types" "5.43.0"
"@typescript-eslint/visitor-keys" "5.43.0"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/types@5.57.0":
version "5.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.57.0.tgz#727bfa2b64c73a4376264379cf1f447998eaa132"
integrity sha512-mxsod+aZRSyLT+jiqHw1KK6xrANm19/+VFALVFP5qa/aiJnlP38qpyaTd0fEKhWvQk6YeNZ5LGwI1pDpBRBhtQ==
"@typescript-eslint/typescript-estree@5.48.2":
version "5.48.2"
@ -3288,19 +3388,18 @@
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.43.0":
version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.43.0.tgz#00fdeea07811dbdf68774a6f6eacfee17fcc669f"
integrity sha512-8nVpA6yX0sCjf7v/NDfeaOlyaIIqL7OaIGOWSPFqUKK59Gnumd3Wa+2l8oAaYO2lk0sO+SbWFWRSvhu8gLGv4A==
"@typescript-eslint/typescript-estree@5.57.0":
version "5.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.57.0.tgz#ebcd0ee3e1d6230e888d88cddf654252d41e2e40"
integrity sha512-LTzQ23TV82KpO8HPnWuxM2V7ieXW8O142I7hQTxWIHDcCEIjtkat6H96PFkYBQqGFLW/G/eVVOB9Z8rcvdY/Vw==
dependencies:
"@types/json-schema" "^7.0.9"
"@types/semver" "^7.3.12"
"@typescript-eslint/scope-manager" "5.43.0"
"@typescript-eslint/types" "5.43.0"
"@typescript-eslint/typescript-estree" "5.43.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/types" "5.57.0"
"@typescript-eslint/visitor-keys" "5.57.0"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.48.2":
version "5.48.2"
@ -3316,13 +3415,19 @@
eslint-utils "^3.0.0"
semver "^7.3.7"
"@typescript-eslint/visitor-keys@5.43.0":
version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.43.0.tgz#cbbdadfdfea385310a20a962afda728ea106befa"
integrity sha512-icl1jNH/d18OVHLfcwdL3bWUKsBeIiKYTGxMJCoGe7xFht+E4QgzOqoWYrU8XSLJWhVw8nTacbm03v23J/hFTg==
"@typescript-eslint/utils@5.57.0":
version "5.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.57.0.tgz#eab8f6563a2ac31f60f3e7024b91bf75f43ecef6"
integrity sha512-ps/4WohXV7C+LTSgAL5CApxvxbMkl9B9AUZRtnEFonpIxZDIT7wC1xfvuJONMidrkB9scs4zhtRyIwHh4+18kw==
dependencies:
"@typescript-eslint/types" "5.43.0"
eslint-visitor-keys "^3.3.0"
"@eslint-community/eslint-utils" "^4.2.0"
"@types/json-schema" "^7.0.9"
"@types/semver" "^7.3.12"
"@typescript-eslint/scope-manager" "5.57.0"
"@typescript-eslint/types" "5.57.0"
"@typescript-eslint/typescript-estree" "5.57.0"
eslint-scope "^5.1.1"
semver "^7.3.7"
"@typescript-eslint/visitor-keys@5.48.2":
version "5.48.2"
@ -3332,6 +3437,14 @@
"@typescript-eslint/types" "5.48.2"
eslint-visitor-keys "^3.3.0"
"@typescript-eslint/visitor-keys@5.57.0":
version "5.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.57.0.tgz#e2b2f4174aff1d15eef887ce3d019ecc2d7a8ac1"
integrity sha512-ery2g3k0hv5BLiKpPuwYt9KBkAp2ugT6VvyShXdLOkax895EC55sP0Tx5L0fZaQueiK3fBLvHVvEl3jFS5ia+g==
dependencies:
"@typescript-eslint/types" "5.57.0"
eslint-visitor-keys "^3.3.0"
"@webassemblyjs/ast@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
@ -3601,10 +3714,10 @@ ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
angular-gridster2@~15.0.3:
version "15.0.3"
resolved "https://registry.yarnpkg.com/angular-gridster2/-/angular-gridster2-15.0.3.tgz#cd77d7b1353b6cf94399fac9c299baca99049552"
integrity sha512-iyF4HPv2eBpURNEQ0t/zFhcIQS0SC2EZJW6eg4WoFQ/r0R7Q93Urs8sEDQfRqtxf6ES60LXJMkNdiX9Eh1j/Qg==
angular-gridster2@~15.0.4:
version "15.0.4"
resolved "https://registry.yarnpkg.com/angular-gridster2/-/angular-gridster2-15.0.4.tgz#aca1a3df0ba5776d2f053cbbdce48ad73019b438"
integrity sha512-5BUUn4ihar8pLdN2YwILygZBPk3v94GQzSML6eYdg5o2USPCRuqXyFznYjiMWJRGrcLrzZYdcKULR+LB/1+tfQ==
dependencies:
tslib "^2.4.0"
@ -4506,10 +4619,10 @@ core-js-compat@^3.25.1:
dependencies:
browserslist "^4.21.5"
core-js@^3.26.1:
version "3.29.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.29.0.tgz#0273e142b67761058bcde5615c503c7406b572d6"
integrity sha512-VG23vuEisJNkGl6XQmFJd3rEG/so/CNatqeE+7uZAwTSwFeB/qaO0be8xZYUNWprJ/GIwL8aMt9cj1kvbpTZhg==
core-js@^3.29.1:
version "3.29.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.29.1.tgz#40ff3b41588b091aaed19ca1aa5cb111803fa9a6"
integrity sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==
core-util-is@1.0.2:
version "1.0.2"
@ -5597,13 +5710,20 @@ eslint-visitor-keys@^3.3.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
eslint@^8.28.0:
version "8.35.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.35.0.tgz#fffad7c7e326bae606f0e8f436a6158566d42323"
integrity sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==
dependencies:
"@eslint/eslintrc" "^2.0.0"
"@eslint/js" "8.35.0"
eslint-visitor-keys@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc"
integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==
eslint@^8.37.0:
version "8.37.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.37.0.tgz#1f660ef2ce49a0bfdec0b0d698e0b8b627287412"
integrity sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.4.0"
"@eslint/eslintrc" "^2.0.2"
"@eslint/js" "8.37.0"
"@humanwhocodes/config-array" "^0.11.8"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
@ -5614,9 +5734,8 @@ eslint@^8.28.0:
doctrine "^3.0.0"
escape-string-regexp "^4.0.0"
eslint-scope "^7.1.1"
eslint-utils "^3.0.0"
eslint-visitor-keys "^3.3.0"
espree "^9.4.0"
eslint-visitor-keys "^3.4.0"
espree "^9.5.1"
esquery "^1.4.2"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
@ -5638,19 +5757,18 @@ eslint@^8.28.0:
minimatch "^3.1.2"
natural-compare "^1.4.0"
optionator "^0.9.1"
regexpp "^3.2.0"
strip-ansi "^6.0.1"
strip-json-comments "^3.1.0"
text-table "^0.2.0"
espree@^9.4.0:
version "9.4.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.1.tgz#51d6092615567a2c2cff7833445e37c28c0065bd"
integrity sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==
espree@^9.5.1:
version "9.5.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4"
integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==
dependencies:
acorn "^8.8.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.3.0"
eslint-visitor-keys "^3.4.0"
esprima@^4.0.0:
version "4.0.1"
@ -7031,7 +7149,7 @@ jest-worker@^27.4.5:
merge-stream "^2.0.0"
supports-color "^8.0.0"
jquery.terminal@^2.29.4:
jquery.terminal@^2.35.3:
version "2.35.3"
resolved "https://registry.yarnpkg.com/jquery.terminal/-/jquery.terminal-2.35.3.tgz#64c9291c9fdfa845083a5a66c6052c96f5cb5f81"
integrity sha512-McYaOivaUB2Gubn8IBhQY7zxjGWXg4ENSofL11rt7HACWUDjqncXxakShuqq7Ma0y+BwCcYdltPl1e+WpDJkeg==
@ -7051,7 +7169,12 @@ jquery@>=1.9.1, jquery@^3.5.0, jquery@^3.6.0:
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.3.tgz#23ed2ffed8a19e048814f13391a19afcdba160e6"
integrity sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==
js-beautify@^1.14.0:
jquery@^3.6.3:
version "3.6.4"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.4.tgz#ba065c188142100be4833699852bf7c24dc0252f"
integrity sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ==
js-beautify@^1.14.7:
version "1.14.7"
resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.7.tgz#9206296de33f86dc106d3e50a35b7cf8729703b2"
integrity sha512-5SOX1KXPFKx+5f6ZrPsIPEY7NwKeQz47n3jm2i+XeHx9MoRsfQenlOP13FQhWvg8JRS0+XLO6XYUQ2GX+q+T9A==
@ -7273,7 +7396,7 @@ jstree-bootstrap-theme@^1.0.1:
dependencies:
jquery ">=1.9.1"
jstree@^3.3.12:
jstree@^3.3.15:
version "3.3.15"
resolved "https://registry.yarnpkg.com/jstree/-/jstree-3.3.15.tgz#364bfa0fc4c0b1b7b5bc122cda44a0f09e59eba4"
integrity sha512-fNK2EBgGjaJQ3cJuINX/80vDeAufYWtM0csudgYl3eJG+eRAH/1r1IJVUOvAlJIa+uSgg+Fi8uGrt+Xbs92eKg==
@ -7908,10 +8031,10 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
moment-timezone@^0.5.41:
version "0.5.41"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.41.tgz#a7ad3285fd24aaf5f93b8119a9d749c8039c64c5"
integrity sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg==
moment-timezone@^0.5.42:
version "0.5.42"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.42.tgz#c59f2aa00442d0dcd1d258d2182873d637b4e17b"
integrity sha512-tjI9goqwzkflKSTxJo+jC/W8riTFwEjjunssmFvAWlvNVApjbkJM7UHggyKO0q1Fd/kZVKY77H7C9A0XKhhAFw==
dependencies:
moment "^2.29.4"
@ -8007,10 +8130,10 @@ ngx-clipboard@^15.1.0:
ngx-window-token ">=6.0.0"
tslib "^2.0.0"
ngx-color-picker@^13.0.0:
version "13.0.0"
resolved "https://registry.yarnpkg.com/ngx-color-picker/-/ngx-color-picker-13.0.0.tgz#fe6d3b2def721ebc4f2a1a4ed83e552e0cfe3601"
integrity sha512-3mgMbs21KeqnmmY5p1cn71ckTH3q7gBt6Qn0fMfeF/Ql7ddTZsW4Z7Z8ga6LymMP/ugooGuLOFX+V6yx0dDxAw==
ngx-color-picker@^14.0.0:
version "14.0.0"
resolved "https://registry.yarnpkg.com/ngx-color-picker/-/ngx-color-picker-14.0.0.tgz#4587f517ac5683a705d4e55cd0939afa91faa853"
integrity sha512-w28zx2DyVpIJeNsTB3T2LUI4Ed/Ujf5Uhxuh0dllputfpxXwZG9ocSJM/0L67+fxA3UnfvvXVZNUX1Ny5nZIIw==
dependencies:
tslib "^2.3.0"
@ -8044,10 +8167,10 @@ ngx-hm-carousel@^3.0.0:
hammerjs "^2.0.8"
tslib "^2.3.1"
ngx-markdown@^15.1.1:
version "15.1.1"
resolved "https://registry.yarnpkg.com/ngx-markdown/-/ngx-markdown-15.1.1.tgz#c52a375bed49ca85ad70784378d65205be8b2e41"
integrity sha512-HdiYJVBoZoKnTti3Yj+PR7Uh5LuP5xx97UyFjhgWV25HrT1NE5Lorcg0Xxi7x1wyuRr9V/CKsd5x45jQmY1CZw==
ngx-markdown@^15.1.2:
version "15.1.2"
resolved "https://registry.yarnpkg.com/ngx-markdown/-/ngx-markdown-15.1.2.tgz#e78ffc5f96a79e4775b9cdf09f10d5b1401b5317"
integrity sha512-mAUORpUnHCV4tnxEHV4oS5YEdIaolUclulCblUrvAEU3AEND8MMTxlwHujqVC2M398/aKH0SBSrjLzDbMUJCoQ==
dependencies:
tslib "^2.3.0"
optionalDependencies:
@ -8057,10 +8180,10 @@ ngx-markdown@^15.1.1:
mermaid "^9.1.2"
prismjs "^1.28.0"
ngx-sharebuttons@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/ngx-sharebuttons/-/ngx-sharebuttons-11.0.0.tgz#3fabdb49cc9a3a648e2d205b8b10abf245fa6e32"
integrity sha512-uvFQgQ6VtJu901iKcsbXFt/9jk9fWGKKD8ZQdOYK9a7HBd8z/Bmb6x1Hly1roF7AbMvCE2fYwOcbRHsEvTecbg==
ngx-sharebuttons@^12.0.0:
version "12.0.0"
resolved "https://registry.yarnpkg.com/ngx-sharebuttons/-/ngx-sharebuttons-12.0.0.tgz#f9080254f5bcec980e784c83e957c4bc0a1b22a2"
integrity sha512-N4tkoNSPARCRD8nE1mMwfQ2vXBlAu1MQZ33KAC5JBxqRlu8+Bu9dGZZO3/Wqq2fmY+shvVmjFKb9wF528dmKNA==
dependencies:
tslib "^2.0.0"
@ -9173,11 +9296,6 @@ regexp.prototype.flags@^1.4.3:
define-properties "^1.1.3"
functions-have-names "^1.2.2"
regexpp@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
regexpu-core@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.1.tgz#66900860f88def39a5cb79ebd9490e84f17bcdfb"
@ -10206,7 +10324,7 @@ tsconfig-paths@^4.1.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
tslib@2.5.0, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1:
tslib@2.5.0, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
@ -10292,10 +10410,10 @@ typeface-roboto@^1.1.13:
resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-1.1.13.tgz#9c4517cb91e311706c74823e857b4bac9a764ae5"
integrity sha512-YXvbd3a1QTREoD+FJoEkl0VQNJoEjewR2H11IjVv4bp6ahuIcw0yyw/3udC4vJkHw3T3cUh85FTg8eWef3pSaw==
typescript@~4.8.4:
version "4.8.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
typescript@~4.9.5:
version "4.9.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
typical@^4.0.0:
version "4.0.0"
@ -10598,7 +10716,7 @@ webpack-subresource-integrity@5.1.0:
dependencies:
typed-assert "^1.0.8"
webpack@5.75.0, webpack@^5.75.0:
webpack@5.75.0:
version "5.75.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152"
integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==
@ -10628,6 +10746,66 @@ webpack@5.75.0, webpack@^5.75.0:
watchpack "^2.4.0"
webpack-sources "^3.2.3"
webpack@5.76.1:
version "5.76.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.1.tgz#7773de017e988bccb0f13c7d75ec245f377d295c"
integrity sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
"@webassemblyjs/ast" "1.11.1"
"@webassemblyjs/wasm-edit" "1.11.1"
"@webassemblyjs/wasm-parser" "1.11.1"
acorn "^8.7.1"
acorn-import-assertions "^1.7.6"
browserslist "^4.14.5"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.10.0"
es-module-lexer "^0.9.0"
eslint-scope "5.1.1"
events "^3.2.0"
glob-to-regexp "^0.4.1"
graceful-fs "^4.2.9"
json-parse-even-better-errors "^2.3.1"
loader-runner "^4.2.0"
mime-types "^2.1.27"
neo-async "^2.6.2"
schema-utils "^3.1.0"
tapable "^2.1.1"
terser-webpack-plugin "^5.1.3"
watchpack "^2.4.0"
webpack-sources "^3.2.3"
webpack@^5.77.0:
version "5.77.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.77.0.tgz#dea3ad16d7ea6b84aa55fa42f4eac9f30e7eb9b4"
integrity sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
"@webassemblyjs/ast" "1.11.1"
"@webassemblyjs/wasm-edit" "1.11.1"
"@webassemblyjs/wasm-parser" "1.11.1"
acorn "^8.7.1"
acorn-import-assertions "^1.7.6"
browserslist "^4.14.5"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.10.0"
es-module-lexer "^0.9.0"
eslint-scope "5.1.1"
events "^3.2.0"
glob-to-regexp "^0.4.1"
graceful-fs "^4.2.9"
json-parse-even-better-errors "^2.3.1"
loader-runner "^4.2.0"
mime-types "^2.1.27"
neo-async "^2.6.2"
schema-utils "^3.1.0"
tapable "^2.1.1"
terser-webpack-plugin "^5.1.3"
watchpack "^2.4.0"
webpack-sources "^3.2.3"
websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
version "0.7.4"
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
@ -10886,9 +11064,9 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zone.js@~0.11.8:
version "0.11.8"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.11.8.tgz#40dea9adc1ad007b5effb2bfed17f350f1f46a21"
integrity sha512-82bctBg2hKcEJ21humWIkXRlLBBmrc3nN7DFh5LGGhcyycO2S7FN8NmdvlcKaGFDNVL4/9kFLmwmInTavdJERA==
zone.js@~0.13.0:
version "0.13.0"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.13.0.tgz#4c735cb8ef49312b58c0ad13451996dc2b202a6d"
integrity sha512-7m3hNNyswsdoDobCkYNAy5WiUulkMd3+fWaGT9ij6iq3Zr/IwJo4RMCYPSDjT+r7tnPErmY9sZpKhWQ8S5k6XQ==
dependencies:
tslib "^2.3.0"

Loading…
Cancel
Save