From 3c53d7ec716d4f6d519ba58942f158ff578dceba Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 29 Jan 2024 13:55:44 +0200 Subject: [PATCH 01/11] deleted db upgrade scripts up to 3.5.0 version --- .../main/data/upgrade/1.3.0/schema_update.cql | 187 ---- .../main/data/upgrade/1.3.1/schema_update.sql | 17 - .../main/data/upgrade/1.4.0/schema_update.cql | 112 --- .../main/data/upgrade/1.4.0/schema_update.sql | 41 - .../main/data/upgrade/2.0.0/schema_update.cql | 103 -- .../main/data/upgrade/2.0.0/schema_update.sql | 44 - .../main/data/upgrade/2.1.1/schema_update.cql | 74 -- .../main/data/upgrade/2.1.1/schema_update.sql | 29 - .../main/data/upgrade/2.1.2/schema_update.cql | 110 --- .../main/data/upgrade/2.1.2/schema_update.sql | 32 - .../main/data/upgrade/2.2.0/schema_update.sql | 19 - .../main/data/upgrade/2.3.1/schema_update.sql | 17 - .../main/data/upgrade/2.4.0/schema_update.sql | 23 - .../main/data/upgrade/2.4.2/schema_update.sql | 31 - .../schema_update_psql_drop_partitions.sql | 209 ----- .../upgrade/2.4.3/schema_update_psql_ts.sql | 359 ------- .../2.4.3/schema_update_timescale_ts.sql | 208 ----- .../data/upgrade/2.4.3/schema_update_ttl.sql | 150 --- .../upgrade/3.0.1/schema_update_to_uuid.sql | 878 ------------------ .../main/data/upgrade/3.1.0/schema_update.sql | 17 - .../upgrade/3.1.1/schema_update_after.sql | 28 - .../upgrade/3.1.1/schema_update_before.sql | 154 --- .../main/data/upgrade/3.2.1/schema_update.sql | 23 - .../data/upgrade/3.2.1/schema_update_ttl.sql | 87 -- .../main/data/upgrade/3.2.2/schema_update.sql | 216 ----- .../upgrade/3.2.2/schema_update_event.sql | 90 -- .../data/upgrade/3.2.2/schema_update_ttl.sql | 32 - .../main/data/upgrade/3.3.2/schema_update.sql | 71 -- .../3.3.2/schema_update_lwm2m_bootstrap.sql | 213 ----- .../3.3.3/schema_event_ttl_procedure.sql | 50 - .../main/data/upgrade/3.3.3/schema_update.sql | 29 - .../main/data/upgrade/3.3.4/schema_update.sql | 140 --- .../main/data/upgrade/3.4.0/schema_update.sql | 234 ----- .../main/data/upgrade/3.4.1/schema_update.sql | 142 --- .../upgrade/3.4.1/schema_update_after.sql | 21 - .../upgrade/3.4.1/schema_update_before.sql | 46 - .../main/data/upgrade/3.4.4/schema_update.sql | 379 -------- .../install/ThingsboardInstallService.java | 155 +--- .../CassandraTsDatabaseUpgradeService.java | 24 - .../service/install/DatabaseHelper.java | 114 --- .../DefaultSystemDataLoaderService.java | 5 - .../install/SqlDatabaseUpgradeService.java | 691 +------------- .../install/SqlTsDatabaseUpgradeService.java | 209 ----- .../install/SystemDataLoaderService.java | 2 - .../TimescaleTsDatabaseUpgradeService.java | 162 ---- .../install/cql/CassandraDbHelper.java | 218 ----- .../CassandraEntitiesToSqlMigrateService.java | 327 ------- .../migrate/CassandraToSqlEventTsColumn.java | 40 - .../CassandraTsLatestToSqlMigrateService.java | 4 +- .../migrate/EntitiesMigrateService.java | 22 - .../service/install/sql/SqlDbHelper.java | 176 ---- .../update/DefaultCacheCleanupService.java | 41 - .../update/DefaultDataUpdateService.java | 572 ------------ .../install/update/PaginatedUpdater.java | 64 -- .../install/update/RateLimitsUpdater.java | 115 --- .../server/dao/audit/AuditLogDao.java | 2 - .../server/dao/sql/audit/JpaAuditLogDao.java | 31 - .../resources/sql/schema-ts-latest-psql.sql | 0 58 files changed, 5 insertions(+), 7584 deletions(-) delete mode 100644 application/src/main/data/upgrade/1.3.0/schema_update.cql delete mode 100644 application/src/main/data/upgrade/1.3.1/schema_update.sql delete mode 100644 application/src/main/data/upgrade/1.4.0/schema_update.cql delete mode 100644 application/src/main/data/upgrade/1.4.0/schema_update.sql delete mode 100644 application/src/main/data/upgrade/2.0.0/schema_update.cql delete mode 100644 application/src/main/data/upgrade/2.0.0/schema_update.sql delete mode 100644 application/src/main/data/upgrade/2.1.1/schema_update.cql delete mode 100644 application/src/main/data/upgrade/2.1.1/schema_update.sql delete mode 100644 application/src/main/data/upgrade/2.1.2/schema_update.cql delete mode 100644 application/src/main/data/upgrade/2.1.2/schema_update.sql delete mode 100644 application/src/main/data/upgrade/2.2.0/schema_update.sql delete mode 100644 application/src/main/data/upgrade/2.3.1/schema_update.sql delete mode 100644 application/src/main/data/upgrade/2.4.0/schema_update.sql delete mode 100644 application/src/main/data/upgrade/2.4.2/schema_update.sql delete mode 100644 application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql delete mode 100644 application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql delete mode 100644 application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql delete mode 100644 application/src/main/data/upgrade/2.4.3/schema_update_ttl.sql delete mode 100644 application/src/main/data/upgrade/3.0.1/schema_update_to_uuid.sql delete mode 100644 application/src/main/data/upgrade/3.1.0/schema_update.sql delete mode 100644 application/src/main/data/upgrade/3.1.1/schema_update_after.sql delete mode 100644 application/src/main/data/upgrade/3.1.1/schema_update_before.sql delete mode 100644 application/src/main/data/upgrade/3.2.1/schema_update.sql delete mode 100644 application/src/main/data/upgrade/3.2.1/schema_update_ttl.sql delete mode 100644 application/src/main/data/upgrade/3.2.2/schema_update.sql delete mode 100644 application/src/main/data/upgrade/3.2.2/schema_update_event.sql delete mode 100644 application/src/main/data/upgrade/3.2.2/schema_update_ttl.sql delete mode 100644 application/src/main/data/upgrade/3.3.2/schema_update.sql delete mode 100644 application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql delete mode 100644 application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql delete mode 100644 application/src/main/data/upgrade/3.3.3/schema_update.sql delete mode 100644 application/src/main/data/upgrade/3.3.4/schema_update.sql delete mode 100644 application/src/main/data/upgrade/3.4.0/schema_update.sql delete mode 100644 application/src/main/data/upgrade/3.4.1/schema_update.sql delete mode 100644 application/src/main/data/upgrade/3.4.1/schema_update_after.sql delete mode 100644 application/src/main/data/upgrade/3.4.1/schema_update_before.sql delete mode 100644 application/src/main/data/upgrade/3.4.4/schema_update.sql delete mode 100644 application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/install/migrate/EntitiesMigrateService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java rename application/src/main/data/upgrade/3.0.1/schema_ts_latest.sql => dao/src/main/resources/sql/schema-ts-latest-psql.sql (100%) diff --git a/application/src/main/data/upgrade/1.3.0/schema_update.cql b/application/src/main/data/upgrade/1.3.0/schema_update.cql deleted file mode 100644 index d28e9f689a..0000000000 --- a/application/src/main/data/upgrade/1.3.0/schema_update.cql +++ /dev/null @@ -1,187 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -DROP MATERIALIZED VIEW IF EXISTS thingsboard.device_by_tenant_and_name; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.device_by_tenant_and_search_text; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.device_by_tenant_by_type_and_search_text; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.device_by_customer_and_search_text; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.device_by_customer_by_type_and_search_text; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.device_types_by_tenant; - -DROP TABLE IF EXISTS thingsboard.device; - -CREATE TABLE IF NOT EXISTS thingsboard.device ( - id timeuuid, - tenant_id timeuuid, - customer_id timeuuid, - name text, - type text, - search_text text, - additional_info text, - PRIMARY KEY (id, tenant_id, customer_id, type) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_name AS - SELECT * - from thingsboard.device - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, name, id, customer_id, type) - WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_search_text AS - SELECT * - from thingsboard.device - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, search_text, id, customer_id, type) - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_by_type_and_search_text AS - SELECT * - from thingsboard.device - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, type, search_text, id, customer_id) - WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_and_search_text AS - SELECT * - from thingsboard.device - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( customer_id, tenant_id, search_text, id, type ) - WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC ); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_by_type_and_search_text AS - SELECT * - from thingsboard.device - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( customer_id, tenant_id, type, search_text, id ) - WITH CLUSTERING ORDER BY ( tenant_id DESC, type ASC, search_text ASC, id DESC ); - -DROP MATERIALIZED VIEW IF EXISTS thingsboard.asset_by_tenant_and_name; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.asset_by_tenant_and_search_text; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.asset_by_tenant_by_type_and_search_text; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.asset_by_customer_and_search_text; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.asset_by_customer_by_type_and_search_text; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.asset_types_by_tenant; - -DROP TABLE IF EXISTS thingsboard.asset; - -CREATE TABLE IF NOT EXISTS thingsboard.asset ( - id timeuuid, - tenant_id timeuuid, - customer_id timeuuid, - name text, - type text, - search_text text, - additional_info text, - PRIMARY KEY (id, tenant_id, customer_id, type) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_and_name AS - SELECT * - from thingsboard.asset - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, name, id, customer_id, type) - WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_and_search_text AS - SELECT * - from thingsboard.asset - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, search_text, id, customer_id, type) - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_by_type_and_search_text AS - SELECT * - from thingsboard.asset - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, type, search_text, id, customer_id) - WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_customer_and_search_text AS - SELECT * - from thingsboard.asset - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( customer_id, tenant_id, search_text, id, type ) - WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC ); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_customer_by_type_and_search_text AS - SELECT * - from thingsboard.asset - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( customer_id, tenant_id, type, search_text, id ) - WITH CLUSTERING ORDER BY ( tenant_id DESC, type ASC, search_text ASC, id DESC ); - -CREATE TABLE IF NOT EXISTS thingsboard.entity_subtype ( - tenant_id timeuuid, - entity_type text, // (DEVICE, ASSET) - type text, - PRIMARY KEY (tenant_id, entity_type, type) -); - -CREATE TABLE IF NOT EXISTS thingsboard.alarm ( - id timeuuid, - tenant_id timeuuid, - type text, - originator_id timeuuid, - originator_type text, - severity text, - status text, - start_ts bigint, - end_ts bigint, - ack_ts bigint, - clear_ts bigint, - details text, - propagate boolean, - PRIMARY KEY ((tenant_id, originator_id, originator_type), type, id) -) WITH CLUSTERING ORDER BY ( type ASC, id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.alarm_by_id AS - SELECT * - from thingsboard.alarm - WHERE tenant_id IS NOT NULL AND originator_id IS NOT NULL AND originator_type IS NOT NULL AND type IS NOT NULL - AND type IS NOT NULL AND id IS NOT NULL - PRIMARY KEY (id, tenant_id, originator_id, originator_type, type) - WITH CLUSTERING ORDER BY ( tenant_id ASC, originator_id ASC, originator_type ASC, type ASC); - -DROP MATERIALIZED VIEW IF EXISTS thingsboard.relation_by_type_and_child_type; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.reverse_relation; - -DROP TABLE IF EXISTS thingsboard.relation; - -CREATE TABLE IF NOT EXISTS thingsboard.relation ( - from_id timeuuid, - from_type text, - to_id timeuuid, - to_type text, - relation_type_group text, - relation_type text, - additional_info text, - PRIMARY KEY ((from_id, from_type), relation_type_group, relation_type, to_id, to_type) -) WITH CLUSTERING ORDER BY ( relation_type_group ASC, relation_type ASC, to_id ASC, to_type ASC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.relation_by_type_and_child_type AS - SELECT * - from thingsboard.relation - WHERE from_id IS NOT NULL AND from_type IS NOT NULL AND relation_type_group IS NOT NULL AND relation_type IS NOT NULL AND to_id IS NOT NULL AND to_type IS NOT NULL - PRIMARY KEY ((from_id, from_type), relation_type_group, relation_type, to_type, to_id) - WITH CLUSTERING ORDER BY ( relation_type_group ASC, relation_type ASC, to_type ASC, to_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.reverse_relation AS - SELECT * - from thingsboard.relation - WHERE from_id IS NOT NULL AND from_type IS NOT NULL AND relation_type_group IS NOT NULL AND relation_type IS NOT NULL AND to_id IS NOT NULL AND to_type IS NOT NULL - PRIMARY KEY ((to_id, to_type), relation_type_group, relation_type, from_id, from_type) - WITH CLUSTERING ORDER BY ( relation_type_group ASC, relation_type ASC, from_id ASC, from_type ASC); diff --git a/application/src/main/data/upgrade/1.3.1/schema_update.sql b/application/src/main/data/upgrade/1.3.1/schema_update.sql deleted file mode 100644 index 73f37d457a..0000000000 --- a/application/src/main/data/upgrade/1.3.1/schema_update.sql +++ /dev/null @@ -1,17 +0,0 @@ --- --- Copyright © 2016-2024 The Thingsboard Authors --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -ALTER TABLE ts_kv_latest ALTER COLUMN str_v SET DATA TYPE varchar(10000000); diff --git a/application/src/main/data/upgrade/1.4.0/schema_update.cql b/application/src/main/data/upgrade/1.4.0/schema_update.cql deleted file mode 100644 index ba5ce1c4c7..0000000000 --- a/application/src/main/data/upgrade/1.4.0/schema_update.cql +++ /dev/null @@ -1,112 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id ( - tenant_id timeuuid, - id timeuuid, - customer_id timeuuid, - entity_id timeuuid, - entity_type text, - entity_name text, - user_id timeuuid, - user_name text, - action_type text, - action_data text, - action_status text, - action_failure_details text, - PRIMARY KEY ((tenant_id, entity_id, entity_type), id) -); - -CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_customer_id ( - tenant_id timeuuid, - id timeuuid, - customer_id timeuuid, - entity_id timeuuid, - entity_type text, - entity_name text, - user_id timeuuid, - user_name text, - action_type text, - action_data text, - action_status text, - action_failure_details text, - PRIMARY KEY ((tenant_id, customer_id), id) -); - -CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_user_id ( - tenant_id timeuuid, - id timeuuid, - customer_id timeuuid, - entity_id timeuuid, - entity_type text, - entity_name text, - user_id timeuuid, - user_name text, - action_type text, - action_data text, - action_status text, - action_failure_details text, - PRIMARY KEY ((tenant_id, user_id), id) -); - - - -CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id ( - tenant_id timeuuid, - id timeuuid, - partition bigint, - customer_id timeuuid, - entity_id timeuuid, - entity_type text, - entity_name text, - user_id timeuuid, - user_name text, - action_type text, - action_data text, - action_status text, - action_failure_details text, - PRIMARY KEY ((tenant_id, partition), id) -); - -CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions ( - tenant_id timeuuid, - partition bigint, - PRIMARY KEY (( tenant_id ), partition) -) WITH CLUSTERING ORDER BY ( partition ASC ) -AND compaction = { 'class' : 'LeveledCompactionStrategy' }; - -DROP MATERIALIZED VIEW IF EXISTS thingsboard.dashboard_by_tenant_and_search_text; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.dashboard_by_customer_and_search_text; - -DROP TABLE IF EXISTS thingsboard.dashboard; - -CREATE TABLE IF NOT EXISTS thingsboard.dashboard ( - id timeuuid, - tenant_id timeuuid, - title text, - search_text text, - assigned_customers text, - configuration text, - PRIMARY KEY (id, tenant_id) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS - SELECT * - from thingsboard.dashboard - WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, search_text, id ) - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC ); - diff --git a/application/src/main/data/upgrade/1.4.0/schema_update.sql b/application/src/main/data/upgrade/1.4.0/schema_update.sql deleted file mode 100644 index 9c24ba95cf..0000000000 --- a/application/src/main/data/upgrade/1.4.0/schema_update.sql +++ /dev/null @@ -1,41 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE TABLE IF NOT EXISTS audit_log ( - id varchar(31) NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY, - tenant_id varchar(31), - customer_id varchar(31), - entity_id varchar(31), - entity_type varchar(255), - entity_name varchar(255), - user_id varchar(31), - user_name varchar(255), - action_type varchar(255), - action_data varchar(1000000), - action_status varchar(255), - action_failure_details varchar(1000000) -); - -DROP TABLE IF EXISTS dashboard; - -CREATE TABLE IF NOT EXISTS dashboard ( - id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY, - configuration varchar(10000000), - assigned_customers varchar(1000000), - search_text varchar(255), - tenant_id varchar(31), - title varchar(255) -); diff --git a/application/src/main/data/upgrade/2.0.0/schema_update.cql b/application/src/main/data/upgrade/2.0.0/schema_update.cql deleted file mode 100644 index 843e81f2f9..0000000000 --- a/application/src/main/data/upgrade/2.0.0/schema_update.cql +++ /dev/null @@ -1,103 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( - node_id timeuuid, - cluster_partition bigint, - ts_partition bigint, - ts bigint, - msg blob, - PRIMARY KEY ((node_id, cluster_partition, ts_partition), ts)) -WITH CLUSTERING ORDER BY (ts DESC) -AND compaction = { - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', - 'min_threshold': '5', - 'base_time_seconds': '43200', - 'max_window_size_seconds': '43200', - 'tombstone_threshold': '0.9', - 'unchecked_tombstone_compaction': 'true' -}; - -CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( - node_id timeuuid, - cluster_partition bigint, - ts_partition bigint, - msg_id timeuuid, - PRIMARY KEY ((node_id, cluster_partition, ts_partition), msg_id)) -WITH CLUSTERING ORDER BY (msg_id DESC) -AND compaction = { - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', - 'min_threshold': '5', - 'base_time_seconds': '43200', - 'max_window_size_seconds': '43200', - 'tombstone_threshold': '0.9', - 'unchecked_tombstone_compaction': 'true' -}; - -CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( - node_id timeuuid, - cluster_partition bigint, - ts_partition bigint, - PRIMARY KEY ((node_id, cluster_partition), ts_partition)) -WITH CLUSTERING ORDER BY (ts_partition DESC) -AND compaction = { - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', - 'min_threshold': '5', - 'base_time_seconds': '43200', - 'max_window_size_seconds': '43200', - 'tombstone_threshold': '0.9', - 'unchecked_tombstone_compaction': 'true' -}; - -CREATE TABLE IF NOT EXISTS thingsboard.rule_chain ( - id uuid, - tenant_id uuid, - name text, - search_text text, - first_rule_node_id uuid, - root boolean, - debug_mode boolean, - configuration text, - additional_info text, - PRIMARY KEY (id, tenant_id) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_search_text AS - SELECT * - from thingsboard.rule_chain - WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, search_text, id ) - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC ); - -CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( - id uuid, - rule_chain_id uuid, - type text, - name text, - debug_mode boolean, - search_text text, - configuration text, - additional_info text, - PRIMARY KEY (id) -); - -DROP MATERIALIZED VIEW IF EXISTS thingsboard.rule_by_plugin_token; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.rule_by_tenant_and_search_text; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.plugin_by_api_token; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.plugin_by_tenant_and_search_text; - -DROP TABLE IF EXISTS thingsboard.rule; -DROP TABLE IF EXISTS thingsboard.plugin; diff --git a/application/src/main/data/upgrade/2.0.0/schema_update.sql b/application/src/main/data/upgrade/2.0.0/schema_update.sql deleted file mode 100644 index 5395d9a160..0000000000 --- a/application/src/main/data/upgrade/2.0.0/schema_update.sql +++ /dev/null @@ -1,44 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE TABLE IF NOT EXISTS rule_chain ( - id varchar(31) NOT NULL CONSTRAINT rule_chain_pkey PRIMARY KEY, - additional_info varchar, - configuration varchar(10000000), - name varchar(255), - first_rule_node_id varchar(31), - root boolean, - debug_mode boolean, - search_text varchar(255), - tenant_id varchar(31) -); - -CREATE TABLE IF NOT EXISTS rule_node ( - id varchar(31) NOT NULL CONSTRAINT rule_node_pkey PRIMARY KEY, - rule_chain_id varchar(31), - additional_info varchar, - configuration varchar(10000000), - type varchar(255), - name varchar(255), - debug_mode boolean, - search_text varchar(255) -); - -DROP TABLE rule; -DROP TABLE plugin; - -DELETE FROM alarm WHERE originator_type = 3 OR originator_type = 4; -UPDATE alarm SET originator_type = (originator_type - 2) where originator_type > 2; diff --git a/application/src/main/data/upgrade/2.1.1/schema_update.cql b/application/src/main/data/upgrade/2.1.1/schema_update.cql deleted file mode 100644 index ad5c0a13ac..0000000000 --- a/application/src/main/data/upgrade/2.1.1/schema_update.cql +++ /dev/null @@ -1,74 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE TABLE IF NOT EXISTS thingsboard.entity_views ( - id timeuuid, - entity_id timeuuid, - entity_type text, - tenant_id timeuuid, - customer_id timeuuid, - name text, - keys text, - start_ts bigint, - end_ts bigint, - search_text text, - additional_info text, - PRIMARY KEY (id, entity_id, tenant_id, customer_id) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS - SELECT * - from thingsboard.entity_views - WHERE tenant_id IS NOT NULL - AND entity_id IS NOT NULL - AND customer_id IS NOT NULL - AND name IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, name, id, customer_id, entity_id) - WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS - SELECT * - from thingsboard.entity_views - WHERE tenant_id IS NOT NULL - AND entity_id IS NOT NULL - AND customer_id IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id) - WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS - SELECT * - from thingsboard.entity_views - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND entity_id IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id) - WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS - SELECT * - from thingsboard.entity_views - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND entity_id IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id) - WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC); \ No newline at end of file diff --git a/application/src/main/data/upgrade/2.1.1/schema_update.sql b/application/src/main/data/upgrade/2.1.1/schema_update.sql deleted file mode 100644 index 91d0b3bcb6..0000000000 --- a/application/src/main/data/upgrade/2.1.1/schema_update.sql +++ /dev/null @@ -1,29 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE TABLE IF NOT EXISTS entity_views ( - id varchar(31) NOT NULL CONSTRAINT entity_views_pkey PRIMARY KEY, - entity_id varchar(31), - entity_type varchar(255), - tenant_id varchar(31), - customer_id varchar(31), - name varchar(255), - keys varchar(255), - start_ts bigint, - end_ts bigint, - search_text varchar(255), - additional_info varchar -); diff --git a/application/src/main/data/upgrade/2.1.2/schema_update.cql b/application/src/main/data/upgrade/2.1.2/schema_update.cql deleted file mode 100644 index 0fcc6ddbc9..0000000000 --- a/application/src/main/data/upgrade/2.1.2/schema_update.cql +++ /dev/null @@ -1,110 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer; -DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id; - -DROP TABLE IF EXISTS thingsboard.entity_views; - -CREATE TABLE IF NOT EXISTS thingsboard.entity_view ( - id timeuuid, - entity_id timeuuid, - entity_type text, - tenant_id timeuuid, - customer_id timeuuid, - name text, - type text, - keys text, - start_ts bigint, - end_ts bigint, - search_text text, - additional_info text, - PRIMARY KEY (id, entity_id, tenant_id, customer_id, type) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS - SELECT * - from thingsboard.entity_view - WHERE tenant_id IS NOT NULL - AND entity_id IS NOT NULL - AND customer_id IS NOT NULL - AND type IS NOT NULL - AND name IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, name, id, customer_id, entity_id, type) - WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS - SELECT * - from thingsboard.entity_view - WHERE tenant_id IS NOT NULL - AND entity_id IS NOT NULL - AND customer_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id, type) - WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_by_type_and_search_text AS - SELECT * - from thingsboard.entity_view - WHERE tenant_id IS NOT NULL - AND entity_id IS NOT NULL - AND customer_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, type, search_text, id, customer_id, entity_id) - WITH CLUSTERING ORDER BY (type ASC, search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS - SELECT * - from thingsboard.entity_view - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND entity_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id, type) - WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer_and_type AS - SELECT * - from thingsboard.entity_view - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND entity_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, type, customer_id, search_text, id, entity_id) - WITH CLUSTERING ORDER BY (type ASC, customer_id DESC, search_text ASC, id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS - SELECT * - from thingsboard.entity_view - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND entity_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type) - WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC); \ No newline at end of file diff --git a/application/src/main/data/upgrade/2.1.2/schema_update.sql b/application/src/main/data/upgrade/2.1.2/schema_update.sql deleted file mode 100644 index 744edb8834..0000000000 --- a/application/src/main/data/upgrade/2.1.2/schema_update.sql +++ /dev/null @@ -1,32 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -DROP TABLE IF EXISTS entity_views; - -CREATE TABLE IF NOT EXISTS entity_view ( - id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY, - entity_id varchar(31), - entity_type varchar(255), - tenant_id varchar(31), - customer_id varchar(31), - type varchar(255), - name varchar(255), - keys varchar(255), - start_ts bigint, - end_ts bigint, - search_text varchar(255), - additional_info varchar -); diff --git a/application/src/main/data/upgrade/2.2.0/schema_update.sql b/application/src/main/data/upgrade/2.2.0/schema_update.sql deleted file mode 100644 index 67b648a2e3..0000000000 --- a/application/src/main/data/upgrade/2.2.0/schema_update.sql +++ /dev/null @@ -1,19 +0,0 @@ --- --- Copyright © 2016-2024 The Thingsboard Authors --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -ALTER TABLE component_descriptor ADD UNIQUE (clazz); - -ALTER TABLE entity_view ALTER COLUMN keys SET DATA TYPE varchar(10000000); diff --git a/application/src/main/data/upgrade/2.3.1/schema_update.sql b/application/src/main/data/upgrade/2.3.1/schema_update.sql deleted file mode 100644 index a7929e2e35..0000000000 --- a/application/src/main/data/upgrade/2.3.1/schema_update.sql +++ /dev/null @@ -1,17 +0,0 @@ --- --- Copyright © 2016-2024 The Thingsboard Authors --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -ALTER TABLE event ALTER COLUMN body SET DATA TYPE varchar(10000000); diff --git a/application/src/main/data/upgrade/2.4.0/schema_update.sql b/application/src/main/data/upgrade/2.4.0/schema_update.sql deleted file mode 100644 index 5e95b8c8de..0000000000 --- a/application/src/main/data/upgrade/2.4.0/schema_update.sql +++ /dev/null @@ -1,23 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(tenant_id, type, originator_type, originator_id); - -CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id); - -CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id); - -CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id); diff --git a/application/src/main/data/upgrade/2.4.2/schema_update.sql b/application/src/main/data/upgrade/2.4.2/schema_update.sql deleted file mode 100644 index ab1d9201c4..0000000000 --- a/application/src/main/data/upgrade/2.4.2/schema_update.sql +++ /dev/null @@ -1,31 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -DROP INDEX IF EXISTS idx_alarm_originator_alarm_type; - -CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC); - -CREATE INDEX IF NOT EXISTS idx_device_customer_id ON device(tenant_id, customer_id); - -CREATE INDEX IF NOT EXISTS idx_device_customer_id_and_type ON device(tenant_id, customer_id, type); - -CREATE INDEX IF NOT EXISTS idx_device_type ON device(tenant_id, type); - -CREATE INDEX IF NOT EXISTS idx_asset_customer_id ON asset(tenant_id, customer_id); - -CREATE INDEX IF NOT EXISTS idx_asset_customer_id_and_type ON asset(tenant_id, customer_id, type); - -CREATE INDEX IF NOT EXISTS idx_asset_type ON asset(tenant_id, type); \ No newline at end of file diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql b/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql deleted file mode 100644 index 4e91bfb367..0000000000 --- a/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql +++ /dev/null @@ -1,209 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint) - LANGUAGE plpgsql AS -$$ -DECLARE - max_tenant_ttl bigint; - max_customer_ttl bigint; - max_ttl bigint; - date timestamp; - partition_by_max_ttl_date varchar; - partition_by_max_ttl_month varchar; - partition_by_max_ttl_day varchar; - partition_by_max_ttl_year varchar; - partition varchar; - partition_year integer; - partition_month integer; - partition_day integer; - -BEGIN - SELECT max(attribute_kv.long_v) - FROM tenant - INNER JOIN attribute_kv ON tenant.id = attribute_kv.entity_id - WHERE attribute_kv.attribute_key = 'TTL' - into max_tenant_ttl; - SELECT max(attribute_kv.long_v) - FROM customer - INNER JOIN attribute_kv ON customer.id = attribute_kv.entity_id - WHERE attribute_kv.attribute_key = 'TTL' - into max_customer_ttl; - max_ttl := GREATEST(system_ttl, max_customer_ttl, max_tenant_ttl); - if max_ttl IS NOT NULL AND max_ttl > 0 THEN - date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl); - partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); - RAISE NOTICE 'Date by max ttl: %', date; - RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; - IF partition_by_max_ttl_date IS NOT NULL THEN - CASE - WHEN partition_type = 'DAYS' THEN - partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); - partition_by_max_ttl_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); - WHEN partition_type = 'MONTHS' THEN - partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); - ELSE - partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - END CASE; - IF partition_by_max_ttl_year IS NULL THEN - RAISE NOTICE 'Failed to remove partitions by max ttl date due to partition_by_max_ttl_year is null!'; - ELSE - IF partition_type = 'YEARS' THEN - FOR partition IN SELECT tablename - FROM pg_tables - WHERE schemaname = 'public' - AND tablename like 'ts_kv_' || '%' - AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' - AND tablename != 'ts_kv_indefinite' - AND tablename != partition_by_max_ttl_date - LOOP - partition_year := SPLIT_PART(partition, '_', 3)::integer; - IF partition_year < partition_by_max_ttl_year::integer THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition; - EXECUTE format('DROP TABLE IF EXISTS %I', partition); - deleted := deleted + 1; - END IF; - END LOOP; - ELSE - IF partition_type = 'MONTHS' THEN - IF partition_by_max_ttl_month IS NULL THEN - RAISE NOTICE 'Failed to remove months partitions by max ttl date due to partition_by_max_ttl_month is null!'; - ELSE - FOR partition IN SELECT tablename - FROM pg_tables - WHERE schemaname = 'public' - AND tablename like 'ts_kv_' || '%' - AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' - AND tablename != 'ts_kv_indefinite' - AND tablename != partition_by_max_ttl_date - LOOP - partition_year := SPLIT_PART(partition, '_', 3)::integer; - IF partition_year > partition_by_max_ttl_year::integer THEN - RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; - CONTINUE; - ELSE - IF partition_year < partition_by_max_ttl_year::integer THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition; - EXECUTE format('DROP TABLE IF EXISTS %I', partition); - deleted := deleted + 1; - ELSE - partition_month := SPLIT_PART(partition, '_', 4)::integer; - IF partition_year = partition_by_max_ttl_year::integer THEN - IF partition_month >= partition_by_max_ttl_month::integer THEN - RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; - CONTINUE; - ELSE - RAISE NOTICE 'Partition to delete by max ttl: %', partition; - EXECUTE format('DROP TABLE IF EXISTS %I', partition); - deleted := deleted + 1; - END IF; - END IF; - END IF; - END IF; - END LOOP; - END IF; - ELSE - IF partition_type = 'DAYS' THEN - IF partition_by_max_ttl_month IS NULL THEN - RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_month is null!'; - ELSE - IF partition_by_max_ttl_day IS NULL THEN - RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_day is null!'; - ELSE - FOR partition IN SELECT tablename - FROM pg_tables - WHERE schemaname = 'public' - AND tablename like 'ts_kv_' || '%' - AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' - AND tablename != 'ts_kv_indefinite' - AND tablename != partition_by_max_ttl_date - LOOP - partition_year := SPLIT_PART(partition, '_', 3)::integer; - IF partition_year > partition_by_max_ttl_year::integer THEN - RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; - CONTINUE; - ELSE - IF partition_year < partition_by_max_ttl_year::integer THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition; - EXECUTE format('DROP TABLE IF EXISTS %I', partition); - deleted := deleted + 1; - ELSE - partition_month := SPLIT_PART(partition, '_', 4)::integer; - IF partition_month > partition_by_max_ttl_month::integer THEN - RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; - CONTINUE; - ELSE - IF partition_month < partition_by_max_ttl_month::integer THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition; - EXECUTE format('DROP TABLE IF EXISTS %I', partition); - deleted := deleted + 1; - ELSE - partition_day := SPLIT_PART(partition, '_', 5)::integer; - IF partition_day >= partition_by_max_ttl_day::integer THEN - RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; - CONTINUE; - ELSE - IF partition_day < partition_by_max_ttl_day::integer THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition; - EXECUTE format('DROP TABLE IF EXISTS %I', partition); - deleted := deleted + 1; - END IF; - END IF; - END IF; - END IF; - END IF; - END IF; - END LOOP; - END IF; - END IF; - END IF; - END IF; - END IF; - END IF; - END IF; - END IF; -END -$$; - -CREATE OR REPLACE FUNCTION get_partition_by_max_ttl_date(IN partition_type varchar, IN date timestamp, OUT partition varchar) AS -$$ -BEGIN - CASE - WHEN partition_type = 'DAYS' THEN - partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM') || '_' || to_char(date, 'dd'); - WHEN partition_type = 'MONTHS' THEN - partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM'); - WHEN partition_type = 'YEARS' THEN - partition := 'ts_kv_' || to_char(date, 'yyyy'); - ELSE - partition := NULL; - END CASE; - IF partition IS NOT NULL THEN - IF NOT EXISTS(SELECT - FROM pg_tables - WHERE schemaname = 'public' - AND tablename = partition) THEN - partition := NULL; - RAISE NOTICE 'Failed to found partition by ttl'; - END IF; - END IF; -END; -$$ LANGUAGE plpgsql; diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql b/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql deleted file mode 100644 index 0883e46982..0000000000 --- a/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql +++ /dev/null @@ -1,359 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - --- call create_partition_ts_kv_table(); - -CREATE OR REPLACE PROCEDURE create_partition_ts_kv_table() - LANGUAGE plpgsql AS -$$ - -BEGIN - ALTER TABLE ts_kv - DROP CONSTRAINT IF EXISTS ts_kv_unq_key; - ALTER TABLE ts_kv - DROP CONSTRAINT IF EXISTS ts_kv_pkey; - ALTER TABLE ts_kv - ADD CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_type, entity_id, key, ts); - ALTER TABLE ts_kv - RENAME TO ts_kv_old; - ALTER TABLE ts_kv_old - RENAME CONSTRAINT ts_kv_pkey TO ts_kv_pkey_old; - CREATE TABLE IF NOT EXISTS ts_kv - ( - LIKE ts_kv_old - ) - PARTITION BY RANGE (ts); - ALTER TABLE ts_kv - DROP COLUMN entity_type; - ALTER TABLE ts_kv - ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; - ALTER TABLE ts_kv - ALTER COLUMN key TYPE integer USING key::integer; - ALTER TABLE ts_kv - ADD CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts); - CREATE TABLE IF NOT EXISTS ts_kv_indefinite PARTITION OF ts_kv DEFAULT; -END; -$$; - --- call create_new_ts_kv_latest_table(); - -CREATE OR REPLACE PROCEDURE create_new_ts_kv_latest_table() - LANGUAGE plpgsql AS -$$ - -BEGIN - IF NOT EXISTS(SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = 'ts_kv_latest_old') THEN - ALTER TABLE ts_kv_latest - DROP CONSTRAINT IF EXISTS ts_kv_latest_unq_key; - ALTER TABLE ts_kv_latest - DROP CONSTRAINT IF EXISTS ts_kv_latest_pkey; - ALTER TABLE ts_kv_latest - ADD CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_type, entity_id, key); - ALTER TABLE ts_kv_latest - RENAME TO ts_kv_latest_old; - ALTER TABLE ts_kv_latest_old - RENAME CONSTRAINT ts_kv_latest_pkey TO ts_kv_latest_pkey_old; - CREATE TABLE IF NOT EXISTS ts_kv_latest - ( - LIKE ts_kv_latest_old - ); - ALTER TABLE ts_kv_latest - DROP COLUMN entity_type; - ALTER TABLE ts_kv_latest - ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; - ALTER TABLE ts_kv_latest - ALTER COLUMN key TYPE integer USING key::integer; - ALTER TABLE ts_kv_latest - ADD CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key); - ELSE - RAISE NOTICE 'ts_kv_latest_old table already exists!'; - IF NOT EXISTS(SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = 'ts_kv_latest') THEN - CREATE TABLE IF NOT EXISTS ts_kv_latest - ( - entity_id uuid NOT NULL, - key int NOT NULL, - ts bigint NOT NULL, - bool_v boolean, - str_v varchar(10000000), - long_v bigint, - dbl_v double precision, - json_v json, - CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) - ); - END IF; - END IF; -END; -$$; - -CREATE OR REPLACE FUNCTION get_partitions_data(IN partition_type varchar) - RETURNS - TABLE - ( - partition_date text, - from_ts bigint, - to_ts bigint - ) -AS -$$ -BEGIN - CASE - WHEN partition_type = 'DAYS' THEN - RETURN QUERY SELECT day_date.day AS partition_date, - (extract(epoch from (day_date.day)::timestamp) * 1000)::bigint AS from_ts, - (extract(epoch from (day_date.day::date + INTERVAL '1 DAY')::timestamp) * - 1000)::bigint AS to_ts - FROM (SELECT DISTINCT TO_CHAR(TO_TIMESTAMP(ts / 1000), 'YYYY_MM_DD') AS day - FROM ts_kv_old) AS day_date; - WHEN partition_type = 'MONTHS' THEN - RETURN QUERY SELECT SUBSTRING(month_date.first_date, 1, 7) AS partition_date, - (extract(epoch from (month_date.first_date)::timestamp) * 1000)::bigint AS from_ts, - (extract(epoch from (month_date.first_date::date + INTERVAL '1 MONTH')::timestamp) * - 1000)::bigint AS to_ts - FROM (SELECT DISTINCT TO_CHAR(TO_TIMESTAMP(ts / 1000), 'YYYY_MM_01') AS first_date - FROM ts_kv_old) AS month_date; - WHEN partition_type = 'YEARS' THEN - RETURN QUERY SELECT SUBSTRING(year_date.year, 1, 4) AS partition_date, - (extract(epoch from (year_date.year)::timestamp) * 1000)::bigint AS from_ts, - (extract(epoch from (year_date.year::date + INTERVAL '1 YEAR')::timestamp) * - 1000)::bigint AS to_ts - FROM (SELECT DISTINCT TO_CHAR(TO_TIMESTAMP(ts / 1000), 'YYYY_01_01') AS year - FROM ts_kv_old) AS year_date; - ELSE - RAISE EXCEPTION 'Failed to parse partitioning property: % !', partition_type; - END CASE; -END; -$$ LANGUAGE plpgsql; - --- call create_partitions(); - -CREATE OR REPLACE PROCEDURE create_partitions(IN partition_type varchar) - LANGUAGE plpgsql AS -$$ - -DECLARE - partition_date varchar; - from_ts bigint; - to_ts bigint; - partitions_cursor CURSOR FOR SELECT * - FROM get_partitions_data(partition_type); -BEGIN - OPEN partitions_cursor; - LOOP - FETCH partitions_cursor INTO partition_date, from_ts, to_ts; - EXIT WHEN NOT FOUND; - EXECUTE 'CREATE TABLE IF NOT EXISTS ts_kv_' || partition_date || - ' PARTITION OF ts_kv FOR VALUES FROM (' || from_ts || - ') TO (' || to_ts || ');'; - RAISE NOTICE 'A partition % has been created!',CONCAT('ts_kv_', partition_date); - END LOOP; - - CLOSE partitions_cursor; -END; -$$; - --- call create_ts_kv_dictionary_table(); - -CREATE OR REPLACE PROCEDURE create_ts_kv_dictionary_table() - LANGUAGE plpgsql AS -$$ - -BEGIN - CREATE TABLE IF NOT EXISTS ts_kv_dictionary - ( - key varchar(255) NOT NULL, - key_id serial UNIQUE, - CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) - ); -END; -$$; - --- call insert_into_dictionary(); - -CREATE OR REPLACE PROCEDURE insert_into_dictionary() - LANGUAGE plpgsql AS -$$ - -DECLARE - insert_record RECORD; - key_cursor CURSOR FOR SELECT DISTINCT key - FROM ts_kv_old - ORDER BY key; -BEGIN - OPEN key_cursor; - LOOP - FETCH key_cursor INTO insert_record; - EXIT WHEN NOT FOUND; - IF NOT EXISTS(SELECT key FROM ts_kv_dictionary WHERE key = insert_record.key) THEN - INSERT INTO ts_kv_dictionary(key) VALUES (insert_record.key); - RAISE NOTICE 'Key: % has been inserted into the dictionary!',insert_record.key; - ELSE - RAISE NOTICE 'Key: % already exists in the dictionary!',insert_record.key; - END IF; - END LOOP; - CLOSE key_cursor; -END; -$$; - -CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS -$$ -BEGIN - uuid_id := substring(entity_id, 8, 8) || '-' || substring(entity_id, 4, 4) || '-1' || substring(entity_id, 1, 3) || - '-' || substring(entity_id, 16, 4) || '-' || substring(entity_id, 20, 12); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE PROCEDURE insert_into_ts_kv(IN path_to_file varchar) - LANGUAGE plpgsql AS -$$ -BEGIN - EXECUTE format('COPY (SELECT to_uuid(entity_id) AS entity_id, - ts_kv_records.key AS key, - ts_kv_records.ts AS ts, - ts_kv_records.bool_v AS bool_v, - ts_kv_records.str_v AS str_v, - ts_kv_records.long_v AS long_v, - ts_kv_records.dbl_v AS dbl_v - FROM (SELECT entity_id AS entity_id, - key_id AS key, - ts, - bool_v, - str_v, - long_v, - dbl_v - FROM ts_kv_old - INNER JOIN ts_kv_dictionary ON (ts_kv_old.key = ts_kv_dictionary.key)) AS ts_kv_records) TO %L;', - path_to_file); - EXECUTE format('COPY ts_kv FROM %L', path_to_file); -END -$$; - --- call insert_into_ts_kv_latest(); - -CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest(IN path_to_file varchar) - LANGUAGE plpgsql AS -$$ -BEGIN - EXECUTE format('COPY (SELECT to_uuid(entity_id) AS entity_id, - ts_kv_latest_records.key AS key, - ts_kv_latest_records.ts AS ts, - ts_kv_latest_records.bool_v AS bool_v, - ts_kv_latest_records.str_v AS str_v, - ts_kv_latest_records.long_v AS long_v, - ts_kv_latest_records.dbl_v AS dbl_v - FROM (SELECT entity_id AS entity_id, - key_id AS key, - ts, - bool_v, - str_v, - long_v, - dbl_v - FROM ts_kv_latest_old - INNER JOIN ts_kv_dictionary ON (ts_kv_latest_old.key = ts_kv_dictionary.key)) AS ts_kv_latest_records) TO %L;', - path_to_file); - EXECUTE format('COPY ts_kv_latest FROM %L', path_to_file); -END; -$$; - --- call insert_into_ts_kv_cursor(); - -CREATE OR REPLACE PROCEDURE insert_into_ts_kv_cursor() - LANGUAGE plpgsql AS -$$ -DECLARE - insert_size CONSTANT integer := 10000; - insert_counter integer DEFAULT 0; - insert_record RECORD; - insert_cursor CURSOR FOR SELECT to_uuid(entity_id) AS entity_id, - ts_kv_records.key AS key, - ts_kv_records.ts AS ts, - ts_kv_records.bool_v AS bool_v, - ts_kv_records.str_v AS str_v, - ts_kv_records.long_v AS long_v, - ts_kv_records.dbl_v AS dbl_v - FROM (SELECT entity_id AS entity_id, - key_id AS key, - ts, - bool_v, - str_v, - long_v, - dbl_v - FROM ts_kv_old - INNER JOIN ts_kv_dictionary ON (ts_kv_old.key = ts_kv_dictionary.key)) AS ts_kv_records; -BEGIN - OPEN insert_cursor; - LOOP - insert_counter := insert_counter + 1; - FETCH insert_cursor INTO insert_record; - IF NOT FOUND THEN - RAISE NOTICE '% records have been inserted into the partitioned ts_kv!',insert_counter - 1; - EXIT; - END IF; - INSERT INTO ts_kv(entity_id, key, ts, bool_v, str_v, long_v, dbl_v) - VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, - insert_record.long_v, insert_record.dbl_v); - IF MOD(insert_counter, insert_size) = 0 THEN - RAISE NOTICE '% records have been inserted into the partitioned ts_kv!',insert_counter; - END IF; - END LOOP; - CLOSE insert_cursor; -END; -$$; - --- call insert_into_ts_kv_latest_cursor(); - -CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest_cursor() - LANGUAGE plpgsql AS -$$ -DECLARE - insert_size CONSTANT integer := 10000; - insert_counter integer DEFAULT 0; - insert_record RECORD; - insert_cursor CURSOR FOR SELECT to_uuid(entity_id) AS entity_id, - ts_kv_latest_records.key AS key, - ts_kv_latest_records.ts AS ts, - ts_kv_latest_records.bool_v AS bool_v, - ts_kv_latest_records.str_v AS str_v, - ts_kv_latest_records.long_v AS long_v, - ts_kv_latest_records.dbl_v AS dbl_v - FROM (SELECT entity_id AS entity_id, - key_id AS key, - ts, - bool_v, - str_v, - long_v, - dbl_v - FROM ts_kv_latest_old - INNER JOIN ts_kv_dictionary ON (ts_kv_latest_old.key = ts_kv_dictionary.key)) AS ts_kv_latest_records; -BEGIN - OPEN insert_cursor; - LOOP - insert_counter := insert_counter + 1; - FETCH insert_cursor INTO insert_record; - IF NOT FOUND THEN - RAISE NOTICE '% records have been inserted into the ts_kv_latest!',insert_counter - 1; - EXIT; - END IF; - INSERT INTO ts_kv_latest(entity_id, key, ts, bool_v, str_v, long_v, dbl_v) - VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, - insert_record.long_v, insert_record.dbl_v); - IF MOD(insert_counter, insert_size) = 0 THEN - RAISE NOTICE '% records have been inserted into the ts_kv_latest!',insert_counter; - END IF; - END LOOP; - CLOSE insert_cursor; -END; -$$; - diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql b/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql deleted file mode 100644 index 3e86854a00..0000000000 --- a/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql +++ /dev/null @@ -1,208 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - --- call create_new_ts_kv_table(); - -CREATE OR REPLACE PROCEDURE create_new_ts_kv_table() LANGUAGE plpgsql AS $$ - -BEGIN - ALTER TABLE tenant_ts_kv - RENAME TO tenant_ts_kv_old; - CREATE TABLE IF NOT EXISTS ts_kv - ( - LIKE tenant_ts_kv_old - ); - ALTER TABLE ts_kv ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; - ALTER TABLE ts_kv ALTER COLUMN key TYPE integer USING key::integer; - ALTER INDEX ts_kv_pkey RENAME TO tenant_ts_kv_pkey_old; - ALTER INDEX idx_tenant_ts_kv RENAME TO idx_tenant_ts_kv_old; - ALTER INDEX tenant_ts_kv_ts_idx RENAME TO tenant_ts_kv_ts_idx_old; - ALTER TABLE ts_kv ADD CONSTRAINT ts_kv_pkey PRIMARY KEY(entity_id, key, ts); --- CREATE INDEX IF NOT EXISTS ts_kv_ts_idx ON ts_kv(ts DESC); - ALTER TABLE ts_kv DROP COLUMN IF EXISTS tenant_id; -END; -$$; - - --- call create_ts_kv_latest_table(); - -CREATE OR REPLACE PROCEDURE create_ts_kv_latest_table() LANGUAGE plpgsql AS $$ - -BEGIN - CREATE TABLE IF NOT EXISTS ts_kv_latest - ( - entity_id uuid NOT NULL, - key int NOT NULL, - ts bigint NOT NULL, - bool_v boolean, - str_v varchar(10000000), - long_v bigint, - dbl_v double precision, - CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) - ); -END; -$$; - - --- call create_ts_kv_dictionary_table(); - -CREATE OR REPLACE PROCEDURE create_ts_kv_dictionary_table() LANGUAGE plpgsql AS $$ - -BEGIN - CREATE TABLE IF NOT EXISTS ts_kv_dictionary - ( - key varchar(255) NOT NULL, - key_id serial UNIQUE, - CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) - ); -END; -$$; - --- call insert_into_dictionary(); - -CREATE OR REPLACE PROCEDURE insert_into_dictionary() LANGUAGE plpgsql AS $$ - -DECLARE - insert_record RECORD; - key_cursor CURSOR FOR SELECT DISTINCT key - FROM tenant_ts_kv_old - ORDER BY key; -BEGIN - OPEN key_cursor; - LOOP - FETCH key_cursor INTO insert_record; - EXIT WHEN NOT FOUND; - IF NOT EXISTS(SELECT key FROM ts_kv_dictionary WHERE key = insert_record.key) THEN - INSERT INTO ts_kv_dictionary(key) VALUES (insert_record.key); - RAISE NOTICE 'Key: % has been inserted into the dictionary!',insert_record.key; - ELSE - RAISE NOTICE 'Key: % already exists in the dictionary!',insert_record.key; - END IF; - END LOOP; - CLOSE key_cursor; -END; -$$; - -CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS -$$ -BEGIN - uuid_id := substring(entity_id, 8, 8) || '-' || substring(entity_id, 4, 4) || '-1' || substring(entity_id, 1, 3) || - '-' || substring(entity_id, 16, 4) || '-' || substring(entity_id, 20, 12); -END; -$$ LANGUAGE plpgsql; - --- call insert_into_ts_kv(); - -CREATE OR REPLACE PROCEDURE insert_into_ts_kv(IN path_to_file varchar) LANGUAGE plpgsql AS $$ -BEGIN - - EXECUTE format ('COPY (SELECT to_uuid(entity_id) AS entity_id, - new_ts_kv_records.key AS key, - new_ts_kv_records.ts AS ts, - new_ts_kv_records.bool_v AS bool_v, - new_ts_kv_records.str_v AS str_v, - new_ts_kv_records.long_v AS long_v, - new_ts_kv_records.dbl_v AS dbl_v - FROM (SELECT entity_id AS entity_id, - key_id AS key, - ts, - bool_v, - str_v, - long_v, - dbl_v - FROM tenant_ts_kv_old - INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS new_ts_kv_records) TO %L;', path_to_file); - EXECUTE format ('COPY ts_kv FROM %L', path_to_file); -END; -$$; - --- call insert_into_ts_kv_latest(); - -CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest() LANGUAGE plpgsql AS $$ - -DECLARE - insert_size CONSTANT integer := 10000; - insert_counter integer DEFAULT 0; - latest_record RECORD; - insert_record RECORD; - insert_cursor CURSOR FOR SELECT - latest_records.key AS key, - latest_records.entity_id AS entity_id, - latest_records.ts AS ts - FROM (SELECT DISTINCT key AS key, entity_id AS entity_id, MAX(ts) AS ts FROM ts_kv GROUP BY key, entity_id) AS latest_records; -BEGIN - OPEN insert_cursor; - LOOP - insert_counter := insert_counter + 1; - FETCH insert_cursor INTO latest_record; - IF NOT FOUND THEN - RAISE NOTICE '% records have been inserted into the ts_kv_latest table!',insert_counter - 1; - EXIT; - END IF; - SELECT entity_id AS entity_id, key AS key, ts AS ts, bool_v AS bool_v, str_v AS str_v, long_v AS long_v, dbl_v AS dbl_v INTO insert_record FROM ts_kv WHERE entity_id = latest_record.entity_id AND key = latest_record.key AND ts = latest_record.ts; - INSERT INTO ts_kv_latest(entity_id, key, ts, bool_v, str_v, long_v, dbl_v) - VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, insert_record.long_v, insert_record.dbl_v); - IF MOD(insert_counter, insert_size) = 0 THEN - RAISE NOTICE '% records have been inserted into the ts_kv_latest table!',insert_counter; - END IF; - END LOOP; - CLOSE insert_cursor; -END; -$$; - --- call insert_into_ts_kv_cursor(); - -CREATE OR REPLACE PROCEDURE insert_into_ts_kv_cursor() LANGUAGE plpgsql AS $$ - -DECLARE - insert_size CONSTANT integer := 10000; - insert_counter integer DEFAULT 0; - insert_record RECORD; - insert_cursor CURSOR FOR SELECT to_uuid(entity_id) AS entity_id, - new_ts_kv_records.key AS key, - new_ts_kv_records.ts AS ts, - new_ts_kv_records.bool_v AS bool_v, - new_ts_kv_records.str_v AS str_v, - new_ts_kv_records.long_v AS long_v, - new_ts_kv_records.dbl_v AS dbl_v - FROM (SELECT entity_id AS entity_id, - key_id AS key, - ts, - bool_v, - str_v, - long_v, - dbl_v - FROM tenant_ts_kv_old - INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS new_ts_kv_records; -BEGIN - OPEN insert_cursor; - LOOP - insert_counter := insert_counter + 1; - FETCH insert_cursor INTO insert_record; - IF NOT FOUND THEN - RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter - 1; - EXIT; - END IF; - INSERT INTO ts_kv(entity_id, key, ts, bool_v, str_v, long_v, dbl_v) - VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, - insert_record.long_v, insert_record.dbl_v); - IF MOD(insert_counter, insert_size) = 0 THEN - RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter; - END IF; - END LOOP; - CLOSE insert_cursor; -END; -$$; \ No newline at end of file diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_ttl.sql b/application/src/main/data/upgrade/2.4.3/schema_update_ttl.sql deleted file mode 100644 index 53ab8351e4..0000000000 --- a/application/src/main/data/upgrade/2.4.3/schema_update_ttl.sql +++ /dev/null @@ -1,150 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS -$$ -BEGIN - uuid_id := substring(entity_id, 8, 8) || '-' || substring(entity_id, 4, 4) || '-1' || substring(entity_id, 1, 3) || - '-' || substring(entity_id, 16, 4) || '-' || substring(entity_id, 20, 12); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION delete_device_records_from_ts_kv(tenant_id uuid, customer_id uuid, ttl bigint, - OUT deleted bigint) AS -$$ -BEGIN - EXECUTE format( - 'WITH deleted AS (DELETE FROM ts_kv WHERE entity_id IN (SELECT device.id as entity_id FROM device WHERE tenant_id = %L and customer_id = %L) AND ts < %L::bigint RETURNING *) SELECT count(*) FROM deleted', - tenant_id, customer_id, ttl) into deleted; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION delete_asset_records_from_ts_kv(tenant_id uuid, customer_id uuid, ttl bigint, - OUT deleted bigint) AS -$$ -BEGIN - EXECUTE format( - 'WITH deleted AS (DELETE FROM ts_kv WHERE entity_id IN (SELECT asset.id as entity_id FROM asset WHERE tenant_id = %L and customer_id = %L) AND ts < %L::bigint RETURNING *) SELECT count(*) FROM deleted', - tenant_id, customer_id, ttl) into deleted; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION delete_customer_records_from_ts_kv(tenant_id uuid, customer_id uuid, ttl bigint, - OUT deleted bigint) AS -$$ -BEGIN - EXECUTE format( - 'WITH deleted AS (DELETE FROM ts_kv WHERE entity_id IN (SELECT customer.id as entity_id FROM customer WHERE tenant_id = %L and id = %L) AND ts < %L::bigint RETURNING *) SELECT count(*) FROM deleted', - tenant_id, customer_id, ttl) into deleted; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid, - IN system_ttl bigint, INOUT deleted bigint) - LANGUAGE plpgsql AS -$$ -DECLARE - tenant_cursor CURSOR FOR select tenant.id as tenant_id - from tenant; - tenant_id_record uuid; - customer_id_record uuid; - tenant_ttl bigint; - customer_ttl bigint; - deleted_for_entities bigint; - tenant_ttl_ts bigint; - customer_ttl_ts bigint; -BEGIN - OPEN tenant_cursor; - FETCH tenant_cursor INTO tenant_id_record; - WHILE FOUND - LOOP - EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', - tenant_id_record, 'TTL') INTO tenant_ttl; - if tenant_ttl IS NULL THEN - tenant_ttl := system_ttl; - END IF; - IF tenant_ttl > 0 THEN - tenant_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - tenant_ttl::bigint * 1000)::bigint; - deleted_for_entities := delete_device_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for devices where tenant_id = %', deleted_for_entities, tenant_id_record; - deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for assets where tenant_id = %', deleted_for_entities, tenant_id_record; - END IF; - FOR customer_id_record IN - SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record - LOOP - EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', - customer_id_record, 'TTL') INTO customer_ttl; - IF customer_ttl IS NULL THEN - customer_ttl_ts := tenant_ttl_ts; - ELSE - IF customer_ttl > 0 THEN - customer_ttl_ts := - (EXTRACT(EPOCH FROM current_timestamp) * 1000 - - customer_ttl::bigint * 1000)::bigint; - END IF; - END IF; - IF customer_ttl_ts IS NOT NULL AND customer_ttl_ts > 0 THEN - deleted_for_entities := - delete_customer_records_from_ts_kv(tenant_id_record, customer_id_record, - customer_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for customer with id = % where tenant_id = %', deleted_for_entities, customer_id_record, tenant_id_record; - deleted_for_entities := - delete_device_records_from_ts_kv(tenant_id_record, customer_id_record, - customer_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for devices where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; - deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, - customer_id_record, - customer_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for assets where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; - END IF; - END LOOP; - FETCH tenant_cursor INTO tenant_id_record; - END LOOP; -END -$$; - -CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) - LANGUAGE plpgsql AS -$$ -DECLARE - ttl_ts bigint; - debug_ttl_ts bigint; - ttl_deleted_count bigint DEFAULT 0; - debug_ttl_deleted_count bigint DEFAULT 0; -BEGIN - IF ttl > 0 THEN - ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - ttl::bigint * 1000)::bigint; - EXECUTE format( - 'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type != %L::varchar AND event_type != %L::varchar) RETURNING *) SELECT count(*) FROM deleted', ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count; - END IF; - IF debug_ttl > 0 THEN - debug_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - debug_ttl::bigint * 1000)::bigint; - EXECUTE format( - 'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type = %L::varchar OR event_type = %L::varchar) RETURNING *) SELECT count(*) FROM deleted', debug_ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count; - END IF; - RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count; - RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count; - deleted := ttl_deleted_count + debug_ttl_deleted_count; -END -$$; diff --git a/application/src/main/data/upgrade/3.0.1/schema_update_to_uuid.sql b/application/src/main/data/upgrade/3.0.1/schema_update_to_uuid.sql deleted file mode 100644 index d4697026d5..0000000000 --- a/application/src/main/data/upgrade/3.0.1/schema_update_to_uuid.sql +++ /dev/null @@ -1,878 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS -$$ -BEGIN - uuid_id := substring(entity_id, 8, 8) || '-' || substring(entity_id, 4, 4) || '-1' || substring(entity_id, 1, 3) || - '-' || substring(entity_id, 16, 4) || '-' || substring(entity_id, 20, 12); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION extract_ts(uuid UUID) RETURNS BIGINT AS -$$ -DECLARE - bytes bytea; -BEGIN - bytes := uuid_send(uuid); - RETURN - ( - ( - (get_byte(bytes, 0)::bigint << 24) | - (get_byte(bytes, 1)::bigint << 16) | - (get_byte(bytes, 2)::bigint << 8) | - (get_byte(bytes, 3)::bigint << 0) - ) + ( - ((get_byte(bytes, 4)::bigint << 8 | - get_byte(bytes, 5)::bigint)) << 32 - ) + ( - (((get_byte(bytes, 6)::bigint & 15) << 8 | get_byte(bytes, 7)::bigint) & 4095) << 48 - ) - 122192928000000000 - ) / 10000::double precision; -END -$$ LANGUAGE plpgsql - IMMUTABLE - PARALLEL SAFE - RETURNS NULL ON NULL INPUT; - - -CREATE OR REPLACE FUNCTION column_type_to_uuid(table_name varchar, column_name varchar) RETURNS VOID - LANGUAGE plpgsql AS -$$ -BEGIN - execute format('ALTER TABLE %s RENAME COLUMN %s TO old_%s;', table_name, column_name, column_name); - execute format('ALTER TABLE %s ADD COLUMN %s UUID;', table_name, column_name); - execute format('UPDATE %s SET %s = to_uuid(old_%s) WHERE old_%s is not null;', table_name, column_name, column_name, column_name); - execute format('ALTER TABLE %s DROP COLUMN old_%s;', table_name, column_name); -END; -$$; - -CREATE OR REPLACE FUNCTION get_column_type(table_name varchar, column_name varchar, OUT data_type varchar) RETURNS varchar - LANGUAGE plpgsql AS -$$ -BEGIN - execute (format('SELECT data_type from information_schema.columns where table_name = %L and column_name = %L', - table_name, column_name)) INTO data_type; -END; -$$; - - -CREATE OR REPLACE PROCEDURE drop_all_idx() - LANGUAGE plpgsql AS -$$ -BEGIN - DROP INDEX IF EXISTS idx_alarm_originator_alarm_type; - DROP INDEX IF EXISTS idx_alarm_originator_created_time; - DROP INDEX IF EXISTS idx_alarm_tenant_created_time; - DROP INDEX IF EXISTS idx_event_type_entity_id; - DROP INDEX IF EXISTS idx_relation_to_id; - DROP INDEX IF EXISTS idx_relation_from_id; - DROP INDEX IF EXISTS idx_device_customer_id; - DROP INDEX IF EXISTS idx_device_customer_id_and_type; - DROP INDEX IF EXISTS idx_device_type; - DROP INDEX IF EXISTS idx_asset_customer_id; - DROP INDEX IF EXISTS idx_asset_customer_id_and_type; - DROP INDEX IF EXISTS idx_asset_type; - DROP INDEX IF EXISTS idx_attribute_kv_by_key_and_last_update_ts; -END; -$$; - -CREATE OR REPLACE PROCEDURE create_all_idx() - LANGUAGE plpgsql AS -$$ -BEGIN - CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC); - CREATE INDEX IF NOT EXISTS idx_alarm_originator_created_time ON alarm(originator_id, created_time DESC); - CREATE INDEX IF NOT EXISTS idx_alarm_tenant_created_time ON alarm(tenant_id, created_time DESC); - CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id); - CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id); - CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id); - CREATE INDEX IF NOT EXISTS idx_device_customer_id ON device(tenant_id, customer_id); - CREATE INDEX IF NOT EXISTS idx_device_customer_id_and_type ON device(tenant_id, customer_id, type); - CREATE INDEX IF NOT EXISTS idx_device_type ON device(tenant_id, type); - CREATE INDEX IF NOT EXISTS idx_asset_customer_id ON asset(tenant_id, customer_id); - CREATE INDEX IF NOT EXISTS idx_asset_customer_id_and_type ON asset(tenant_id, customer_id, type); - CREATE INDEX IF NOT EXISTS idx_asset_type ON asset(tenant_id, type); - CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribute_kv(entity_id, attribute_key, last_update_ts desc); -END; -$$; - - --- admin_settings -CREATE OR REPLACE PROCEDURE update_admin_settings() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'admin_settings'; - column_id varchar := 'id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE admin_settings DROP CONSTRAINT admin_settings_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE admin_settings ADD CONSTRAINT admin_settings_pkey PRIMARY KEY (id); - ALTER TABLE admin_settings ADD COLUMN created_time BIGINT; - UPDATE admin_settings SET created_time = extract_ts(id) WHERE id is not null; - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; -END; -$$; - - --- alarm -CREATE OR REPLACE PROCEDURE update_alarm() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'alarm'; - column_id varchar := 'id'; - column_originator_id varchar := 'originator_id'; - column_tenant_id varchar := 'tenant_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE alarm DROP CONSTRAINT alarm_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE alarm ADD COLUMN created_time BIGINT; - UPDATE alarm SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE alarm ADD CONSTRAINT alarm_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_originator_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_originator_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_originator_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_originator_id; - END IF; - - data_type := get_column_type(table_name, column_tenant_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_tenant_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; - END IF; -END; -$$; - - --- asset -CREATE OR REPLACE PROCEDURE update_asset() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'asset'; - column_id varchar := 'id'; - column_customer_id varchar := 'customer_id'; - column_tenant_id varchar := 'tenant_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE asset DROP CONSTRAINT asset_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE asset ADD COLUMN created_time BIGINT; - UPDATE asset SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE asset ADD CONSTRAINT asset_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_customer_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_customer_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; - END IF; - - data_type := get_column_type(table_name, column_tenant_id); - IF data_type = 'character varying' THEN - ALTER TABLE asset DROP CONSTRAINT asset_name_unq_key; - PERFORM column_type_to_uuid(table_name, column_tenant_id); - ALTER TABLE asset ADD CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name); - RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; - END IF; - END; -$$; - --- attribute_kv -CREATE OR REPLACE PROCEDURE update_attribute_kv() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'attribute_kv'; - column_entity_id varchar := 'entity_id'; -BEGIN - data_type := get_column_type(table_name, column_entity_id); - IF data_type = 'character varying' THEN - ALTER TABLE attribute_kv DROP CONSTRAINT attribute_kv_pkey; - PERFORM column_type_to_uuid(table_name, column_entity_id); - ALTER TABLE attribute_kv ADD CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key); - RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id; - END IF; -END; -$$; - --- audit_log -CREATE OR REPLACE PROCEDURE update_audit_log() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'audit_log'; - column_id varchar := 'id'; - column_customer_id varchar := 'customer_id'; - column_tenant_id varchar := 'tenant_id'; - column_entity_id varchar := 'entity_id'; - column_user_id varchar := 'user_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE audit_log DROP CONSTRAINT audit_log_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE audit_log ADD COLUMN created_time BIGINT; - UPDATE audit_log SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE audit_log ADD CONSTRAINT audit_log_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_customer_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_customer_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; - END IF; - - data_type := get_column_type(table_name, column_tenant_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_tenant_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; - END IF; - - data_type := get_column_type(table_name, column_entity_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_entity_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id; - END IF; - - data_type := get_column_type(table_name, column_user_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_user_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_user_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_user_id; - END IF; -END; -$$; - - --- component_descriptor -CREATE OR REPLACE PROCEDURE update_component_descriptor() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'component_descriptor'; - column_id varchar := 'id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE component_descriptor DROP CONSTRAINT component_descriptor_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE component_descriptor ADD CONSTRAINT component_descriptor_pkey PRIMARY KEY (id); - ALTER TABLE component_descriptor ADD COLUMN created_time BIGINT; - UPDATE component_descriptor SET created_time = extract_ts(id) WHERE id is not null; - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; -END; -$$; - --- customer -CREATE OR REPLACE PROCEDURE update_customer() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'customer'; - column_id varchar := 'id'; - column_tenant_id varchar := 'tenant_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE customer DROP CONSTRAINT customer_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE customer ADD CONSTRAINT customer_pkey PRIMARY KEY (id); - ALTER TABLE customer ADD COLUMN created_time BIGINT; - UPDATE customer SET created_time = extract_ts(id) WHERE id is not null; - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_tenant_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_tenant_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; - END IF; -END; -$$; - - --- dashboard -CREATE OR REPLACE PROCEDURE update_dashboard() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'dashboard'; - column_id varchar := 'id'; - column_tenant_id varchar := 'tenant_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE dashboard DROP CONSTRAINT dashboard_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE dashboard ADD CONSTRAINT dashboard_pkey PRIMARY KEY (id); - ALTER TABLE dashboard ADD COLUMN created_time BIGINT; - UPDATE dashboard SET created_time = extract_ts(id) WHERE id is not null; - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_tenant_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_tenant_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; - END IF; -END; -$$; - --- device -CREATE OR REPLACE PROCEDURE update_device() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'device'; - column_id varchar := 'id'; - column_customer_id varchar := 'customer_id'; - column_tenant_id varchar := 'tenant_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE device DROP CONSTRAINT device_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE device ADD COLUMN created_time BIGINT; - UPDATE device SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE device ADD CONSTRAINT device_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_customer_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_customer_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; - END IF; - - data_type := get_column_type(table_name, column_tenant_id); - IF data_type = 'character varying' THEN - ALTER TABLE device DROP CONSTRAINT device_name_unq_key; - PERFORM column_type_to_uuid(table_name, column_tenant_id); - ALTER TABLE device ADD CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name); - RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; - END IF; -END; -$$; - - --- device_credentials -CREATE OR REPLACE PROCEDURE update_device_credentials() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'device_credentials'; - column_id varchar := 'id'; - column_device_id varchar := 'device_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE device_credentials DROP CONSTRAINT device_credentials_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE device_credentials ADD COLUMN created_time BIGINT; - UPDATE device_credentials SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE device_credentials ADD CONSTRAINT device_credentials_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_device_id); - IF data_type = 'character varying' THEN - ALTER TABLE device_credentials DROP CONSTRAINT IF EXISTS device_credentials_device_id_unq_key; - PERFORM column_type_to_uuid(table_name, column_device_id); - -- remove duplicate credentials with same device_id - DELETE from device_credentials where id in ( - select dc.id - from ( - SELECT id, device_id, - ROW_NUMBER() OVER ( - PARTITION BY - device_id - ORDER BY - created_time DESC - ) row_num - FROM - device_credentials - ) as dc - WHERE dc.row_num > 1 - ); - ALTER TABLE device_credentials ADD CONSTRAINT device_credentials_device_id_unq_key UNIQUE (device_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_device_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_device_id; - END IF; -END; -$$; - - --- event -CREATE OR REPLACE PROCEDURE update_event() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'event'; - column_id varchar := 'id'; - column_entity_id varchar := 'entity_id'; - column_tenant_id varchar := 'tenant_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE event DROP CONSTRAINT event_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE event ADD COLUMN created_time BIGINT; - UPDATE event SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE event ADD CONSTRAINT event_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - ALTER TABLE event DROP CONSTRAINT event_unq_key; - - data_type := get_column_type(table_name, column_entity_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_entity_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id; - END IF; - - data_type := get_column_type(table_name, column_tenant_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_tenant_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; - END IF; - - ALTER TABLE event ADD CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid); -END; -$$; - - --- relation -CREATE OR REPLACE PROCEDURE update_relation() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'relation'; - column_from_id varchar := 'from_id'; - column_to_id varchar := 'to_id'; -BEGIN - ALTER TABLE relation DROP CONSTRAINT relation_pkey; - - data_type := get_column_type(table_name, column_from_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_from_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_from_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_from_id; - END IF; - - data_type := get_column_type(table_name, column_to_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_to_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_to_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_to_id; - END IF; - - ALTER TABLE relation ADD CONSTRAINT relation_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type); -END; -$$; - - --- tb_user -CREATE OR REPLACE PROCEDURE update_tb_user() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'tb_user'; - column_id varchar := 'id'; - column_customer_id varchar := 'customer_id'; - column_tenant_id varchar := 'tenant_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE tb_user DROP CONSTRAINT tb_user_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE tb_user ADD COLUMN created_time BIGINT; - UPDATE tb_user SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE tb_user ADD CONSTRAINT tb_user_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_customer_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_customer_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; - END IF; - - data_type := get_column_type(table_name, column_tenant_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_tenant_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; - END IF; -END; -$$; - - --- tenant -CREATE OR REPLACE PROCEDURE update_tenant() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'tenant'; - column_id varchar := 'id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE tenant DROP CONSTRAINT tenant_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE tenant ADD COLUMN created_time BIGINT; - UPDATE tenant SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE tenant ADD CONSTRAINT tenant_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; -END; -$$; - - --- user_credentials -CREATE OR REPLACE PROCEDURE update_user_credentials() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'user_credentials'; - column_id varchar := 'id'; - column_user_id varchar := 'user_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE user_credentials DROP CONSTRAINT user_credentials_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE user_credentials ADD COLUMN created_time BIGINT; - UPDATE user_credentials SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE user_credentials ADD CONSTRAINT user_credentials_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_user_id); - IF data_type = 'character varying' THEN - ALTER TABLE user_credentials DROP CONSTRAINT user_credentials_user_id_key; - ALTER TABLE user_credentials RENAME COLUMN user_id TO old_user_id; - ALTER TABLE user_credentials ADD COLUMN user_id UUID UNIQUE; - UPDATE user_credentials SET user_id = to_uuid(old_user_id) WHERE old_user_id is not null; - ALTER TABLE user_credentials DROP COLUMN old_user_id; - RAISE NOTICE 'Table % column % updated!', table_name, column_user_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_user_id; - END IF; -END; -$$; - - --- widget_type -CREATE OR REPLACE PROCEDURE update_widget_type() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'widget_type'; - column_id varchar := 'id'; - column_tenant_id varchar := 'tenant_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE widget_type DROP CONSTRAINT widget_type_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE widget_type ADD COLUMN created_time BIGINT; - UPDATE widget_type SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE widget_type ADD CONSTRAINT widget_type_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_tenant_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_tenant_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; - END IF; -END; -$$; - - --- widgets_bundle -CREATE OR REPLACE PROCEDURE update_widgets_bundle() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'widgets_bundle'; - column_id varchar := 'id'; - column_tenant_id varchar := 'tenant_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE widgets_bundle DROP CONSTRAINT widgets_bundle_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE widgets_bundle ADD COLUMN created_time BIGINT; - UPDATE widgets_bundle SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE widgets_bundle ADD CONSTRAINT widgets_bundle_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_tenant_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_tenant_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; - END IF; -END; -$$; - - --- rule_chain -CREATE OR REPLACE PROCEDURE update_rule_chain() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'rule_chain'; - column_id varchar := 'id'; - column_first_rule_node_id varchar := 'first_rule_node_id'; - column_tenant_id varchar := 'tenant_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE rule_chain DROP CONSTRAINT rule_chain_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE rule_chain ADD COLUMN created_time BIGINT; - UPDATE rule_chain SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE rule_chain ADD CONSTRAINT rule_chain_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_first_rule_node_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_first_rule_node_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_first_rule_node_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_first_rule_node_id; - END IF; - - data_type := get_column_type(table_name, column_tenant_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_tenant_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; - END IF; -END; -$$; - - --- rule_node -CREATE OR REPLACE PROCEDURE update_rule_node() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'rule_node'; - column_id varchar := 'id'; - column_rule_chain_id varchar := 'rule_chain_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE rule_node DROP CONSTRAINT rule_node_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE rule_node ADD COLUMN created_time BIGINT; - UPDATE rule_node SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE rule_node ADD CONSTRAINT rule_node_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_rule_chain_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_rule_chain_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_rule_chain_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_rule_chain_id; - END IF; -END; -$$; - - --- entity_view -CREATE OR REPLACE PROCEDURE update_entity_view() - LANGUAGE plpgsql AS -$$ -DECLARE - data_type varchar; - table_name varchar := 'entity_view'; - column_id varchar := 'id'; - column_entity_id varchar := 'entity_id'; - column_tenant_id varchar := 'tenant_id'; - column_customer_id varchar := 'customer_id'; -BEGIN - data_type := get_column_type(table_name, column_id); - IF data_type = 'character varying' THEN - ALTER TABLE entity_view DROP CONSTRAINT entity_view_pkey; - PERFORM column_type_to_uuid(table_name, column_id); - ALTER TABLE entity_view ADD COLUMN created_time BIGINT; - UPDATE entity_view SET created_time = extract_ts(id) WHERE id is not null; - ALTER TABLE entity_view ADD CONSTRAINT entity_view_pkey PRIMARY KEY (id); - RAISE NOTICE 'Table % column % updated!', table_name, column_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_id; - END IF; - - data_type := get_column_type(table_name, column_entity_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_entity_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id; - END IF; - - data_type := get_column_type(table_name, column_tenant_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_tenant_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; - END IF; - - data_type := get_column_type(table_name, column_customer_id); - IF data_type = 'character varying' THEN - PERFORM column_type_to_uuid(table_name, column_customer_id); - RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; - ELSE - RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; - END IF; -END; -$$; - -CREATE TABLE IF NOT EXISTS ts_kv_latest -( - entity_id uuid NOT NULL, - key int NOT NULL, - ts bigint NOT NULL, - bool_v boolean, - str_v varchar(10000000), - long_v bigint, - dbl_v double precision, - json_v json, - CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) -); - -CREATE TABLE IF NOT EXISTS ts_kv_dictionary -( - key varchar(255) NOT NULL, - key_id serial UNIQUE, - CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) -); diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql deleted file mode 100644 index 13a1529eeb..0000000000 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ /dev/null @@ -1,17 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE INDEX IF NOT EXISTS idx_alarm_tenant_alarm_type_created_time ON alarm(tenant_id, type, created_time DESC); diff --git a/application/src/main/data/upgrade/3.1.1/schema_update_after.sql b/application/src/main/data/upgrade/3.1.1/schema_update_after.sql deleted file mode 100644 index 94dec6351d..0000000000 --- a/application/src/main/data/upgrade/3.1.1/schema_update_after.sql +++ /dev/null @@ -1,28 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -DROP PROCEDURE IF EXISTS update_tenant_profiles; -DROP PROCEDURE IF EXISTS update_device_profiles; - -ALTER TABLE tenant ALTER COLUMN tenant_profile_id SET NOT NULL; -ALTER TABLE tenant DROP CONSTRAINT IF EXISTS fk_tenant_profile; -ALTER TABLE tenant ADD CONSTRAINT fk_tenant_profile FOREIGN KEY (tenant_profile_id) REFERENCES tenant_profile(id); -ALTER TABLE tenant DROP COLUMN IF EXISTS isolated_tb_core; -ALTER TABLE tenant DROP COLUMN IF EXISTS isolated_tb_rule_engine; - -ALTER TABLE device ALTER COLUMN device_profile_id SET NOT NULL; -ALTER TABLE device DROP CONSTRAINT IF EXISTS fk_device_profile; -ALTER TABLE device ADD CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id); diff --git a/application/src/main/data/upgrade/3.1.1/schema_update_before.sql b/application/src/main/data/upgrade/3.1.1/schema_update_before.sql deleted file mode 100644 index 2162341f8a..0000000000 --- a/application/src/main/data/upgrade/3.1.1/schema_update_before.sql +++ /dev/null @@ -1,154 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, - enabled boolean, - created_time bigint NOT NULL, - additional_info varchar, - client_id varchar(255), - client_secret varchar(255), - authorization_uri varchar(255), - token_uri varchar(255), - scope varchar(255), - user_info_uri varchar(255), - user_name_attribute_name varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - login_button_label varchar(255), - login_button_icon varchar(255), - allow_user_creation boolean, - activate_user boolean, - type varchar(31), - basic_email_attribute_key varchar(31), - basic_first_name_attribute_key varchar(31), - basic_last_name_attribute_key varchar(31), - basic_tenant_name_strategy varchar(31), - basic_tenant_name_pattern varchar(255), - basic_customer_name_pattern varchar(255), - basic_default_dashboard_name varchar(255), - basic_always_full_screen boolean, - custom_url varchar(255), - custom_username varchar(255), - custom_password varchar(255), - custom_send_token boolean -); - -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, - created_time bigint NOT NULL, - domain_name varchar(255), - domain_scheme varchar(31), - client_registration_info_id uuid -); - -CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, - created_time bigint NOT NULL, - additional_info varchar, - provider_id varchar(255), - authorization_uri varchar(255), - token_uri varchar(255), - scope varchar(255), - user_info_uri varchar(255), - user_name_attribute_name varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - type varchar(31), - basic_email_attribute_key varchar(31), - basic_first_name_attribute_key varchar(31), - basic_last_name_attribute_key varchar(31), - basic_tenant_name_strategy varchar(31), - basic_tenant_name_pattern varchar(255), - basic_customer_name_pattern varchar(255), - basic_default_dashboard_name varchar(255), - basic_always_full_screen boolean, - comment varchar, - login_button_icon varchar(255), - login_button_label varchar(255), - help_link varchar(255), - CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) -); - -CREATE TABLE IF NOT EXISTS device_profile ( - id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY, - created_time bigint NOT NULL, - name varchar(255), - type varchar(255), - transport_type varchar(255), - provision_type varchar(255), - profile_data jsonb, - description varchar, - search_text varchar(255), - is_default boolean, - tenant_id uuid, - default_rule_chain_id uuid, - default_queue_name varchar(255), - provision_device_key varchar, - CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), - CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), - CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) -); - -CREATE TABLE IF NOT EXISTS tenant_profile ( - id uuid NOT NULL CONSTRAINT tenant_profile_pkey PRIMARY KEY, - created_time bigint NOT NULL, - name varchar(255), - profile_data jsonb, - description varchar, - search_text varchar(255), - is_default boolean, - isolated_tb_core boolean, - isolated_tb_rule_engine boolean, - CONSTRAINT tenant_profile_name_unq_key UNIQUE (name) -); - -CREATE OR REPLACE PROCEDURE update_tenant_profiles() - LANGUAGE plpgsql AS -$$ -BEGIN - UPDATE tenant as t SET tenant_profile_id = p.id - FROM - (SELECT id from tenant_profile WHERE isolated_tb_core = false AND isolated_tb_rule_engine = false) as p - WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = false AND t.isolated_tb_rule_engine = false; - - UPDATE tenant as t SET tenant_profile_id = p.id - FROM - (SELECT id from tenant_profile WHERE isolated_tb_core = true AND isolated_tb_rule_engine = false) as p - WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = true AND t.isolated_tb_rule_engine = false; - - UPDATE tenant as t SET tenant_profile_id = p.id - FROM - (SELECT id from tenant_profile WHERE isolated_tb_core = false AND isolated_tb_rule_engine = true) as p - WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = false AND t.isolated_tb_rule_engine = true; - - UPDATE tenant as t SET tenant_profile_id = p.id - FROM - (SELECT id from tenant_profile WHERE isolated_tb_core = true AND isolated_tb_rule_engine = true) as p - WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = true AND t.isolated_tb_rule_engine = true; -END; -$$; - -CREATE OR REPLACE PROCEDURE update_device_profiles() - LANGUAGE plpgsql AS -$$ -BEGIN - UPDATE device as d SET device_profile_id = p.id, device_data = '{"configuration":{"type":"DEFAULT"}, "transportConfiguration":{"type":"DEFAULT"}}' - FROM - (SELECT id, tenant_id, name from device_profile) as p - WHERE d.device_profile_id IS NULL AND p.tenant_id = d.tenant_id AND d.type = p.name; -END; -$$; diff --git a/application/src/main/data/upgrade/3.2.1/schema_update.sql b/application/src/main/data/upgrade/3.2.1/schema_update.sql deleted file mode 100644 index 14a994b4df..0000000000 --- a/application/src/main/data/upgrade/3.2.1/schema_update.sql +++ /dev/null @@ -1,23 +0,0 @@ --- --- Copyright © 2016-2024 The Thingsboard Authors --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -ALTER TABLE widget_type - ADD COLUMN IF NOT EXISTS image varchar (1000000), - ADD COLUMN IF NOT EXISTS description varchar (255); - -ALTER TABLE widgets_bundle - ADD COLUMN IF NOT EXISTS image varchar (1000000), - ADD COLUMN IF NOT EXISTS description varchar (255); diff --git a/application/src/main/data/upgrade/3.2.1/schema_update_ttl.sql b/application/src/main/data/upgrade/3.2.1/schema_update_ttl.sql deleted file mode 100644 index a15a2a3eca..0000000000 --- a/application/src/main/data/upgrade/3.2.1/schema_update_ttl.sql +++ /dev/null @@ -1,87 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid, - IN system_ttl bigint, INOUT deleted bigint) - LANGUAGE plpgsql AS -$$ -DECLARE -tenant_cursor CURSOR FOR select tenant.id as tenant_id - from tenant; - tenant_id_record uuid; - customer_id_record uuid; - tenant_ttl bigint; - customer_ttl bigint; - deleted_for_entities bigint; - tenant_ttl_ts bigint; - customer_ttl_ts bigint; -BEGIN -OPEN tenant_cursor; -FETCH tenant_cursor INTO tenant_id_record; -WHILE FOUND - LOOP - EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', - tenant_id_record, 'TTL') INTO tenant_ttl; - if tenant_ttl IS NULL THEN - tenant_ttl := system_ttl; -END IF; - IF tenant_ttl > 0 THEN - tenant_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - tenant_ttl::bigint * 1000)::bigint; - deleted_for_entities := delete_device_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for devices where tenant_id = %', deleted_for_entities, tenant_id_record; - deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for assets where tenant_id = %', deleted_for_entities, tenant_id_record; -END IF; -FOR customer_id_record IN -SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record - LOOP - EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', - customer_id_record, 'TTL') INTO customer_ttl; -IF customer_ttl IS NULL THEN - customer_ttl_ts := tenant_ttl_ts; -ELSE - IF customer_ttl > 0 THEN - customer_ttl_ts := - (EXTRACT(EPOCH FROM current_timestamp) * 1000 - - customer_ttl::bigint * 1000)::bigint; -END IF; -END IF; - IF customer_ttl_ts IS NOT NULL AND customer_ttl_ts > 0 THEN - deleted_for_entities := - delete_customer_records_from_ts_kv(tenant_id_record, customer_id_record, - customer_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for customer with id = % where tenant_id = %', deleted_for_entities, customer_id_record, tenant_id_record; - deleted_for_entities := - delete_device_records_from_ts_kv(tenant_id_record, customer_id_record, - customer_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for devices where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; - deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, - customer_id_record, - customer_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for assets where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; -END IF; -END LOOP; -FETCH tenant_cursor INTO tenant_id_record; -END LOOP; -END -$$; diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql deleted file mode 100644 index 2caf3b8587..0000000000 --- a/application/src/main/data/upgrade/3.2.2/schema_update.sql +++ /dev/null @@ -1,216 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE TABLE IF NOT EXISTS edge ( - id uuid NOT NULL CONSTRAINT edge_pkey PRIMARY KEY, - created_time bigint NOT NULL, - additional_info varchar, - customer_id uuid, - root_rule_chain_id uuid, - type varchar(255), - name varchar(255), - label varchar(255), - routing_key varchar(255), - secret varchar(255), - edge_license_key varchar(30), - cloud_endpoint varchar(255), - search_text varchar(255), - tenant_id uuid, - CONSTRAINT edge_name_unq_key UNIQUE (tenant_id, name), - CONSTRAINT edge_routing_key_unq_key UNIQUE (routing_key) - ); - -CREATE TABLE IF NOT EXISTS edge_event ( - id uuid NOT NULL CONSTRAINT edge_event_pkey PRIMARY KEY, - created_time bigint NOT NULL, - edge_id uuid, - edge_event_type varchar(255), - edge_event_uid varchar(255), - entity_id uuid, - edge_event_action varchar(255), - body varchar(10000000), - tenant_id uuid, - ts bigint NOT NULL - ); - -CREATE TABLE IF NOT EXISTS resource ( - id uuid NOT NULL CONSTRAINT resource_pkey PRIMARY KEY, - created_time bigint NOT NULL, - tenant_id uuid NOT NULL, - title varchar(255) NOT NULL, - resource_type varchar(32) NOT NULL, - resource_key varchar(255) NOT NULL, - search_text varchar(255), - file_name varchar(255) NOT NULL, - data varchar, - CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key) -); - -CREATE TABLE IF NOT EXISTS ota_package ( - id uuid NOT NULL CONSTRAINT ota_package_pkey PRIMARY KEY, - created_time bigint NOT NULL, - tenant_id uuid NOT NULL, - device_profile_id uuid, - type varchar(32) NOT NULL, - title varchar(255) NOT NULL, - version varchar(255) NOT NULL, - tag varchar(255), - url varchar(255), - file_name varchar(255), - content_type varchar(255), - checksum_algorithm varchar(32), - checksum varchar(1020), - data oid, - data_size bigint, - additional_info varchar, - search_text varchar(255), - CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) -); - -CREATE TABLE IF NOT EXISTS oauth2_params ( - id uuid NOT NULL CONSTRAINT oauth2_params_pkey PRIMARY KEY, - enabled boolean, - tenant_id uuid, - created_time bigint NOT NULL -); - -CREATE TABLE IF NOT EXISTS oauth2_registration ( - id uuid NOT NULL CONSTRAINT oauth2_registration_pkey PRIMARY KEY, - oauth2_params_id uuid NOT NULL, - created_time bigint NOT NULL, - additional_info varchar, - client_id varchar(255), - client_secret varchar(2048), - authorization_uri varchar(255), - token_uri varchar(255), - scope varchar(255), - platforms varchar(255), - user_info_uri varchar(255), - user_name_attribute_name varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - login_button_label varchar(255), - login_button_icon varchar(255), - allow_user_creation boolean, - activate_user boolean, - type varchar(31), - basic_email_attribute_key varchar(31), - basic_first_name_attribute_key varchar(31), - basic_last_name_attribute_key varchar(31), - basic_tenant_name_strategy varchar(31), - basic_tenant_name_pattern varchar(255), - basic_customer_name_pattern varchar(255), - basic_default_dashboard_name varchar(255), - basic_always_full_screen boolean, - custom_url varchar(255), - custom_username varchar(255), - custom_password varchar(255), - custom_send_token boolean, - CONSTRAINT fk_registration_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE -); - -CREATE TABLE IF NOT EXISTS oauth2_domain ( - id uuid NOT NULL CONSTRAINT oauth2_domain_pkey PRIMARY KEY, - oauth2_params_id uuid NOT NULL, - created_time bigint NOT NULL, - domain_name varchar(255), - domain_scheme varchar(31), - CONSTRAINT fk_domain_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE, - CONSTRAINT oauth2_domain_unq_key UNIQUE (oauth2_params_id, domain_name, domain_scheme) -); - -CREATE TABLE IF NOT EXISTS oauth2_mobile ( - id uuid NOT NULL CONSTRAINT oauth2_mobile_pkey PRIMARY KEY, - oauth2_params_id uuid NOT NULL, - created_time bigint NOT NULL, - pkg_name varchar(255), - app_secret varchar(2048), - CONSTRAINT fk_mobile_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE, - CONSTRAINT oauth2_mobile_unq_key UNIQUE (oauth2_params_id, pkg_name) -); - -ALTER TABLE dashboard - ADD COLUMN IF NOT EXISTS image varchar(1000000), - ADD COLUMN IF NOT EXISTS mobile_hide boolean DEFAULT false, - ADD COLUMN IF NOT EXISTS mobile_order int; - -ALTER TABLE device_profile - ADD COLUMN IF NOT EXISTS image varchar(1000000), - ADD COLUMN IF NOT EXISTS firmware_id uuid, - ADD COLUMN IF NOT EXISTS software_id uuid, - ADD COLUMN IF NOT EXISTS default_dashboard_id uuid; - -ALTER TABLE device - ADD COLUMN IF NOT EXISTS firmware_id uuid, - ADD COLUMN IF NOT EXISTS software_id uuid; - -ALTER TABLE alarm - ADD COLUMN IF NOT EXISTS customer_id uuid; - -DELETE FROM relation WHERE from_type = 'TENANT' AND relation_type_group = 'RULE_CHAIN'; - -DO $$ - BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN - ALTER TABLE device_profile - ADD CONSTRAINT fk_firmware_device_profile - FOREIGN KEY (firmware_id) REFERENCES ota_package(id); - END IF; - - IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device_profile') THEN - ALTER TABLE device_profile - ADD CONSTRAINT fk_software_device_profile - FOREIGN KEY (firmware_id) REFERENCES ota_package(id); - END IF; - - IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_default_dashboard_device_profile') THEN - ALTER TABLE device_profile - ADD CONSTRAINT fk_default_dashboard_device_profile - FOREIGN KEY (default_dashboard_id) REFERENCES dashboard(id); - END IF; - - IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN - ALTER TABLE device - ADD CONSTRAINT fk_firmware_device - FOREIGN KEY (firmware_id) REFERENCES ota_package(id); - END IF; - - IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device') THEN - ALTER TABLE device - ADD CONSTRAINT fk_software_device - FOREIGN KEY (firmware_id) REFERENCES ota_package(id); - END IF; - END; -$$; - - -ALTER TABLE api_usage_state - ADD COLUMN IF NOT EXISTS alarm_exec VARCHAR(32); -UPDATE api_usage_state SET alarm_exec = 'ENABLED' WHERE alarm_exec IS NULL; - -CREATE TABLE IF NOT EXISTS rpc ( - id uuid NOT NULL CONSTRAINT rpc_pkey PRIMARY KEY, - created_time bigint NOT NULL, - tenant_id uuid NOT NULL, - device_id uuid NOT NULL, - expiration_time bigint NOT NULL, - request varchar(10000000) NOT NULL, - response varchar(10000000), - additional_info varchar(10000000), - status varchar(255) NOT NULL -); - -CREATE INDEX IF NOT EXISTS idx_rpc_tenant_id_device_id ON rpc(tenant_id, device_id); diff --git a/application/src/main/data/upgrade/3.2.2/schema_update_event.sql b/application/src/main/data/upgrade/3.2.2/schema_update_event.sql deleted file mode 100644 index e89312f783..0000000000 --- a/application/src/main/data/upgrade/3.2.2/schema_update_event.sql +++ /dev/null @@ -1,90 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - --- PROCEDURE: public.cleanup_events_by_ttl(bigint, bigint, bigint) - -DROP PROCEDURE IF EXISTS public.cleanup_events_by_ttl(bigint, bigint, bigint); - -CREATE OR REPLACE PROCEDURE public.cleanup_events_by_ttl( - ttl bigint, - debug_ttl bigint, - INOUT deleted bigint) -LANGUAGE 'plpgsql' -AS $BODY$ -DECLARE - ttl_ts bigint; - debug_ttl_ts bigint; - ttl_deleted_count bigint DEFAULT 0; - debug_ttl_deleted_count bigint DEFAULT 0; -BEGIN - IF ttl > 0 THEN - ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - ttl::bigint * 1000)::bigint; - - DELETE FROM event - WHERE ts < ttl_ts - AND NOT event_type IN ('DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN', 'DEBUG_CONVERTER', 'DEBUG_INTEGRATION'); - - GET DIAGNOSTICS ttl_deleted_count = ROW_COUNT; - END IF; - - IF debug_ttl > 0 THEN - debug_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - debug_ttl::bigint * 1000)::bigint; - - DELETE FROM event - WHERE ts < debug_ttl_ts - AND event_type IN ('DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN', 'DEBUG_CONVERTER', 'DEBUG_INTEGRATION'); - - GET DIAGNOSTICS debug_ttl_deleted_count = ROW_COUNT; - END IF; - - RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count; - RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count; - deleted := ttl_deleted_count + debug_ttl_deleted_count; -END -$BODY$; - - --- Index: idx_event_ts - -DROP INDEX IF EXISTS public.idx_event_ts; - --- Hint: add CONCURRENTLY to CREATE INDEX query in case of more then 1 million records or during live update --- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_event_ts -CREATE INDEX IF NOT EXISTS idx_event_ts - ON public.event - (ts DESC NULLS LAST) - WITH (FILLFACTOR=95); - -COMMENT ON INDEX public.idx_event_ts - IS 'This index helps to delete events by TTL using timestamp'; - - --- Index: idx_event_tenant_entity_type_entity_event_type_created_time_des - -DROP INDEX IF EXISTS public.idx_event_tenant_entity_type_entity_event_type_created_time_des; - --- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_event_tenant_entity_type_entity_event_type_created_time_des -CREATE INDEX IF NOT EXISTS idx_event_tenant_entity_type_entity_event_type_created_time_des - ON public.event - (tenant_id ASC, entity_type ASC, entity_id ASC, event_type ASC, created_time DESC NULLS LAST) - WITH (FILLFACTOR=95); - -COMMENT ON INDEX public.idx_event_tenant_entity_type_entity_event_type_created_time_des - IS 'This index helps to open latest events on UI fast'; - --- Index: idx_event_type_entity_id --- Description: replaced with more suitable idx_event_tenant_entity_type_entity_event_type_created_time_des -DROP INDEX IF EXISTS public.idx_event_type_entity_id; \ No newline at end of file diff --git a/application/src/main/data/upgrade/3.2.2/schema_update_ttl.sql b/application/src/main/data/upgrade/3.2.2/schema_update_ttl.sql deleted file mode 100644 index ed0cb70ed1..0000000000 --- a/application/src/main/data/upgrade/3.2.2/schema_update_ttl.sql +++ /dev/null @@ -1,32 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE OR REPLACE PROCEDURE cleanup_edge_events_by_ttl(IN ttl bigint, INOUT deleted bigint) - LANGUAGE plpgsql AS -$$ -DECLARE - ttl_ts bigint; - ttl_deleted_count bigint DEFAULT 0; -BEGIN - IF ttl > 0 THEN - ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - ttl::bigint * 1000)::bigint; - EXECUTE format( - 'WITH deleted AS (DELETE FROM edge_event WHERE ts < %L::bigint RETURNING *) SELECT count(*) FROM deleted', ttl_ts) into ttl_deleted_count; - END IF; - RAISE NOTICE 'Edge events removed by ttl: %', ttl_deleted_count; - deleted := ttl_deleted_count; -END -$$; diff --git a/application/src/main/data/upgrade/3.3.2/schema_update.sql b/application/src/main/data/upgrade/3.3.2/schema_update.sql deleted file mode 100644 index 00ab32d761..0000000000 --- a/application/src/main/data/upgrade/3.3.2/schema_update.sql +++ /dev/null @@ -1,71 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE TABLE IF NOT EXISTS entity_alarm ( - tenant_id uuid NOT NULL, - entity_type varchar(32), - entity_id uuid NOT NULL, - created_time bigint NOT NULL, - alarm_type varchar(255) NOT NULL, - customer_id uuid, - alarm_id uuid, - CONSTRAINT entity_alarm_pkey PRIMARY KEY (entity_id, alarm_id), - CONSTRAINT fk_entity_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS idx_alarm_tenant_status_created_time ON alarm(tenant_id, status, created_time DESC); -CREATE INDEX IF NOT EXISTS idx_entity_alarm_created_time ON entity_alarm(tenant_id, entity_id, created_time DESC); -CREATE INDEX IF NOT EXISTS idx_entity_alarm_alarm_id ON entity_alarm(alarm_id); - -INSERT INTO entity_alarm(tenant_id, entity_type, entity_id, created_time, alarm_type, customer_id, alarm_id) -SELECT tenant_id, - CASE - WHEN originator_type = 0 THEN 'TENANT' - WHEN originator_type = 1 THEN 'CUSTOMER' - WHEN originator_type = 2 THEN 'USER' - WHEN originator_type = 3 THEN 'DASHBOARD' - WHEN originator_type = 4 THEN 'ASSET' - WHEN originator_type = 5 THEN 'DEVICE' - WHEN originator_type = 6 THEN 'ALARM' - WHEN originator_type = 7 THEN 'RULE_CHAIN' - WHEN originator_type = 8 THEN 'RULE_NODE' - WHEN originator_type = 9 THEN 'ENTITY_VIEW' - WHEN originator_type = 10 THEN 'WIDGETS_BUNDLE' - WHEN originator_type = 11 THEN 'WIDGET_TYPE' - WHEN originator_type = 12 THEN 'TENANT_PROFILE' - WHEN originator_type = 13 THEN 'DEVICE_PROFILE' - WHEN originator_type = 14 THEN 'API_USAGE_STATE' - WHEN originator_type = 15 THEN 'TB_RESOURCE' - WHEN originator_type = 16 THEN 'OTA_PACKAGE' - WHEN originator_type = 17 THEN 'EDGE' - WHEN originator_type = 18 THEN 'RPC' - else 'UNKNOWN' - END, - originator_id, - created_time, - type, - customer_id, - id -FROM alarm -ON CONFLICT DO NOTHING; - -INSERT INTO entity_alarm(tenant_id, entity_type, entity_id, created_time, alarm_type, customer_id, alarm_id) -SELECT a.tenant_id, r.from_type, r.from_id, created_time, type, customer_id, id -FROM alarm a - INNER JOIN relation r ON r.relation_type_group = 'ALARM' and r.relation_type = 'ANY' and a.id = r.to_id -ON CONFLICT DO NOTHING; - -DELETE FROM relation r WHERE r.relation_type_group = 'ALARM'; \ No newline at end of file diff --git a/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql b/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql deleted file mode 100644 index 9066f5c34d..0000000000 --- a/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql +++ /dev/null @@ -1,213 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - - -CREATE OR REPLACE PROCEDURE update_profile_bootstrap() - LANGUAGE plpgsql AS -$$ - -BEGIN - - UPDATE device_profile - SET profile_data = jsonb_set( - profile_data, - '{transportConfiguration}', - get_bootstrap( - profile_data::jsonb #> '{transportConfiguration}', - subquery.publickey_bs, - subquery.publickey_lw, - profile_data::json #>> '{transportConfiguration, bootstrap, bootstrapServer, securityMode}', - profile_data::json #>> '{transportConfiguration, bootstrap, lwm2mServer, securityMode}'), - true) - FROM ( - SELECT id, - encode( - decode(profile_data::json #> '{transportConfiguration,bootstrap,bootstrapServer}' ->> - 'serverPublicKey', 'hex')::bytea, 'base64') AS publickey_bs, - encode( - decode(profile_data::json #> '{transportConfiguration,bootstrap,lwm2mServer}' ->> - 'serverPublicKey', 'hex')::bytea, 'base64') AS publickey_lw - FROM device_profile - WHERE transport_type = 'LWM2M' - ) AS subquery - WHERE device_profile.id = subquery.id - AND subquery.publickey_bs IS NOT NULL - AND subquery.publickey_lw IS NOT NULL; - -END; -$$; - -CREATE OR REPLACE FUNCTION get_bootstrap(transport_configuration_in jsonb, publickey_bs text, - publickey_lw text, security_mode_bs text, - security_mode_lw text) RETURNS jsonb AS -$$ - -DECLARE - bootstrap_new jsonb; - bootstrap_in jsonb; - -BEGIN - - IF security_mode_lw IS NULL THEN - security_mode_lw := 'NO_SEC'; - END IF; - - IF security_mode_bs IS NULL THEN - security_mode_bs := 'NO_SEC'; - END IF; - - bootstrap_in := transport_configuration_in::jsonb #> '{bootstrap}'; - bootstrap_new := json_build_array( - json_build_object('shortServerId', bootstrap_in::json #> '{bootstrapServer}' -> 'serverId', - 'securityMode', security_mode_bs, - 'binding', bootstrap_in::json #> '{servers}' ->> 'binding', - 'lifetime', bootstrap_in::json #> '{servers}' -> 'lifetime', - 'notifIfDisabled', bootstrap_in::json #> '{servers}' -> 'notifIfDisabled', - 'defaultMinPeriod', bootstrap_in::json #> '{servers}' -> 'defaultMinPeriod', - 'host', bootstrap_in::json #> '{bootstrapServer}' ->> 'host', - 'port', bootstrap_in::json #> '{bootstrapServer}' -> 'port', - 'serverPublicKey', publickey_bs, - 'bootstrapServerIs', true, - 'clientHoldOffTime', bootstrap_in::json #> '{bootstrapServer}' -> 'clientHoldOffTime', - 'bootstrapServerAccountTimeout', - bootstrap_in::json #> '{bootstrapServer}' -> 'bootstrapServerAccountTimeout' - ), - json_build_object('shortServerId', bootstrap_in::json #> '{lwm2mServer}' -> 'serverId', - 'securityMode', security_mode_lw, - 'binding', bootstrap_in::json #> '{servers}' ->> 'binding', - 'lifetime', bootstrap_in::json #> '{servers}' -> 'lifetime', - 'notifIfDisabled', bootstrap_in::json #> '{servers}' -> 'notifIfDisabled', - 'defaultMinPeriod', bootstrap_in::json #> '{servers}' -> 'defaultMinPeriod', - 'host', bootstrap_in::json #> '{lwm2mServer}' ->> 'host', - 'port', bootstrap_in::json #> '{lwm2mServer}' -> 'port', - 'serverPublicKey', publickey_lw, - 'bootstrapServerIs', false, - 'clientHoldOffTime', bootstrap_in::json #> '{lwm2mServer}' -> 'clientHoldOffTime', - 'bootstrapServerAccountTimeout', - bootstrap_in::json #> '{lwm2mServer}' -> 'bootstrapServerAccountTimeout' - ) - ); - RETURN jsonb_set( - transport_configuration_in, - '{bootstrap}', - bootstrap_new, - true) || '{"bootstrapServerUpdateEnable": true}'; - -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE PROCEDURE update_device_credentials_to_base64_and_bootstrap() - LANGUAGE plpgsql AS -$$ - -BEGIN - - UPDATE device_credentials - SET credentials_value = get_device_and_bootstrap(credentials_value::text) - WHERE credentials_type = 'LWM2M_CREDENTIALS'; -END; -$$; - -CREATE OR REPLACE FUNCTION get_device_and_bootstrap(IN credentials_value text, OUT credentials_value_new text) - LANGUAGE plpgsql AS -$$ -DECLARE - client_secret_key text; - client_public_key_or_id text; - client_key_value_object jsonb; - client_bootstrap_server_value_object jsonb; - client_bootstrap_server_object jsonb; - client_bootstrap_object jsonb; - -BEGIN - credentials_value_new := credentials_value; - IF credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode' = 'RPK' AND - NULLIF((credentials_value::jsonb #> '{client}' ->> 'key' ~ '^[0-9a-fA-F]+$')::text, 'false') = 'true' THEN - client_public_key_or_id := encode(decode(credentials_value::jsonb #> '{client}' ->> 'key', 'hex')::bytea, 'base64'); - client_key_value_object := json_build_object( - 'endpoint', credentials_value::jsonb #> '{client}' ->> 'endpoint', - 'securityConfigClientMode', credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode', - 'key', client_public_key_or_id); - credentials_value_new := - credentials_value_new::jsonb || json_build_object('client', client_key_value_object)::jsonb; - END IF; - IF credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode' = 'X509' AND - NULLIF((credentials_value::jsonb #> '{client}' ->> 'cert' ~ '^[0-9a-fA-F]+$')::text, 'false') = 'true' THEN - client_public_key_or_id := - encode(decode(credentials_value::jsonb #> '{client}' ->> 'cert', 'hex')::bytea, 'base64'); - client_key_value_object := json_build_object( - 'endpoint', credentials_value::jsonb #> '{client}' ->> 'endpoint', - 'securityConfigClientMode', credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode', - 'cert', client_public_key_or_id); - credentials_value_new := - credentials_value_new::jsonb || json_build_object('client', client_key_value_object)::jsonb; - END IF; - - IF credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode' = 'RPK' OR - credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode' = 'X509' THEN - IF NULLIF((credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientSecretKey' ~ '^[0-9a-fA-F]+$')::text, - 'false') = 'true' AND - NULLIF( - (credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientPublicKeyOrId' ~ '^[0-9a-fA-F]+$')::text, - 'false') = 'true' THEN - client_secret_key := - encode(decode(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientSecretKey', 'hex')::bytea, - 'base64'); - client_public_key_or_id := encode( - decode(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientPublicKeyOrId', 'hex')::bytea, - 'base64'); - client_bootstrap_server_value_object := jsonb_build_object( - 'securityMode', credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode', - 'clientPublicKeyOrId', client_public_key_or_id, - 'clientSecretKey', client_secret_key - ); - client_bootstrap_server_object := jsonb_build_object('lwm2mServer', client_bootstrap_server_value_object::jsonb); - client_bootstrap_object := credentials_value_new::jsonb #> '{bootstrap}' || client_bootstrap_server_object::jsonb; - credentials_value_new := - jsonb_set(credentials_value_new::jsonb, '{bootstrap}', client_bootstrap_object::jsonb, false)::jsonb; - END IF; - END IF; - - IF credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode' = 'RPK' OR - credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode' = 'X509' THEN - IF NULLIF( - (credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientSecretKey' ~ '^[0-9a-fA-F]+$')::text, - 'false') = 'true' AND - NULLIF( - (credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientPublicKeyOrId' ~ '^[0-9a-fA-F]+$')::text, - 'false') = 'true' THEN - client_secret_key := - encode( - decode(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientSecretKey', 'hex')::bytea, - 'base64'); - client_public_key_or_id := encode( - decode(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientPublicKeyOrId', 'hex')::bytea, - 'base64'); - client_bootstrap_server_value_object := jsonb_build_object( - 'securityMode', credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode', - 'clientPublicKeyOrId', client_public_key_or_id, - 'clientSecretKey', client_secret_key - ); - client_bootstrap_server_object := - jsonb_build_object('bootstrapServer', client_bootstrap_server_value_object::jsonb); - client_bootstrap_object := credentials_value_new::jsonb #> '{bootstrap}' || client_bootstrap_server_object::jsonb; - credentials_value_new := - jsonb_set(credentials_value_new::jsonb, '{bootstrap}', client_bootstrap_object::jsonb, false)::jsonb; - END IF; - END IF; - -END; -$$; \ No newline at end of file diff --git a/application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql b/application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql deleted file mode 100644 index 10a5384040..0000000000 --- a/application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql +++ /dev/null @@ -1,50 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - - -DROP PROCEDURE IF EXISTS public.cleanup_events_by_ttl(bigint, bigint, bigint); - -CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl( - IN regular_events_start_ts bigint, - IN regular_events_end_ts bigint, - IN debug_events_start_ts bigint, - IN debug_events_end_ts bigint, - INOUT deleted bigint) - LANGUAGE plpgsql AS -$$ -DECLARE - ttl_deleted_count bigint DEFAULT 0; - debug_ttl_deleted_count bigint DEFAULT 0; -BEGIN - IF regular_events_start_ts > 0 AND regular_events_end_ts > 0 THEN - EXECUTE format( - 'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' || - '(event_type != %L::varchar AND event_type != %L::varchar)) RETURNING *) ' || - 'SELECT count(*) FROM deleted', regular_events_start_ts, regular_events_end_ts, - 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count; - END IF; - IF debug_events_start_ts > 0 AND debug_events_end_ts > 0 THEN - EXECUTE format( - 'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' || - '(event_type = %L::varchar OR event_type = %L::varchar)) RETURNING *) ' || - 'SELECT count(*) FROM deleted', debug_events_start_ts, debug_events_end_ts, - 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count; - END IF; - RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count; - RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count; - deleted := ttl_deleted_count + debug_ttl_deleted_count; -END -$$; diff --git a/application/src/main/data/upgrade/3.3.3/schema_update.sql b/application/src/main/data/upgrade/3.3.3/schema_update.sql deleted file mode 100644 index b9a43435da..0000000000 --- a/application/src/main/data/upgrade/3.3.3/schema_update.sql +++ /dev/null @@ -1,29 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -DELETE from ota_package as op WHERE NOT EXISTS(SELECT * FROM device_profile dp where op.device_profile_id = dp.id); - -DO -$$ - BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'fk_device_profile_ota_package') THEN - ALTER TABLE ota_package - ADD CONSTRAINT fk_device_profile_ota_package - FOREIGN KEY (device_profile_id) REFERENCES device_profile (id) - ON DELETE CASCADE; - END IF; - END; -$$; diff --git a/application/src/main/data/upgrade/3.3.4/schema_update.sql b/application/src/main/data/upgrade/3.3.4/schema_update.sql deleted file mode 100644 index dff72ca33b..0000000000 --- a/application/src/main/data/upgrade/3.3.4/schema_update.sql +++ /dev/null @@ -1,140 +0,0 @@ --- --- Copyright © 2016-2024 The Thingsboard Authors --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -ALTER TABLE device - ADD COLUMN IF NOT EXISTS external_id UUID; -ALTER TABLE device_profile - ADD COLUMN IF NOT EXISTS external_id UUID; -ALTER TABLE asset - ADD COLUMN IF NOT EXISTS external_id UUID; -ALTER TABLE rule_chain - ADD COLUMN IF NOT EXISTS external_id UUID; -ALTER TABLE rule_node - ADD COLUMN IF NOT EXISTS external_id UUID; -ALTER TABLE dashboard - ADD COLUMN IF NOT EXISTS external_id UUID; -ALTER TABLE customer - ADD COLUMN IF NOT EXISTS external_id UUID; -ALTER TABLE widgets_bundle - ADD COLUMN IF NOT EXISTS external_id UUID; -ALTER TABLE entity_view - ADD COLUMN IF NOT EXISTS external_id UUID; - -CREATE INDEX IF NOT EXISTS idx_rule_node_external_id ON rule_node(rule_chain_id, external_id); -CREATE INDEX IF NOT EXISTS idx_rule_node_type ON rule_node(type); - -ALTER TABLE admin_settings - ADD COLUMN IF NOT EXISTS tenant_id uuid NOT NULL DEFAULT '13814000-1dd2-11b2-8080-808080808080'; - -CREATE TABLE IF NOT EXISTS queue ( - id uuid NOT NULL CONSTRAINT queue_pkey PRIMARY KEY, - created_time bigint NOT NULL, - tenant_id uuid, - name varchar(255), - topic varchar(255), - poll_interval int, - partitions int, - consumer_per_partition boolean, - pack_processing_timeout bigint, - submit_strategy varchar(255), - processing_strategy varchar(255), - additional_info varchar -); - -CREATE TABLE IF NOT EXISTS user_auth_settings ( - id uuid NOT NULL CONSTRAINT user_auth_settings_pkey PRIMARY KEY, - created_time bigint NOT NULL, - user_id uuid UNIQUE NOT NULL CONSTRAINT fk_user_auth_settings_user_id REFERENCES tb_user(id), - two_fa_settings varchar -); - -CREATE INDEX IF NOT EXISTS idx_api_usage_state_entity_id ON api_usage_state(entity_id); - -ALTER TABLE tenant_profile DROP COLUMN IF EXISTS isolated_tb_core; - -DO -$$ - BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'device_external_id_unq_key') THEN - ALTER TABLE device ADD CONSTRAINT device_external_id_unq_key UNIQUE (tenant_id, external_id); - END IF; - END; -$$; - -DO -$$ - BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'device_profile_external_id_unq_key') THEN - ALTER TABLE device_profile ADD CONSTRAINT device_profile_external_id_unq_key UNIQUE (tenant_id, external_id); - END IF; - END; -$$; - -DO -$$ - BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'asset_external_id_unq_key') THEN - ALTER TABLE asset ADD CONSTRAINT asset_external_id_unq_key UNIQUE (tenant_id, external_id); - END IF; - END; -$$; - -DO -$$ - BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'rule_chain_external_id_unq_key') THEN - ALTER TABLE rule_chain ADD CONSTRAINT rule_chain_external_id_unq_key UNIQUE (tenant_id, external_id); - END IF; - END; -$$; - - -DO -$$ - BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'dashboard_external_id_unq_key') THEN - ALTER TABLE dashboard ADD CONSTRAINT dashboard_external_id_unq_key UNIQUE (tenant_id, external_id); - END IF; - END; -$$; - -DO -$$ - BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'customer_external_id_unq_key') THEN - ALTER TABLE customer ADD CONSTRAINT customer_external_id_unq_key UNIQUE (tenant_id, external_id); - END IF; - END; -$$; - -DO -$$ - BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'widgets_bundle_external_id_unq_key') THEN - ALTER TABLE widgets_bundle ADD CONSTRAINT widgets_bundle_external_id_unq_key UNIQUE (tenant_id, external_id); - END IF; - END; -$$; - -DO -$$ - BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'entity_view_external_id_unq_key') THEN - ALTER TABLE entity_view ADD CONSTRAINT entity_view_external_id_unq_key UNIQUE (tenant_id, external_id); - END IF; - END; -$$; - diff --git a/application/src/main/data/upgrade/3.4.0/schema_update.sql b/application/src/main/data/upgrade/3.4.0/schema_update.sql deleted file mode 100644 index c511210e1b..0000000000 --- a/application/src/main/data/upgrade/3.4.0/schema_update.sql +++ /dev/null @@ -1,234 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE TABLE IF NOT EXISTS rule_node_debug_event ( - id uuid NOT NULL, - tenant_id uuid NOT NULL , - ts bigint NOT NULL, - entity_id uuid NOT NULL, - service_id varchar, - e_type varchar, - e_entity_id uuid, - e_entity_type varchar, - e_msg_id uuid, - e_msg_type varchar, - e_data_type varchar, - e_relation_type varchar, - e_data varchar, - e_metadata varchar, - e_error varchar -) PARTITION BY RANGE (ts); - -CREATE TABLE IF NOT EXISTS rule_chain_debug_event ( - id uuid NOT NULL, - tenant_id uuid NOT NULL, - ts bigint NOT NULL, - entity_id uuid NOT NULL, - service_id varchar NOT NULL, - e_message varchar, - e_error varchar -) PARTITION BY RANGE (ts); - -CREATE TABLE IF NOT EXISTS stats_event ( - id uuid NOT NULL, - tenant_id uuid NOT NULL, - ts bigint NOT NULL, - entity_id uuid NOT NULL, - service_id varchar NOT NULL, - e_messages_processed bigint NOT NULL, - e_errors_occurred bigint NOT NULL -) PARTITION BY RANGE (ts); - -CREATE TABLE IF NOT EXISTS lc_event ( - id uuid NOT NULL, - tenant_id uuid NOT NULL, - ts bigint NOT NULL, - entity_id uuid NOT NULL, - service_id varchar NOT NULL, - e_type varchar NOT NULL, - e_success boolean NOT NULL, - e_error varchar -) PARTITION BY RANGE (ts); - -CREATE TABLE IF NOT EXISTS error_event ( - id uuid NOT NULL, - tenant_id uuid NOT NULL, - ts bigint NOT NULL, - entity_id uuid NOT NULL, - service_id varchar NOT NULL, - e_method varchar NOT NULL, - e_error varchar -) PARTITION BY RANGE (ts); - -CREATE INDEX IF NOT EXISTS idx_rule_node_debug_event_main - ON rule_node_debug_event (tenant_id ASC, entity_id ASC, ts DESC NULLS LAST) WITH (FILLFACTOR=95); - -CREATE INDEX IF NOT EXISTS idx_rule_chain_debug_event_main - ON rule_chain_debug_event (tenant_id ASC, entity_id ASC, ts DESC NULLS LAST) WITH (FILLFACTOR=95); - -CREATE INDEX IF NOT EXISTS idx_stats_event_main - ON stats_event (tenant_id ASC, entity_id ASC, ts DESC NULLS LAST) WITH (FILLFACTOR=95); - -CREATE INDEX IF NOT EXISTS idx_lc_event_main - ON lc_event (tenant_id ASC, entity_id ASC, ts DESC NULLS LAST) WITH (FILLFACTOR=95); - -CREATE INDEX IF NOT EXISTS idx_error_event_main - ON error_event (tenant_id ASC, entity_id ASC, ts DESC NULLS LAST) WITH (FILLFACTOR=95); - -CREATE OR REPLACE FUNCTION to_safe_json(p_json text) RETURNS json -LANGUAGE plpgsql AS -$$ -BEGIN - return REPLACE(p_json, '\u0000', '' )::json; -EXCEPTION - WHEN OTHERS THEN - return '{}'::json; -END; -$$; - --- Useful to migrate old events to the new table structure; -CREATE OR REPLACE PROCEDURE migrate_regular_events(IN start_ts_in_ms bigint, IN end_ts_in_ms bigint, IN partition_size_in_hours int) - LANGUAGE plpgsql AS -$$ -DECLARE - partition_size_in_ms bigint; - p record; - table_name varchar; -BEGIN - partition_size_in_ms = partition_size_in_hours * 3600 * 1000; - - FOR p IN SELECT DISTINCT event_type as event_type, (created_time - created_time % partition_size_in_ms) as partition_ts FROM event e WHERE e.event_type in ('STATS', 'LC_EVENT', 'ERROR') and ts >= start_ts_in_ms and ts < end_ts_in_ms - LOOP - IF p.event_type = 'STATS' THEN - table_name := 'stats_event'; - ELSEIF p.event_type = 'LC_EVENT' THEN - table_name := 'lc_event'; - ELSEIF p.event_type = 'ERROR' THEN - table_name := 'error_event'; - END IF; - RAISE NOTICE '[%] Partition to create : [%-%]', table_name, p.partition_ts, (p.partition_ts + partition_size_in_ms); - EXECUTE format('CREATE TABLE IF NOT EXISTS %s_%s PARTITION OF %s FOR VALUES FROM ( %s ) TO ( %s )', table_name, p.partition_ts, table_name, p.partition_ts, (p.partition_ts + partition_size_in_ms)); - END LOOP; - - INSERT INTO stats_event - SELECT id, - tenant_id, - ts, - entity_id, - body ->> 'server', - (body ->> 'messagesProcessed')::bigint, - (body ->> 'errorsOccurred')::bigint - FROM - (select id, tenant_id, ts, entity_id, to_safe_json(body) as body - FROM event WHERE ts >= start_ts_in_ms and ts < end_ts_in_ms AND event_type = 'STATS' AND to_safe_json(body) ->> 'server' IS NOT NULL - ) safe_event - ON CONFLICT DO NOTHING; - - INSERT INTO lc_event - SELECT id, - tenant_id, - ts, - entity_id, - body ->> 'server', - body ->> 'event', - (body ->> 'success')::boolean, - body ->> 'error' - FROM - (select id, tenant_id, ts, entity_id, to_safe_json(body) as body - FROM event WHERE ts >= start_ts_in_ms and ts < end_ts_in_ms AND event_type = 'LC_EVENT' AND to_safe_json(body) ->> 'server' IS NOT NULL - ) safe_event - ON CONFLICT DO NOTHING; - - INSERT INTO error_event - SELECT id, - tenant_id, - ts, - entity_id, - body ->> 'server', - body ->> 'method', - body ->> 'error' - FROM - (select id, tenant_id, ts, entity_id, to_safe_json(body) as body - FROM event WHERE ts >= start_ts_in_ms and ts < end_ts_in_ms AND event_type = 'ERROR' AND to_safe_json(body) ->> 'server' IS NOT NULL - ) safe_event - ON CONFLICT DO NOTHING; - -END -$$; - --- Useful to migrate old debug events to the new table structure; -CREATE OR REPLACE PROCEDURE migrate_debug_events(IN start_ts_in_ms bigint, IN end_ts_in_ms bigint, IN partition_size_in_hours int) - LANGUAGE plpgsql AS -$$ -DECLARE - partition_size_in_ms bigint; - p record; - table_name varchar; -BEGIN - partition_size_in_ms = partition_size_in_hours * 3600 * 1000; - - FOR p IN SELECT DISTINCT event_type as event_type, (created_time - created_time % partition_size_in_ms) as partition_ts FROM event e WHERE e.event_type in ('DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') and ts >= start_ts_in_ms and ts < end_ts_in_ms - LOOP - IF p.event_type = 'DEBUG_RULE_NODE' THEN - table_name := 'rule_node_debug_event'; - ELSEIF p.event_type = 'DEBUG_RULE_CHAIN' THEN - table_name := 'rule_chain_debug_event'; - END IF; - RAISE NOTICE '[%] Partition to create : [%-%]', table_name, p.partition_ts, (p.partition_ts + partition_size_in_ms); - EXECUTE format('CREATE TABLE IF NOT EXISTS %s_%s PARTITION OF %s FOR VALUES FROM ( %s ) TO ( %s )', table_name, p.partition_ts, table_name, p.partition_ts, (p.partition_ts + partition_size_in_ms)); - END LOOP; - - INSERT INTO rule_node_debug_event - SELECT id, - tenant_id, - ts, - entity_id, - body ->> 'server', - body ->> 'type', - (body ->> 'entityId')::uuid, - body ->> 'entityName', - (body ->> 'msgId')::uuid, - body ->> 'msgType', - body ->> 'dataType', - body ->> 'relationType', - body ->> 'data', - body ->> 'metadata', - body ->> 'error' - FROM - (select id, tenant_id, ts, entity_id, to_safe_json(body) as body - FROM event WHERE ts >= start_ts_in_ms and ts < end_ts_in_ms AND event_type = 'DEBUG_RULE_NODE' AND to_safe_json(body) ->> 'server' IS NOT NULL - ) safe_event - ON CONFLICT DO NOTHING; - - INSERT INTO rule_chain_debug_event - SELECT id, - tenant_id, - ts, - entity_id, - body ->> 'server', - body ->> 'message', - body ->> 'error' - FROM - (select id, tenant_id, ts, entity_id, to_safe_json(body) as body - FROM event WHERE ts >= start_ts_in_ms and ts < end_ts_in_ms AND event_type = 'DEBUG_RULE_CHAIN' AND to_safe_json(body) ->> 'server' IS NOT NULL - ) safe_event - ON CONFLICT DO NOTHING; -END -$$; - -UPDATE tb_user - SET additional_info = REPLACE(additional_info, '"lang":"ja_JA"', '"lang":"ja_JP"') - WHERE additional_info LIKE '%"lang":"ja_JA"%'; diff --git a/application/src/main/data/upgrade/3.4.1/schema_update.sql b/application/src/main/data/upgrade/3.4.1/schema_update.sql deleted file mode 100644 index c0bcdaca39..0000000000 --- a/application/src/main/data/upgrade/3.4.1/schema_update.sql +++ /dev/null @@ -1,142 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - --- AUDIT LOGS MIGRATION START -DO -$$ - DECLARE table_partition RECORD; - BEGIN - -- in case of running the upgrade script a second time: - IF NOT (SELECT exists(SELECT FROM pg_tables WHERE tablename = 'old_audit_log')) THEN - ALTER TABLE audit_log RENAME TO old_audit_log; - CREATE INDEX IF NOT EXISTS idx_old_audit_log_created_time ON old_audit_log(created_time); - - ALTER INDEX IF EXISTS idx_audit_log_tenant_id_and_created_time RENAME TO idx_old_audit_log_tenant_id_and_created_time; - - FOR table_partition IN SELECT tablename AS name, split_part(tablename, '_', 3) AS partition_ts - FROM pg_tables WHERE tablename LIKE 'audit_log_%' - LOOP - EXECUTE format('ALTER TABLE %s RENAME TO old_audit_log_%s', table_partition.name, table_partition.partition_ts); - END LOOP; - ELSE - RAISE NOTICE 'Table old_audit_log already exists, leaving as is'; - END IF; - END; -$$; - -CREATE TABLE IF NOT EXISTS audit_log ( - id uuid NOT NULL, - created_time bigint NOT NULL, - tenant_id uuid, - customer_id uuid, - entity_id uuid, - entity_type varchar(255), - entity_name varchar(255), - user_id uuid, - user_name varchar(255), - action_type varchar(255), - action_data varchar(1000000), - action_status varchar(255), - action_failure_details varchar(1000000) -) PARTITION BY RANGE (created_time); -CREATE INDEX IF NOT EXISTS idx_audit_log_tenant_id_and_created_time ON audit_log(tenant_id, created_time DESC); -CREATE INDEX IF NOT EXISTS idx_audit_log_id ON audit_log(id); - -CREATE OR REPLACE PROCEDURE migrate_audit_logs(IN start_time_ms BIGINT, IN end_time_ms BIGINT, IN partition_size_ms BIGINT) - LANGUAGE plpgsql AS -$$ -DECLARE - p RECORD; - partition_end_ts BIGINT; -BEGIN - FOR p IN SELECT DISTINCT (created_time - created_time % partition_size_ms) AS partition_ts FROM old_audit_log - WHERE created_time >= start_time_ms AND created_time < end_time_ms - LOOP - partition_end_ts = p.partition_ts + partition_size_ms; - RAISE NOTICE '[audit_log] Partition to create : [%-%]', p.partition_ts, partition_end_ts; - EXECUTE format('CREATE TABLE IF NOT EXISTS audit_log_%s PARTITION OF audit_log ' || - 'FOR VALUES FROM ( %s ) TO ( %s )', p.partition_ts, p.partition_ts, partition_end_ts); - END LOOP; - - INSERT INTO audit_log - SELECT id, created_time, tenant_id, customer_id, entity_id, entity_type, entity_name, user_id, user_name, action_type, action_data, action_status, action_failure_details - FROM old_audit_log - WHERE created_time >= start_time_ms AND created_time < end_time_ms; -END; -$$; --- AUDIT LOGS MIGRATION END - - --- EDGE EVENTS MIGRATION START -DO -$$ - DECLARE table_partition RECORD; - BEGIN - -- in case of running the upgrade script a second time: - IF NOT (SELECT exists(SELECT FROM pg_tables WHERE tablename = 'old_edge_event')) THEN - ALTER TABLE edge_event RENAME TO old_edge_event; - CREATE INDEX IF NOT EXISTS idx_old_edge_event_created_time_tmp ON old_edge_event(created_time); - ALTER INDEX IF EXISTS idx_edge_event_tenant_id_and_created_time RENAME TO idx_old_edge_event_tenant_id_and_created_time; - - FOR table_partition IN SELECT tablename AS name, split_part(tablename, '_', 3) AS partition_ts - FROM pg_tables WHERE tablename LIKE 'edge_event_%' - LOOP - EXECUTE format('ALTER TABLE %s RENAME TO old_edge_event_%s', table_partition.name, table_partition.partition_ts); - END LOOP; - ELSE - RAISE NOTICE 'Table old_edge_event already exists, leaving as is'; - END IF; -END; -$$; - -CREATE TABLE IF NOT EXISTS edge_event ( - id uuid NOT NULL, - created_time bigint NOT NULL, - edge_id uuid, - edge_event_type varchar(255), - edge_event_uid varchar(255), - entity_id uuid, - edge_event_action varchar(255), - body varchar(10000000), - tenant_id uuid, - ts bigint NOT NULL - ) PARTITION BY RANGE (created_time); -CREATE INDEX IF NOT EXISTS idx_edge_event_tenant_id_and_created_time ON edge_event(tenant_id, created_time DESC); -CREATE INDEX IF NOT EXISTS idx_edge_event_id ON edge_event(id); - -CREATE OR REPLACE PROCEDURE migrate_edge_event(IN start_time_ms BIGINT, IN end_time_ms BIGINT, IN partition_size_ms BIGINT) - LANGUAGE plpgsql AS -$$ -DECLARE - p RECORD; - partition_end_ts BIGINT; -BEGIN - FOR p IN SELECT DISTINCT (created_time - created_time % partition_size_ms) AS partition_ts FROM old_edge_event - WHERE created_time >= start_time_ms AND created_time < end_time_ms - LOOP - partition_end_ts = p.partition_ts + partition_size_ms; - RAISE NOTICE '[edge_event] Partition to create : [%-%]', p.partition_ts, partition_end_ts; - EXECUTE format('CREATE TABLE IF NOT EXISTS edge_event_%s PARTITION OF edge_event ' || - 'FOR VALUES FROM ( %s ) TO ( %s )', p.partition_ts, p.partition_ts, partition_end_ts); - END LOOP; - - INSERT INTO edge_event - SELECT id, created_time, edge_id, edge_event_type, edge_event_uid, entity_id, edge_event_action, body, tenant_id, ts - FROM old_edge_event - WHERE created_time >= start_time_ms AND created_time < end_time_ms; -END; -$$; --- EDGE EVENTS MIGRATION END diff --git a/application/src/main/data/upgrade/3.4.1/schema_update_after.sql b/application/src/main/data/upgrade/3.4.1/schema_update_after.sql deleted file mode 100644 index 6d1ee4d8a8..0000000000 --- a/application/src/main/data/upgrade/3.4.1/schema_update_after.sql +++ /dev/null @@ -1,21 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -DROP PROCEDURE IF EXISTS update_asset_profiles; - -ALTER TABLE asset ALTER COLUMN asset_profile_id SET NOT NULL; -ALTER TABLE asset DROP CONSTRAINT IF EXISTS fk_asset_profile; -ALTER TABLE asset ADD CONSTRAINT fk_asset_profile FOREIGN KEY (asset_profile_id) REFERENCES asset_profile(id); diff --git a/application/src/main/data/upgrade/3.4.1/schema_update_before.sql b/application/src/main/data/upgrade/3.4.1/schema_update_before.sql deleted file mode 100644 index f2c849fb24..0000000000 --- a/application/src/main/data/upgrade/3.4.1/schema_update_before.sql +++ /dev/null @@ -1,46 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - -CREATE TABLE IF NOT EXISTS asset_profile ( - id uuid NOT NULL CONSTRAINT asset_profile_pkey PRIMARY KEY, - created_time bigint NOT NULL, - name varchar(255), - image varchar(1000000), - description varchar, - search_text varchar(255), - is_default boolean, - tenant_id uuid, - default_rule_chain_id uuid, - default_dashboard_id uuid, - default_queue_name varchar(255), - external_id uuid, - CONSTRAINT asset_profile_name_unq_key UNIQUE (tenant_id, name), - CONSTRAINT asset_profile_external_id_unq_key UNIQUE (tenant_id, external_id), - CONSTRAINT fk_default_rule_chain_asset_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id), - CONSTRAINT fk_default_dashboard_asset_profile FOREIGN KEY (default_dashboard_id) REFERENCES dashboard(id) - ); - -CREATE OR REPLACE PROCEDURE update_asset_profiles() - LANGUAGE plpgsql AS -$$ -BEGIN - UPDATE asset a SET asset_profile_id = COALESCE( - (SELECT id from asset_profile p WHERE p.tenant_id = a.tenant_id AND a.type = p.name), - (SELECT id from asset_profile p WHERE p.tenant_id = a.tenant_id AND p.name = 'default') - ) - WHERE a.asset_profile_id IS NULL; -END; -$$; diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql deleted file mode 100644 index 37b1b3a7e0..0000000000 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ /dev/null @@ -1,379 +0,0 @@ --- --- Copyright © 2016-2024 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. --- - --- USER CREDENTIALS START - -ALTER TABLE user_credentials - ADD COLUMN IF NOT EXISTS additional_info varchar NOT NULL DEFAULT '{}'; - -UPDATE user_credentials - SET additional_info = json_build_object('userPasswordHistory', (u.additional_info::json -> 'userPasswordHistory')) - FROM tb_user u WHERE user_credentials.user_id = u.id AND u.additional_info::jsonb ? 'userPasswordHistory'; - -UPDATE tb_user SET additional_info = tb_user.additional_info::jsonb - 'userPasswordHistory' WHERE additional_info::jsonb ? 'userPasswordHistory'; - --- USER CREDENTIALS END - --- ALARM ASSIGN TO USER START - -ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assign_ts BIGINT DEFAULT 0; -ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assignee_id UUID; - -CREATE INDEX IF NOT EXISTS idx_alarm_tenant_assignee_created_time ON alarm(tenant_id, assignee_id, created_time DESC); - --- ALARM ASSIGN TO USER END - --- ALARM STATUS REFACTORING START - -ALTER TABLE alarm ADD COLUMN IF NOT EXISTS acknowledged boolean; -ALTER TABLE alarm ADD COLUMN IF NOT EXISTS cleared boolean; - -ALTER TABLE alarm ADD COLUMN IF NOT EXISTS status varchar; -- to avoid failure of the subsequent upgrade. -UPDATE alarm SET acknowledged = true, cleared = true WHERE status = 'CLEARED_ACK'; -UPDATE alarm SET acknowledged = true, cleared = false WHERE status = 'ACTIVE_ACK'; -UPDATE alarm SET acknowledged = false, cleared = true WHERE status = 'CLEARED_UNACK'; -UPDATE alarm SET acknowledged = false, cleared = false WHERE status = 'ACTIVE_UNACK'; - --- Drop index by 'status' column and replace with new indexes that has only active alarms; -DROP INDEX IF EXISTS idx_alarm_originator_alarm_type_active; -CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type_active - ON alarm USING btree (originator_id, type) WHERE cleared = false; - -DROP INDEX IF EXISTS idx_alarm_tenant_alarm_type_active; -CREATE INDEX IF NOT EXISTS idx_alarm_tenant_alarm_type_active - ON alarm USING btree (tenant_id, type) WHERE cleared = false; - --- Cover index by alarm type to optimize propagated alarm queries; -DROP INDEX IF EXISTS idx_entity_alarm_entity_id_alarm_type_created_time_alarm_id; -CREATE INDEX IF NOT EXISTS idx_entity_alarm_entity_id_alarm_type_created_time_alarm_id ON entity_alarm -USING btree (tenant_id, entity_id, alarm_type, created_time DESC) INCLUDE(alarm_id); - -DROP INDEX IF EXISTS idx_alarm_tenant_status_created_time; -ALTER TABLE alarm DROP COLUMN IF EXISTS status; - --- Update old alarms and set their state to clear, if there are newer alarms. -UPDATE alarm a -SET cleared = TRUE -WHERE cleared = FALSE - AND id != (SELECT l.id - FROM alarm l - WHERE l.tenant_id = a.tenant_id - AND l.originator_id = a.originator_id - AND l.type = a.type - ORDER BY l.created_time DESC, l.id - LIMIT 1); - --- ALARM STATUS REFACTORING END - --- ALARM COMMENTS START - -CREATE TABLE IF NOT EXISTS alarm_comment ( - id uuid NOT NULL, - created_time bigint NOT NULL, - alarm_id uuid NOT NULL, - user_id uuid, - type varchar(255) NOT NULL, - comment varchar(10000), - CONSTRAINT fk_alarm_comment_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE -) PARTITION BY RANGE (created_time); -CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id); - --- ALARM COMMENTS END - --- NOTIFICATIONS START - -CREATE TABLE IF NOT EXISTS notification_target ( - id UUID NOT NULL CONSTRAINT notification_target_pkey PRIMARY KEY, - created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL, - name VARCHAR(255) NOT NULL, - configuration VARCHAR(10000) NOT NULL, - CONSTRAINT uq_notification_target_name UNIQUE (tenant_id, name) -); -CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_created_time ON notification_target(tenant_id, created_time DESC); - -CREATE TABLE IF NOT EXISTS notification_template ( - id UUID NOT NULL CONSTRAINT notification_template_pkey PRIMARY KEY, - created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL, - name VARCHAR(255) NOT NULL, - notification_type VARCHAR(50) NOT NULL, - configuration VARCHAR(10000000) NOT NULL, - CONSTRAINT uq_notification_template_name UNIQUE (tenant_id, name) -); -CREATE INDEX IF NOT EXISTS idx_notification_template_tenant_id_created_time ON notification_template(tenant_id, created_time DESC); - -CREATE TABLE IF NOT EXISTS notification_rule ( - id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY, - created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL, - name VARCHAR(255) NOT NULL, - template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), - trigger_type VARCHAR(50) NOT NULL, - trigger_config VARCHAR(1000) NOT NULL, - recipients_config VARCHAR(10000) NOT NULL, - additional_config VARCHAR(255), - CONSTRAINT uq_notification_rule_name UNIQUE (tenant_id, name) -); -CREATE INDEX IF NOT EXISTS idx_notification_rule_tenant_id_trigger_type_created_time ON notification_rule(tenant_id, trigger_type, created_time DESC); - -CREATE TABLE IF NOT EXISTS notification_request ( - id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, - created_time BIGINT NOT NULL, - tenant_id UUID NOT NULL, - targets VARCHAR(10000) NOT NULL, - template_id UUID, - template VARCHAR(10000000), - info VARCHAR(1000), - additional_config VARCHAR(1000), - originator_entity_id UUID, - originator_entity_type VARCHAR(32), - rule_id UUID NULL, - status VARCHAR(32), - stats VARCHAR(10000) -); -CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_user_created_time ON notification_request(tenant_id, created_time DESC) - WHERE originator_entity_type = 'USER'; -CREATE INDEX IF NOT EXISTS idx_notification_request_rule_id_originator_entity_id ON notification_request(rule_id, originator_entity_id) - WHERE originator_entity_type = 'ALARM'; -CREATE INDEX IF NOT EXISTS idx_notification_request_status ON notification_request(status) - WHERE status = 'SCHEDULED'; - -CREATE TABLE IF NOT EXISTS notification ( - id UUID NOT NULL, - created_time BIGINT NOT NULL, - request_id UUID NULL CONSTRAINT fk_notification_request_id REFERENCES notification_request(id) ON DELETE CASCADE, - recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE, - type VARCHAR(50) NOT NULL, - subject VARCHAR(255), - body VARCHAR(1000) NOT NULL, - additional_config VARCHAR(1000), - status VARCHAR(32) -) PARTITION BY RANGE (created_time); -CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); -CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC); - --- NOTIFICATIONS END - -ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255); - -CREATE TABLE IF NOT EXISTS user_settings ( - 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) -); - --- TTL DROP PARTITIONS FUNCTIONS UPDATE START - -DROP PROCEDURE IF EXISTS drop_partitions_by_max_ttl(character varying, bigint, bigint); -DROP FUNCTION IF EXISTS get_partition_by_max_ttl_date; - -CREATE OR REPLACE FUNCTION get_partition_by_system_ttl_date(IN partition_type varchar, IN date timestamp, OUT partition varchar) AS -$$ -BEGIN - CASE - WHEN partition_type = 'DAYS' THEN - partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM') || '_' || to_char(date, 'dd'); - WHEN partition_type = 'MONTHS' THEN - partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM'); - WHEN partition_type = 'YEARS' THEN - partition := 'ts_kv_' || to_char(date, 'yyyy'); - ELSE - partition := NULL; - END CASE; - IF partition IS NOT NULL THEN - IF NOT EXISTS(SELECT - FROM pg_tables - WHERE schemaname = 'public' - AND tablename = partition) THEN - partition := NULL; - RAISE NOTICE 'Failed to found partition by ttl'; - END IF; - END IF; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE PROCEDURE drop_partitions_by_system_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint) - LANGUAGE plpgsql AS -$$ -DECLARE - date timestamp; - partition_by_max_ttl_date varchar; - partition_by_max_ttl_month varchar; - partition_by_max_ttl_day varchar; - partition_by_max_ttl_year varchar; - partition varchar; - partition_year integer; - partition_month integer; - partition_day integer; - -BEGIN - if system_ttl IS NOT NULL AND system_ttl > 0 THEN - date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - system_ttl); - partition_by_max_ttl_date := get_partition_by_system_ttl_date(partition_type, date); - RAISE NOTICE 'Date by max ttl: %', date; - RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; - IF partition_by_max_ttl_date IS NOT NULL THEN - CASE - WHEN partition_type = 'DAYS' THEN - partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); - partition_by_max_ttl_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); - WHEN partition_type = 'MONTHS' THEN - partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); - ELSE - partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - END CASE; - IF partition_by_max_ttl_year IS NULL THEN - RAISE NOTICE 'Failed to remove partitions by max ttl date due to partition_by_max_ttl_year is null!'; - ELSE - IF partition_type = 'YEARS' THEN - FOR partition IN SELECT tablename - FROM pg_tables - WHERE schemaname = 'public' - AND tablename like 'ts_kv_' || '%' - AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' - AND tablename != 'ts_kv_indefinite' - AND tablename != partition_by_max_ttl_date - LOOP - partition_year := SPLIT_PART(partition, '_', 3)::integer; - IF partition_year < partition_by_max_ttl_year::integer THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition; - EXECUTE format('DROP TABLE IF EXISTS %I', partition); - deleted := deleted + 1; - END IF; - END LOOP; - ELSE - IF partition_type = 'MONTHS' THEN - IF partition_by_max_ttl_month IS NULL THEN - RAISE NOTICE 'Failed to remove months partitions by max ttl date due to partition_by_max_ttl_month is null!'; - ELSE - FOR partition IN SELECT tablename - FROM pg_tables - WHERE schemaname = 'public' - AND tablename like 'ts_kv_' || '%' - AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' - AND tablename != 'ts_kv_indefinite' - AND tablename != partition_by_max_ttl_date - LOOP - partition_year := SPLIT_PART(partition, '_', 3)::integer; - IF partition_year > partition_by_max_ttl_year::integer THEN - RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; - CONTINUE; - ELSE - IF partition_year < partition_by_max_ttl_year::integer THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition; - EXECUTE format('DROP TABLE IF EXISTS %I', partition); - deleted := deleted + 1; - ELSE - partition_month := SPLIT_PART(partition, '_', 4)::integer; - IF partition_year = partition_by_max_ttl_year::integer THEN - IF partition_month >= partition_by_max_ttl_month::integer THEN - RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; - CONTINUE; - ELSE - RAISE NOTICE 'Partition to delete by max ttl: %', partition; - EXECUTE format('DROP TABLE IF EXISTS %I', partition); - deleted := deleted + 1; - END IF; - END IF; - END IF; - END IF; - END LOOP; - END IF; - ELSE - IF partition_type = 'DAYS' THEN - IF partition_by_max_ttl_month IS NULL THEN - RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_month is null!'; - ELSE - IF partition_by_max_ttl_day IS NULL THEN - RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_day is null!'; - ELSE - FOR partition IN SELECT tablename - FROM pg_tables - WHERE schemaname = 'public' - AND tablename like 'ts_kv_' || '%' - AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' - AND tablename != 'ts_kv_indefinite' - AND tablename != partition_by_max_ttl_date - LOOP - partition_year := SPLIT_PART(partition, '_', 3)::integer; - IF partition_year > partition_by_max_ttl_year::integer THEN - RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; - CONTINUE; - ELSE - IF partition_year < partition_by_max_ttl_year::integer THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition; - EXECUTE format('DROP TABLE IF EXISTS %I', partition); - deleted := deleted + 1; - ELSE - partition_month := SPLIT_PART(partition, '_', 4)::integer; - IF partition_month > partition_by_max_ttl_month::integer THEN - RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; - CONTINUE; - ELSE - IF partition_month < partition_by_max_ttl_month::integer THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition; - EXECUTE format('DROP TABLE IF EXISTS %I', partition); - deleted := deleted + 1; - ELSE - partition_day := SPLIT_PART(partition, '_', 5)::integer; - IF partition_day >= partition_by_max_ttl_day::integer THEN - RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; - CONTINUE; - ELSE - IF partition_day < partition_by_max_ttl_day::integer THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition; - EXECUTE format('DROP TABLE IF EXISTS %I', partition); - deleted := deleted + 1; - END IF; - END IF; - END IF; - END IF; - END IF; - END IF; - END LOOP; - END IF; - END IF; - END IF; - END IF; - END IF; - END IF; - END IF; - END IF; -END -$$; - --- TTL DROP PARTITIONS FUNCTIONS UPDATE END - --- RULE NODE SINGLETON MODE SUPPORT - -ALTER TABLE rule_node ADD COLUMN IF NOT EXISTS singleton_mode bool DEFAULT false; - -UPDATE rule_node SET singleton_mode = true WHERE type IN ('org.thingsboard.rule.engine.mqtt.azure.TbAzureIotHubNode', 'org.thingsboard.rule.engine.mqtt.TbMqttNode'); - -ALTER TABLE component_descriptor ADD COLUMN IF NOT EXISTS clustering_mode varchar(255) DEFAULT 'ENABLED'; - -UPDATE component_descriptor SET clustering_mode = 'USER_PREFERENCE' WHERE clazz = 'org.thingsboard.rule.engine.mqtt.TbMqttNode'; - -UPDATE component_descriptor SET clustering_mode = 'SINGLETON' WHERE clazz = 'org.thingsboard.rule.engine.mqtt.azure.TbAzureIotHubNode'; - diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 85a6e061d0..e8339e3202 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -24,14 +24,12 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.install.DatabaseEntitiesUpgradeService; -import org.thingsboard.server.service.install.DatabaseTsUpgradeService; import org.thingsboard.server.service.install.EntityDatabaseSchemaService; import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.install.NoSqlKeyspaceService; import org.thingsboard.server.service.install.SystemDataLoaderService; import org.thingsboard.server.service.install.TsDatabaseSchemaService; import org.thingsboard.server.service.install.TsLatestDatabaseSchemaService; -import org.thingsboard.server.service.install.migrate.EntitiesMigrateService; import org.thingsboard.server.service.install.migrate.TsLatestMigrateService; import org.thingsboard.server.service.install.update.CacheCleanupService; import org.thingsboard.server.service.install.update.DataUpdateService; @@ -70,9 +68,6 @@ public class ThingsboardInstallService { @Autowired private DatabaseEntitiesUpgradeService databaseEntitiesUpgradeService; - @Autowired(required = false) - private DatabaseTsUpgradeService databaseTsUpgradeService; - @Autowired private ComponentDiscoveryService componentDiscoveryService; @@ -88,9 +83,6 @@ public class ThingsboardInstallService { @Autowired private CacheCleanupService cacheCleanupService; - @Autowired(required = false) - private EntitiesMigrateService entitiesMigrateService; - @Autowired(required = false) private TsLatestMigrateService latestMigrateService; @@ -104,158 +96,13 @@ public class ThingsboardInstallService { cacheCleanupService.clearCache(upgradeFromVersion); - if ("2.5.0-cassandra".equals(upgradeFromVersion)) { - log.info("Migrating ThingsBoard entities data from cassandra to SQL database ..."); - entitiesMigrateService.migrate(); - log.info("Updating system data..."); - systemDataLoaderService.loadSystemWidgets(); - } else if ("3.0.1-cassandra".equals(upgradeFromVersion)) { + if ("cassandra-latest-to-postgres".equals(upgradeFromVersion)) { log.info("Migrating ThingsBoard latest timeseries data from cassandra to SQL database ..."); latestMigrateService.migrate(); } else if (upgradeFromVersion.equals("3.6.2-images")) { installScripts.updateImages(); } else { switch (upgradeFromVersion) { - case "1.2.3": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion - log.info("Upgrading ThingsBoard from version 1.2.3 to 1.3.0 ..."); - - databaseEntitiesUpgradeService.upgradeDatabase("1.2.3"); - - case "1.3.0": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion - log.info("Upgrading ThingsBoard from version 1.3.0 to 1.3.1 ..."); - - databaseEntitiesUpgradeService.upgradeDatabase("1.3.0"); - - case "1.3.1": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion - log.info("Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ..."); - - databaseEntitiesUpgradeService.upgradeDatabase("1.3.1"); - - case "1.4.0": - log.info("Upgrading ThingsBoard from version 1.4.0 to 2.0.0 ..."); - - databaseEntitiesUpgradeService.upgradeDatabase("1.4.0"); - - dataUpdateService.updateData("1.4.0"); - - case "2.0.0": - log.info("Upgrading ThingsBoard from version 2.0.0 to 2.1.1 ..."); - - databaseEntitiesUpgradeService.upgradeDatabase("2.0.0"); - - case "2.1.1": - log.info("Upgrading ThingsBoard from version 2.1.1 to 2.1.2 ..."); - - databaseEntitiesUpgradeService.upgradeDatabase("2.1.1"); - case "2.1.3": - log.info("Upgrading ThingsBoard from version 2.1.3 to 2.2.0 ..."); - - databaseEntitiesUpgradeService.upgradeDatabase("2.1.3"); - - case "2.3.0": - log.info("Upgrading ThingsBoard from version 2.3.0 to 2.3.1 ..."); - - databaseEntitiesUpgradeService.upgradeDatabase("2.3.0"); - - case "2.3.1": - log.info("Upgrading ThingsBoard from version 2.3.1 to 2.4.0 ..."); - - databaseEntitiesUpgradeService.upgradeDatabase("2.3.1"); - - case "2.4.0": - log.info("Upgrading ThingsBoard from version 2.4.0 to 2.4.1 ..."); - - case "2.4.1": - log.info("Upgrading ThingsBoard from version 2.4.1 to 2.4.2 ..."); - - databaseEntitiesUpgradeService.upgradeDatabase("2.4.1"); - case "2.4.2": - log.info("Upgrading ThingsBoard from version 2.4.2 to 2.4.3 ..."); - - databaseEntitiesUpgradeService.upgradeDatabase("2.4.2"); - - case "2.4.3": - log.info("Upgrading ThingsBoard from version 2.4.3 to 2.5 ..."); - - if (databaseTsUpgradeService != null) { - databaseTsUpgradeService.upgradeDatabase("2.4.3"); - } - databaseEntitiesUpgradeService.upgradeDatabase("2.4.3"); - case "2.5.0": - log.info("Upgrading ThingsBoard from version 2.5.0 to 2.5.1 ..."); - if (databaseTsUpgradeService != null) { - databaseTsUpgradeService.upgradeDatabase("2.5.0"); - } - case "2.5.1": - log.info("Upgrading ThingsBoard from version 2.5.1 to 3.0.0 ..."); - case "3.0.1": - log.info("Upgrading ThingsBoard from version 3.0.1 to 3.1.0 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.0.1"); - dataUpdateService.updateData("3.0.1"); - case "3.1.0": - log.info("Upgrading ThingsBoard from version 3.1.0 to 3.1.1 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.1.0"); - case "3.1.1": - log.info("Upgrading ThingsBoard from version 3.1.1 to 3.2.0 ..."); - if (databaseTsUpgradeService != null) { - databaseTsUpgradeService.upgradeDatabase("3.1.1"); - } - databaseEntitiesUpgradeService.upgradeDatabase("3.1.1"); - dataUpdateService.updateData("3.1.1"); - systemDataLoaderService.createOAuth2Templates(); - case "3.2.0": - log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.2.0"); - case "3.2.1": - log.info("Upgrading ThingsBoard from version 3.2.1 to 3.2.2 ..."); - if (databaseTsUpgradeService != null) { - databaseTsUpgradeService.upgradeDatabase("3.2.1"); - } - databaseEntitiesUpgradeService.upgradeDatabase("3.2.1"); - case "3.2.2": - log.info("Upgrading ThingsBoard from version 3.2.2 to 3.3.0 ..."); - if (databaseTsUpgradeService != null) { - databaseTsUpgradeService.upgradeDatabase("3.2.2"); - } - databaseEntitiesUpgradeService.upgradeDatabase("3.2.2"); - - dataUpdateService.updateData("3.2.2"); - systemDataLoaderService.createOAuth2Templates(); - case "3.3.0": - log.info("Upgrading ThingsBoard from version 3.3.0 to 3.3.1 ..."); - case "3.3.1": - log.info("Upgrading ThingsBoard from version 3.3.1 to 3.3.2 ..."); - case "3.3.2": - log.info("Upgrading ThingsBoard from version 3.3.2 to 3.3.3 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.3.2"); - dataUpdateService.updateData("3.3.2"); - case "3.3.3": - log.info("Upgrading ThingsBoard from version 3.3.3 to 3.3.4 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.3.3"); - case "3.3.4": - log.info("Upgrading ThingsBoard from version 3.3.4 to 3.4.0 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.3.4"); - dataUpdateService.updateData("3.3.4"); - case "3.4.0": - log.info("Upgrading ThingsBoard from version 3.4.0 to 3.4.1 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.4.0"); - dataUpdateService.updateData("3.4.0"); - case "3.4.1": - log.info("Upgrading ThingsBoard from version 3.4.1 to 3.4.2 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.4.1"); - dataUpdateService.updateData("3.4.1"); - case "3.4.2": - log.info("Upgrading ThingsBoard from version 3.4.2 to 3.4.3 ..."); - case "3.4.3": - log.info("Upgrading ThingsBoard from version 3.4.3 to 3.4.4 ..."); - case "3.4.4": - log.info("Upgrading ThingsBoard from version 3.4.4 to 3.5.0 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.4.4"); - if (!getEnv("SKIP_DEFAULT_NOTIFICATION_CONFIGS_CREATION", false)) { - systemDataLoaderService.createDefaultNotificationConfigs(); - } else { - log.info("Skipping default notification configs creation"); - } case "3.5.0": log.info("Upgrading ThingsBoard from version 3.5.0 to 3.5.1 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.5.0"); diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java index c1297d7cfa..2937b71974 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.install; -import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; @@ -30,29 +29,6 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase @Override public void upgradeDatabase(String fromVersion) throws Exception { switch (fromVersion) { - case "2.4.3": - log.info("Updating schema ..."); - String updateTsKvTableStmt = "alter table ts_kv_cf add json_v text"; - String updateTsKvLatestTableStmt = "alter table ts_kv_latest_cf add json_v text"; - - try { - log.info("Updating ts ..."); - cluster.getSession().execute(updateTsKvTableStmt); - Thread.sleep(2500); - log.info("Ts updated."); - log.info("Updating ts latest ..."); - cluster.getSession().execute(updateTsKvLatestTableStmt); - Thread.sleep(2500); - log.info("Ts latest updated."); - } catch (InvalidQueryException e) { - } - log.info("Schema updated."); - break; - case "2.5.0": - case "3.1.1": - case "3.2.1": - case "3.2.2": - break; default: throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java deleted file mode 100644 index 89a8dd9187..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright © 2016-2024 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.install; - -import com.fasterxml.jackson.databind.JavaType; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVParser; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.ShortCustomerInfo; -import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.UUIDConverter; -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.dao.dashboard.DashboardService; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; - -/** - * Created by igor on 2/27/18. - */ -@Slf4j -public class DatabaseHelper { - - public static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N"); - - public static final String DEVICE = "device"; - public static final String ENTITY_ID = "entity_id"; - public static final String TENANT_ID = "tenant_id"; - public static final String ENTITY_TYPE = "entity_type"; - public static final String CUSTOMER_ID = "customer_id"; - public static final String SEARCH_TEXT = "search_text"; - public static final String ADDITIONAL_INFO = "additional_info"; - public static final String ASSET = "asset"; - public static final String DASHBOARD = "dashboard"; - public static final String ENTITY_VIEWS = "entity_views"; - public static final String ENTITY_VIEW = "entity_view"; - public static final String RULE_CHAIN = "rule_chain"; - public static final String ID = "id"; - public static final String TITLE = "title"; - public static final String TYPE = "type"; - public static final String NAME = "name"; - public static final String KEYS = "keys"; - public static final String START_TS = "start_ts"; - public static final String END_TS = "end_ts"; - public static final String ASSIGNED_CUSTOMERS = "assigned_customers"; - public static final String CONFIGURATION = "configuration"; - - - public static void upgradeTo40_assignDashboards(Path dashboardsDump, DashboardService dashboardService, boolean sql) throws Exception { - JavaType assignedCustomersType = - JacksonUtil.constructCollectionType(HashSet.class, ShortCustomerInfo.class); - try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(dashboardsDump), CSV_DUMP_FORMAT.withFirstRecordAsHeader())) { - csvParser.forEach(record -> { - String customerIdString = record.get(CUSTOMER_ID); - String assignedCustomersString = record.get(ASSIGNED_CUSTOMERS); - DashboardId dashboardId = new DashboardId(toUUID(record.get(ID), sql)); - List customerIds = new ArrayList<>(); - if (!StringUtils.isEmpty(assignedCustomersString)) { - try { - Set assignedCustomers = JacksonUtil.fromString(assignedCustomersString, assignedCustomersType); - assignedCustomers.forEach((customerInfo) -> { - CustomerId customerId = customerInfo.getCustomerId(); - if (!customerId.isNullUid()) { - customerIds.add(customerId); - } - }); - } catch (IllegalArgumentException e) { - log.error("Unable to parse assigned customers field", e); - } - } - if (!StringUtils.isEmpty(customerIdString)) { - CustomerId customerId = new CustomerId(toUUID(customerIdString, sql)); - if (!customerId.isNullUid()) { - customerIds.add(customerId); - } - } - for (CustomerId customerId : customerIds) { - dashboardService.assignDashboardToCustomer(TenantId.SYS_TENANT_ID, dashboardId, customerId); - } - }); - } - } - - private static UUID toUUID(String src, boolean sql) { - if (sql) { - return UUIDConverter.fromString(src); - } else { - return UUID.fromString(src); - } - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index ddc848a91d..b9f4dc482f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -297,11 +297,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { jwtSettingsService.createRandomJwtSettings(); } - @Override - public void saveLegacyYmlSettings() throws Exception { - jwtSettingsService.saveLegacyYmlSettings(); - } - @Override public void createOAuth2Templates() throws Exception { installScripts.createOAuth2Templates(); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 1b42ebcb8d..436c2fcc84 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -15,37 +15,11 @@ */ package org.thingsboard.server.service.install; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.Tenant; -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.common.data.queue.ProcessingStrategy; -import org.thingsboard.server.common.data.queue.ProcessingStrategyType; -import org.thingsboard.server.common.data.queue.Queue; -import org.thingsboard.server.common.data.queue.SubmitStrategy; -import org.thingsboard.server.common.data.queue.SubmitStrategyType; -import org.thingsboard.server.common.data.util.TbPair; -import org.thingsboard.server.dao.asset.AssetDao; -import org.thingsboard.server.dao.asset.AssetProfileService; -import org.thingsboard.server.dao.dashboard.DashboardService; -import org.thingsboard.server.dao.device.DeviceProfileService; -import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.queue.QueueService; -import org.thingsboard.server.dao.sql.tenant.TenantRepository; -import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; -import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; -import org.thingsboard.server.service.install.sql.SqlDbHelper; import org.thingsboard.server.service.install.update.DefaultDataUpdateService; import java.nio.charset.Charset; @@ -56,34 +30,11 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.SQLSyntaxErrorException; import java.sql.SQLWarning; import java.sql.Statement; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO; -import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS; -import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION; -import static org.thingsboard.server.service.install.DatabaseHelper.CUSTOMER_ID; -import static org.thingsboard.server.service.install.DatabaseHelper.DASHBOARD; -import static org.thingsboard.server.service.install.DatabaseHelper.END_TS; -import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_ID; -import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_TYPE; -import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEW; -import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEWS; -import static org.thingsboard.server.service.install.DatabaseHelper.ID; -import static org.thingsboard.server.service.install.DatabaseHelper.KEYS; -import static org.thingsboard.server.service.install.DatabaseHelper.NAME; -import static org.thingsboard.server.service.install.DatabaseHelper.SEARCH_TEXT; -import static org.thingsboard.server.service.install.DatabaseHelper.START_TS; -import static org.thingsboard.server.service.install.DatabaseHelper.TENANT_ID; -import static org.thingsboard.server.service.install.DatabaseHelper.TITLE; -import static org.thingsboard.server.service.install.DatabaseHelper.TYPE; - @Service @Profile("install") @Slf4j @@ -100,623 +51,12 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @Value("${spring.datasource.password}") private String dbPassword; - @Autowired - private DashboardService dashboardService; - @Autowired private InstallScripts installScripts; - @Autowired - private SystemDataLoaderService systemDataLoaderService; - - @Autowired - private TenantService tenantService; - - @Autowired - private TenantRepository tenantRepository; - - @Autowired - private DeviceService deviceService; - - @Autowired - private AssetDao assetDao; - - @Autowired - private DeviceProfileService deviceProfileService; - - @Autowired - private AssetProfileService assetProfileService; - - @Autowired - private ApiUsageStateService apiUsageStateService; - - @Lazy - @Autowired - private QueueService queueService; - - @Autowired - private TbRuleEngineQueueConfigService queueConfig; - - @Autowired - private DbUpgradeExecutorService dbUpgradeExecutor; - @Override public void upgradeDatabase(String fromVersion) throws Exception { switch (fromVersion) { - case "1.3.0": - log.info("Updating schema ..."); - Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.1", SCHEMA_UPDATE_SQL); - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - loadSql(schemaUpdateFile, conn); - } - log.info("Schema updated."); - break; - case "1.3.1": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - - log.info("Dumping dashboards ..."); - Path dashboardsDump = SqlDbHelper.dumpTableIfExists(conn, DASHBOARD, - new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION}, - new String[]{"", "", "", "", "", "", ""}, - "tb-dashboards", true); - log.info("Dashboards dumped."); - - log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.4.0", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - log.info("Schema updated."); - - log.info("Restoring dashboards ..."); - if (dashboardsDump != null) { - SqlDbHelper.loadTable(conn, DASHBOARD, - new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump, true); - DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, true); - Files.deleteIfExists(dashboardsDump); - } - log.info("Dashboards restored."); - } - break; - case "1.4.0": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.0.0", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - log.info("Schema updated."); - } - break; - case "2.0.0": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.1", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - log.info("Schema updated."); - } - break; - case "2.1.1": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - - log.info("Dumping entity views ..."); - Path entityViewsDump = SqlDbHelper.dumpTableIfExists(conn, ENTITY_VIEWS, - new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, TYPE, NAME, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO}, - new String[]{"", "", "", "", "", "default", "", "", "0", "0", "", ""}, - "tb-entity-views", true); - log.info("Entity views dumped."); - - log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.2", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - log.info("Schema updated."); - - log.info("Restoring entity views ..."); - if (entityViewsDump != null) { - SqlDbHelper.loadTable(conn, ENTITY_VIEW, - new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, TYPE, NAME, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO}, entityViewsDump, true); - Files.deleteIfExists(entityViewsDump); - } - log.info("Entity views restored."); - } - break; - case "2.1.3": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.2.0", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - log.info("Schema updated."); - } - break; - case "2.3.0": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.3.1", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - log.info("Schema updated."); - } - break; - case "2.3.1": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.0", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - try { - conn.createStatement().execute("ALTER TABLE device ADD COLUMN label varchar(255)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } - log.info("Schema updated."); - } - break; - case "2.4.1": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - try { - conn.createStatement().execute("ALTER TABLE asset ADD COLUMN label varchar(255)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.2", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - try { - conn.createStatement().execute("ALTER TABLE device ADD CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } - try { - conn.createStatement().execute("ALTER TABLE device_credentials ADD CONSTRAINT device_credentials_id_unq_key UNIQUE (credentials_id)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } - try { - conn.createStatement().execute("ALTER TABLE asset ADD CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } - log.info("Schema updated."); - } - break; - case "2.4.2": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - try { - conn.createStatement().execute("ALTER TABLE alarm ADD COLUMN propagate_relation_types varchar"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } - log.info("Schema updated."); - } - break; - case "2.4.3": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - try { - conn.createStatement().execute("ALTER TABLE attribute_kv ADD COLUMN json_v json;"); - } catch (Exception e) { - if (e instanceof SQLSyntaxErrorException) { - try { - conn.createStatement().execute("ALTER TABLE attribute_kv ADD COLUMN json_v varchar(10000000);"); - } catch (Exception e1) { - } - } - } - try { - conn.createStatement().execute("ALTER TABLE tenant ADD COLUMN isolated_tb_core boolean DEFAULT (false), ADD COLUMN isolated_tb_rule_engine boolean DEFAULT (false)"); - } catch (Exception e) { - } - try { - long ts = System.currentTimeMillis(); - conn.createStatement().execute("ALTER TABLE event ADD COLUMN ts bigint DEFAULT " + ts + ";"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } - log.info("Schema updated."); - } - break; - case "3.0.1": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - if (isOldSchema(conn, 3000001)) { - String[] tables = new String[]{"admin_settings", "alarm", "asset", "audit_log", "attribute_kv", - "component_descriptor", "customer", "dashboard", "device", "device_credentials", "event", - "relation", "tb_user", "tenant", "user_credentials", "widget_type", "widgets_bundle", - "rule_chain", "rule_node", "entity_view"}; - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.0.1", "schema_update_to_uuid.sql"); - loadSql(schemaUpdateFile, conn); - - conn.createStatement().execute("call drop_all_idx()"); - - log.info("Optimizing alarm relations..."); - conn.createStatement().execute("DELETE from relation WHERE relation_type_group = 'ALARM' AND relation_type <> 'ALARM_ANY';"); - conn.createStatement().execute("DELETE from relation WHERE relation_type_group = 'ALARM' AND relation_type = 'ALARM_ANY' " + - "AND exists(SELECT * FROM alarm WHERE alarm.id = relation.to_id AND alarm.originator_id = relation.from_id)"); - log.info("Alarm relations optimized."); - - for (String table : tables) { - log.info("Updating table {}.", table); - Statement statement = conn.createStatement(); - statement.execute("call update_" + table + "();"); - - SQLWarning warnings = statement.getWarnings(); - if (warnings != null) { - log.info("{}", warnings.getMessage()); - SQLWarning nextWarning = warnings.getNextWarning(); - while (nextWarning != null) { - log.info("{}", nextWarning.getMessage()); - nextWarning = nextWarning.getNextWarning(); - } - } - - conn.createStatement().execute("DROP PROCEDURE update_" + table); - log.info("Table {} updated.", table); - } - conn.createStatement().execute("call create_all_idx()"); - - conn.createStatement().execute("DROP PROCEDURE drop_all_idx"); - conn.createStatement().execute("DROP PROCEDURE create_all_idx"); - conn.createStatement().execute("DROP FUNCTION column_type_to_uuid"); - - log.info("Updating alarm relations..."); - conn.createStatement().execute("UPDATE relation SET relation_type = 'ANY' WHERE relation_type_group = 'ALARM' AND relation_type = 'ALARM_ANY';"); - log.info("Alarm relations updated."); - - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3001000;"); - - conn.createStatement().execute("VACUUM FULL"); - } - log.info("Schema updated."); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } - break; - case "3.1.0": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.0", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - log.info("Schema updated."); - } - break; - case "3.1.1": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - if (isOldSchema(conn, 3001000)) { - - try { - conn.createStatement().execute("ALTER TABLE device ADD COLUMN device_profile_id uuid, ADD COLUMN device_data jsonb"); - } catch (Exception e) { - } - - try { - conn.createStatement().execute("ALTER TABLE tenant ADD COLUMN tenant_profile_id uuid"); - } catch (Exception e) { - } - - try { - conn.createStatement().execute("CREATE TABLE IF NOT EXISTS rule_node_state (" + - " id uuid NOT NULL CONSTRAINT rule_node_state_pkey PRIMARY KEY," + - " created_time bigint NOT NULL," + - " rule_node_id uuid NOT NULL," + - " entity_type varchar(32) NOT NULL," + - " entity_id uuid NOT NULL," + - " state_data varchar(16384) NOT NULL," + - " CONSTRAINT rule_node_state_unq_key UNIQUE (rule_node_id, entity_id)," + - " CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE)"); - } catch (Exception e) { - } - - try { - conn.createStatement().execute("CREATE TABLE IF NOT EXISTS api_usage_state (" + - " id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY," + - " created_time bigint NOT NULL," + - " tenant_id uuid," + - " entity_type varchar(32)," + - " entity_id uuid," + - " transport varchar(32)," + - " db_storage varchar(32)," + - " re_exec varchar(32)," + - " js_exec varchar(32)," + - " email_exec varchar(32)," + - " sms_exec varchar(32)," + - " CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)\n" + - ");"); - } catch (Exception e) { - } - - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update_before.sql"); - loadSql(schemaUpdateFile, conn); - - log.info("Creating default tenant profiles..."); - systemDataLoaderService.createDefaultTenantProfiles(); - - log.info("Updating tenant profiles..."); - conn.createStatement().execute("call update_tenant_profiles()"); - - log.info("Creating default device profiles..."); - PageLink pageLink = new PageLink(100); - PageData pageData; - do { - pageData = tenantService.findTenants(pageLink); - for (Tenant tenant : pageData.getData()) { - try { - apiUsageStateService.createDefaultApiUsageState(tenant.getId(), null); - } catch (Exception e) { - } - List deviceTypes = deviceService.findDeviceTypesByTenantId(tenant.getId()).get(); - try { - deviceProfileService.createDefaultDeviceProfile(tenant.getId()); - } catch (Exception e) { - } - for (EntitySubtype deviceType : deviceTypes) { - try { - deviceProfileService.findOrCreateDeviceProfile(tenant.getId(), deviceType.getType()); - } catch (Exception e) { - } - } - } - pageLink = pageLink.nextPageLink(); - } while (pageData.hasNext()); - - log.info("Updating device profiles..."); - conn.createStatement().execute("call update_device_profiles()"); - - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update_after.sql"); - loadSql(schemaUpdateFile, conn); - - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3002000;"); - } - log.info("Schema updated."); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } - break; - case "3.2.0": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - try { - conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_device_device_profile_id ON device(tenant_id, device_profile_id);"); - conn.createStatement().execute("ALTER TABLE dashboard ALTER COLUMN configuration TYPE varchar;"); - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3002001;"); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } - log.info("Schema updated."); - } - break; - case "3.2.1": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_audit_log_tenant_id_and_created_time ON audit_log(tenant_id, created_time);"); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.1", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3002002;"); - log.info("Schema updated."); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } - break; - case "3.2.2": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - try { - conn.createStatement().execute("ALTER TABLE rule_chain ADD COLUMN type varchar(255) DEFAULT 'CORE'"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception ignored) { - } - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.2", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - log.info("Load Edge TTL functions ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.2", "schema_update_ttl.sql"); - loadSql(schemaUpdateFile, conn); - log.info("Edge TTL functions successfully loaded!"); - log.info("Updating indexes and TTL procedure for event table..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.2", "schema_update_event.sql"); - loadSql(schemaUpdateFile, conn); - log.info("Updating schema settings..."); - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;"); - log.info("Schema updated."); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } - break; - case "3.3.2": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.2", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - try { - conn.createStatement().execute("ALTER TABLE alarm ADD COLUMN propagate_to_owner boolean DEFAULT false;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - conn.createStatement().execute("ALTER TABLE alarm ADD COLUMN propagate_to_tenant boolean DEFAULT false;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception ignored) { - } - - try { - conn.createStatement().execute("insert into entity_alarm(tenant_id, entity_id, created_time, alarm_type, customer_id, alarm_id)" + - " select tenant_id, originator_id, created_time, type, customer_id, id from alarm ON CONFLICT DO NOTHING;"); - conn.createStatement().execute("insert into entity_alarm(tenant_id, entity_id, created_time, alarm_type, customer_id, alarm_id)" + - " select a.tenant_id, r.from_id, created_time, type, customer_id, id" + - " from alarm a inner join relation r on r.relation_type_group = 'ALARM' and r.relation_type = 'ANY' and a.id = r.to_id ON CONFLICT DO NOTHING;"); - conn.createStatement().execute("delete from relation r where r.relation_type_group = 'ALARM';"); - } catch (Exception e) { - log.error("Failed to update alarm relations!!!", e); - } - - log.info("Updating lwm2m device profiles ..."); - try { - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.2", "schema_update_lwm2m_bootstrap.sql"); - loadSql(schemaUpdateFile, conn); - log.info("Updating server`s public key from HexDec to Base64 in profile for LWM2M..."); - conn.createStatement().execute("call update_profile_bootstrap();"); - log.info("Server`s public key from HexDec to Base64 in profile for LWM2M updated."); - log.info("Updating client`s public key and secret key from HexDec to Base64 for LWM2M..."); - conn.createStatement().execute("call update_device_credentials_to_base64_and_bootstrap();"); - log.info("Client`s public key and secret key from HexDec to Base64 for LWM2M updated."); - } catch (Exception e) { - log.error("Failed to update lwm2m profiles!!!", e); - } - log.info("Updating schema settings..."); - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003003;"); - log.info("Schema updated."); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } - break; - case "3.3.3": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - try { - conn.createStatement().execute("ALTER TABLE edge DROP COLUMN edge_license_key;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - conn.createStatement().execute("ALTER TABLE edge DROP COLUMN cloud_endpoint;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception ignored) { - } - - log.info("Updating TTL cleanup procedure for the event table..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", "schema_event_ttl_procedure.sql"); - loadSql(schemaUpdateFile, conn); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - - log.info("Updating schema settings..."); - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003004;"); - log.info("Schema updated."); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } - break; - case "3.3.4": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.4", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - - log.info("Loading queues..."); - try { - if (!CollectionUtils.isEmpty(queueConfig.getQueues())) { - queueConfig.getQueues().forEach(queueSettings -> { - Queue queue = queueConfigToQueue(queueSettings); - Queue existing = queueService.findQueueByTenantIdAndName(queue.getTenantId(), queue.getName()); - if (existing == null) { - queueService.saveQueue(queue); - } - }); - } else { - systemDataLoaderService.createQueues(); - } - } catch (Exception e) { - } - - log.info("Updating schema settings..."); - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004000;"); - log.info("Schema updated."); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } - break; - case "3.4.0": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.0", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - log.info("Updating schema settings..."); - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004001;"); - log.info("Schema updated."); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } - break; - case "3.4.1": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - runSchemaUpdateScript(conn, "3.4.1"); - if (isOldSchema(conn, 3004001)) { - try { - conn.createStatement().execute("ALTER TABLE asset ADD COLUMN asset_profile_id uuid"); - } catch (Exception e) { - } - - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.1", "schema_update_before.sql"); - loadSql(schemaUpdateFile, conn); - - conn.createStatement().execute("DELETE FROM asset a WHERE NOT exists(SELECT id FROM tenant WHERE id = a.tenant_id);"); - - log.info("Creating default asset profiles..."); - - PageLink pageLink = new PageLink(1000); - PageData tenantIds; - do { - List> futures = new ArrayList<>(); - tenantIds = tenantService.findTenantsIds(pageLink); - for (TenantId tenantId : tenantIds.getData()) { - futures.add(dbUpgradeExecutor.submit(() -> { - try { - assetProfileService.createDefaultAssetProfile(tenantId); - } catch (Exception e) { - } - })); - } - Futures.allAsList(futures).get(); - pageLink = pageLink.nextPageLink(); - } while (tenantIds.hasNext()); - - pageLink = new PageLink(1000); - PageData> pairs; - do { - List> futures = new ArrayList<>(); - pairs = assetDao.getAllAssetTypes(pageLink); - for (TbPair pair : pairs.getData()) { - TenantId tenantId = new TenantId(pair.getFirst()); - String assetType = pair.getSecond(); - if (!"default".equals(assetType)) { - futures.add(dbUpgradeExecutor.submit(() -> { - try { - assetProfileService.findOrCreateAssetProfile(tenantId, assetType); - } catch (Exception e) { - } - })); - } - } - Futures.allAsList(futures).get(); - pageLink = pageLink.nextPageLink(); - } while (pairs.hasNext()); - - log.info("Updating asset profiles..."); - conn.createStatement().execute("call update_asset_profiles()"); - - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.1", "schema_update_after.sql"); - loadSql(schemaUpdateFile, conn); - - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004002;"); - } - log.info("Schema updated."); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } - break; - case "3.4.4": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating schema ..."); - if (isOldSchema(conn, 3004002)) { - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.4", SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, conn); - - try { - conn.createStatement().execute("VACUUM FULL ANALYZE alarm;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } - - try { - conn.createStatement().execute("ALTER TABLE asset_profile ADD COLUMN default_edge_rule_chain_id uuid"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } - try { - conn.createStatement().execute("ALTER TABLE device_profile ADD COLUMN default_edge_rule_chain_id uuid"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } - try { - conn.createStatement().execute("ALTER TABLE asset_profile ADD CONSTRAINT fk_default_edge_rule_chain_asset_profile FOREIGN KEY (default_edge_rule_chain_id) REFERENCES rule_chain(id)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } - try { - conn.createStatement().execute("ALTER TABLE device_profile ADD CONSTRAINT fk_default_edge_rule_chain_device_profile FOREIGN KEY (default_edge_rule_chain_id) REFERENCES rule_chain(id)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } - - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3005000;"); - } - log.info("Schema updated."); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } - break; case "3.5.0": updateSchema("3.5.0", 3005000, "3.5.1", 3005001, null); break; @@ -726,7 +66,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService "asset_profile", "asset", "device_profile", "tb_user", "tenant_profile", "tenant", "widgets_bundle", "entity_view", "edge"}; for (String entityName : entityNames) { try { - conn.createStatement().execute("ALTER TABLE " + entityName + " DROP COLUMN " + SEARCH_TEXT + " CASCADE"); + conn.createStatement().execute("ALTER TABLE " + entityName + " DROP COLUMN search_text CASCADE"); } catch (Exception e) { } } @@ -799,11 +139,6 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService } } - private void runSchemaUpdateScript(Connection connection, String version) throws Exception { - Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, SCHEMA_UPDATE_SQL); - loadSql(schemaUpdateFile, connection); - } - private void loadSql(Path sqlFile, Connection conn) throws Exception { String sql = new String(Files.readAllBytes(sqlFile), Charset.forName("UTF-8")); Statement st = conn.createStatement(); @@ -848,28 +183,4 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService } return isOldSchema; } - - private Queue queueConfigToQueue(TbRuleEngineQueueConfiguration queueSettings) { - Queue queue = new Queue(); - queue.setTenantId(TenantId.SYS_TENANT_ID); - queue.setName(queueSettings.getName()); - queue.setTopic(queueSettings.getTopic()); - queue.setPollInterval(queueSettings.getPollInterval()); - queue.setPartitions(queueSettings.getPartitions()); - queue.setPackProcessingTimeout(queueSettings.getPackProcessingTimeout()); - SubmitStrategy submitStrategy = new SubmitStrategy(); - submitStrategy.setBatchSize(queueSettings.getSubmitStrategy().getBatchSize()); - submitStrategy.setType(SubmitStrategyType.valueOf(queueSettings.getSubmitStrategy().getType())); - queue.setSubmitStrategy(submitStrategy); - ProcessingStrategy processingStrategy = new ProcessingStrategy(); - processingStrategy.setType(ProcessingStrategyType.valueOf(queueSettings.getProcessingStrategy().getType())); - processingStrategy.setRetries(queueSettings.getProcessingStrategy().getRetries()); - processingStrategy.setFailurePercentage(queueSettings.getProcessingStrategy().getFailurePercentage()); - processingStrategy.setPauseBetweenRetries(queueSettings.getProcessingStrategy().getPauseBetweenRetries()); - processingStrategy.setMaxPauseBetweenRetries(queueSettings.getProcessingStrategy().getMaxPauseBetweenRetries()); - queue.setProcessingStrategy(processingStrategy); - queue.setConsumerPerPartition(queueSettings.isConsumerPerPartition()); - return queue; - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java index 2de90fb9b0..ebe4cb8ef5 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java @@ -16,20 +16,13 @@ package org.thingsboard.server.service.install; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.SystemUtils; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.dao.util.SqlTsDao; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; -import java.sql.DriverManager; @Service @Profile("install") @@ -37,216 +30,14 @@ import java.sql.DriverManager; @SqlTsDao public class SqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { - @Value("${sql.postgres.ts_key_value_partitioning:MONTHS}") - private String partitionType; - - private static final String TS_KV_LATEST_SQL = "ts_kv_latest.sql"; - private static final String LOAD_FUNCTIONS_SQL = "schema_update_psql_ts.sql"; - private static final String LOAD_TTL_FUNCTIONS_SQL = "schema_update_ttl.sql"; - private static final String LOAD_DROP_PARTITIONS_FUNCTIONS_SQL = "schema_update_psql_drop_partitions.sql"; - - private static final String TS_KV_OLD = "ts_kv_old;"; - private static final String TS_KV_LATEST_OLD = "ts_kv_latest_old;"; - - private static final String CREATE_PARTITION_TS_KV_TABLE = "create_partition_ts_kv_table()"; - private static final String CREATE_NEW_TS_KV_LATEST_TABLE = "create_new_ts_kv_latest_table()"; - private static final String CREATE_PARTITIONS = "create_partitions(IN partition_type varchar)"; - private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()"; - private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()"; - private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv(IN path_to_file varchar)"; - private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest(IN path_to_file varchar)"; - private static final String INSERT_INTO_TS_KV_CURSOR = "insert_into_ts_kv_cursor()"; - private static final String INSERT_INTO_TS_KV_LATEST_CURSOR = "insert_into_ts_kv_latest_cursor()"; - - private static final String CALL_CREATE_PARTITION_TS_KV_TABLE = CALL_REGEX + CREATE_PARTITION_TS_KV_TABLE; - private static final String CALL_CREATE_NEW_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_NEW_TS_KV_LATEST_TABLE; - private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE; - private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY; - private static final String CALL_INSERT_INTO_TS_KV_CURSOR = CALL_REGEX + INSERT_INTO_TS_KV_CURSOR; - private static final String CALL_INSERT_INTO_TS_KV_LATEST_CURSOR = CALL_REGEX + INSERT_INTO_TS_KV_LATEST_CURSOR; - - private static final String DROP_TABLE_TS_KV_OLD = DROP_TABLE + TS_KV_OLD; - private static final String DROP_TABLE_TS_KV_LATEST_OLD = DROP_TABLE + TS_KV_LATEST_OLD; - - private static final String DROP_PROCEDURE_CREATE_PARTITION_TS_KV_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_PARTITION_TS_KV_TABLE; - private static final String DROP_PROCEDURE_CREATE_NEW_TS_KV_LATEST_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_NEW_TS_KV_LATEST_TABLE; - private static final String DROP_PROCEDURE_CREATE_PARTITIONS = DROP_PROCEDURE_IF_EXISTS + CREATE_PARTITIONS; - private static final String DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE; - private static final String DROP_PROCEDURE_INSERT_INTO_DICTIONARY = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_DICTIONARY; - private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV; - private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST; - private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_CURSOR = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_CURSOR; - private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST_CURSOR = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST_CURSOR; - private static final String DROP_FUNCTION_GET_PARTITION_DATA = "DROP FUNCTION IF EXISTS get_partitions_data;"; - @Override public void upgradeDatabase(String fromVersion) throws Exception { switch (fromVersion) { - case "2.4.3": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Check the current PostgreSQL version..."); - boolean versionValid = checkVersion(conn); - if (!versionValid) { - throw new RuntimeException("PostgreSQL version should be at least more than 11, please upgrade your PostgreSQL and restart the script!"); - } else { - log.info("PostgreSQL version is valid!"); - if (isOldSchema(conn, 2004003)) { - log.info("Load upgrade functions ..."); - loadSql(conn, LOAD_FUNCTIONS_SQL, "2.4.3"); - log.info("Updating timeseries schema ..."); - executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE); - if (!partitionType.equals("INDEFINITE")) { - executeQuery(conn, "call create_partitions('" + partitionType + "')"); - } - executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); - executeQuery(conn, CALL_INSERT_INTO_DICTIONARY); - - Path pathToTempTsKvFile = null; - Path pathToTempTsKvLatestFile = null; - if (SystemUtils.IS_OS_WINDOWS) { - log.info("Lookup for environment variable: {} ...", THINGSBOARD_WINDOWS_UPGRADE_DIR); - Path pathToDir; - String thingsboardWindowsUpgradeDir = System.getenv("THINGSBOARD_WINDOWS_UPGRADE_DIR"); - if (StringUtils.isNotEmpty(thingsboardWindowsUpgradeDir)) { - log.info("Environment variable: {} was found!", THINGSBOARD_WINDOWS_UPGRADE_DIR); - pathToDir = Paths.get(thingsboardWindowsUpgradeDir); - } else { - log.info("Failed to lookup environment variable: {}", THINGSBOARD_WINDOWS_UPGRADE_DIR); - pathToDir = Paths.get(PATH_TO_USERS_PUBLIC_FOLDER); - } - log.info("Directory: {} will be used for creation temporary upgrade files!", pathToDir); - try { - Path tsKvFile = Files.createTempFile(pathToDir, "ts_kv", ".sql"); - Path tsKvLatestFile = Files.createTempFile(pathToDir, "ts_kv_latest", ".sql"); - pathToTempTsKvFile = tsKvFile.toAbsolutePath(); - pathToTempTsKvLatestFile = tsKvLatestFile.toAbsolutePath(); - try { - copyTimeseries(conn, pathToTempTsKvFile, pathToTempTsKvLatestFile); - } catch (Exception e) { - insertTimeseries(conn); - } - } catch (IOException | SecurityException e) { - log.warn("Failed to create time-series upgrade files due to: {}", e.getMessage()); - insertTimeseries(conn); - } - } else { - try { - Path tempDirPath = Files.createTempDirectory("ts_kv"); - File tempDirAsFile = tempDirPath.toFile(); - boolean writable = tempDirAsFile.setWritable(true, false); - boolean readable = tempDirAsFile.setReadable(true, false); - boolean executable = tempDirAsFile.setExecutable(true, false); - pathToTempTsKvFile = tempDirPath.resolve(TS_KV_SQL).toAbsolutePath(); - pathToTempTsKvLatestFile = tempDirPath.resolve(TS_KV_LATEST_SQL).toAbsolutePath(); - try { - if (writable && readable && executable) { - copyTimeseries(conn, pathToTempTsKvFile, pathToTempTsKvLatestFile); - } else { - throw new RuntimeException("Failed to grant write permissions for the: " + tempDirPath + "folder!"); - } - } catch (Exception e) { - insertTimeseries(conn); - } - } catch (IOException | SecurityException e) { - log.warn("Failed to create time-series upgrade files due to: {}", e.getMessage()); - insertTimeseries(conn); - } - } - - removeUpgradeFiles(pathToTempTsKvFile, pathToTempTsKvLatestFile); - - executeQuery(conn, DROP_TABLE_TS_KV_OLD); - executeQuery(conn, DROP_TABLE_TS_KV_LATEST_OLD); - - executeQuery(conn, DROP_PROCEDURE_CREATE_PARTITION_TS_KV_TABLE); - executeQuery(conn, DROP_PROCEDURE_CREATE_PARTITIONS); - executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE); - executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_DICTIONARY); - executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV); - executeQuery(conn, DROP_PROCEDURE_CREATE_NEW_TS_KV_LATEST_TABLE); - executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST); - executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_CURSOR); - executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST_CURSOR); - executeQuery(conn, DROP_FUNCTION_GET_PARTITION_DATA); - - executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN IF NOT EXISTS json_v json;"); - executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN IF NOT EXISTS json_v json;"); - } else { - executeQuery(conn, "ALTER TABLE ts_kv DROP CONSTRAINT IF EXISTS ts_kv_pkey;"); - executeQuery(conn, "ALTER TABLE ts_kv ADD CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts);"); - } - - log.info("Load TTL functions ..."); - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3"); - log.info("Load Drop Partitions functions ..."); - loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3"); - - executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000"); - - log.info("schema timeseries updated!"); - } - } - break; - case "2.5.0": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - executeQuery(conn, "CREATE TABLE IF NOT EXISTS ts_kv_indefinite PARTITION OF ts_kv DEFAULT;"); - executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001"); - } - break; - case "3.1.1": - case "3.2.1": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Load TTL functions ..."); - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3"); - log.info("Load Drop Partitions functions ..."); - loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3"); - - executeQuery(conn, "DROP PROCEDURE IF EXISTS cleanup_timeseries_by_ttl(character varying, bigint, bigint);"); - executeQuery(conn, "DROP FUNCTION IF EXISTS delete_asset_records_from_ts_kv(character varying, character varying, bigint);"); - executeQuery(conn, "DROP FUNCTION IF EXISTS delete_device_records_from_ts_kv(character varying, character varying, bigint);"); - executeQuery(conn, "DROP FUNCTION IF EXISTS delete_customer_records_from_ts_kv(character varying, character varying, bigint);"); - } - break; - case "3.2.2": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Load Drop Partitions functions ..."); - loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3"); - } - break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } } - private void removeUpgradeFiles(Path pathToTempTsKvFile, Path pathToTempTsKvLatestFile) { - if (pathToTempTsKvFile != null && pathToTempTsKvFile.toFile().exists()) { - boolean deleteTsKvFile = pathToTempTsKvFile.toFile().delete(); - if (deleteTsKvFile) { - log.info("Successfully deleted the temp file for ts_kv table upgrade!"); - } - } - if (pathToTempTsKvLatestFile != null && pathToTempTsKvLatestFile.toFile().exists()) { - boolean deleteTsKvLatestFile = pathToTempTsKvLatestFile.toFile().delete(); - if (deleteTsKvLatestFile) { - log.info("Successfully deleted the temp file for ts_kv_latest table upgrade!"); - } - } - } - - private void copyTimeseries(Connection conn, Path pathToTempTsKvFile, Path pathToTempTsKvLatestFile) { - executeQuery(conn, "call insert_into_ts_kv('" + pathToTempTsKvFile + "')"); - executeQuery(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE); - executeQuery(conn, "call insert_into_ts_kv_latest('" + pathToTempTsKvLatestFile + "')"); - } - - private void insertTimeseries(Connection conn) { - log.warn("Upgrade script failed using the copy to/from files strategy!" + - " Trying to perfrom the upgrade using Inserts strategy ..."); - executeQuery(conn, CALL_INSERT_INTO_TS_KV_CURSOR); - executeQuery(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE); - executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST_CURSOR); - } - @Override protected void loadSql(Connection conn, String fileName, String version) { Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, fileName); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java index 1f29665884..eeac1b6aa4 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java @@ -25,8 +25,6 @@ public interface SystemDataLoaderService { void createRandomJwtSettings() throws Exception; - void saveLegacyYmlSettings() throws Exception; - void createOAuth2Templates() throws Exception; void loadSystemWidgets() throws Exception; diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java index 3eda90c554..43b0b37d71 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java @@ -16,21 +16,14 @@ package org.thingsboard.server.service.install; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.SystemUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.dao.util.TimescaleDBTsDao; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; -import java.sql.DriverManager; @Service @Profile("install") @@ -38,172 +31,17 @@ import java.sql.DriverManager; @TimescaleDBTsDao public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { - @Value("${sql.timescale.chunk_time_interval:86400000}") - private long chunkTimeInterval; - - private static final String LOAD_FUNCTIONS_SQL = "schema_update_timescale_ts.sql"; - private static final String LOAD_TTL_FUNCTIONS_SQL = "schema_update_ttl.sql"; - - private static final String TENANT_TS_KV_OLD_TABLE = "tenant_ts_kv_old;"; - - private static final String CREATE_TS_KV_LATEST_TABLE = "create_ts_kv_latest_table()"; - private static final String CREATE_NEW_TS_KV_TABLE = "create_new_ts_kv_table()"; - private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()"; - private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()"; - private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv(IN path_to_file varchar)"; - private static final String INSERT_INTO_TS_KV_CURSOR = "insert_into_ts_kv_cursor()"; - private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest()"; - - private static final String CALL_CREATE_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_TS_KV_LATEST_TABLE; - private static final String CALL_CREATE_NEW_TENANT_TS_KV_TABLE = CALL_REGEX + CREATE_NEW_TS_KV_TABLE; - private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE; - private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY; - private static final String CALL_INSERT_INTO_TS_KV_LATEST = CALL_REGEX + INSERT_INTO_TS_KV_LATEST; - private static final String CALL_INSERT_INTO_TS_KV_CURSOR = CALL_REGEX + INSERT_INTO_TS_KV_CURSOR; - - private static final String DROP_OLD_TENANT_TS_KV_TABLE = DROP_TABLE + TENANT_TS_KV_OLD_TABLE; - - private static final String DROP_PROCEDURE_CREATE_TS_KV_LATEST_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_TS_KV_LATEST_TABLE; - private static final String DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY = DROP_PROCEDURE_IF_EXISTS + CREATE_NEW_TS_KV_TABLE; - private static final String DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE; - private static final String DROP_PROCEDURE_INSERT_INTO_DICTIONARY = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_DICTIONARY; - private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV; - private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_CURSOR = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_CURSOR; - private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST; - @Autowired private InstallScripts installScripts; @Override public void upgradeDatabase(String fromVersion) throws Exception { switch (fromVersion) { - case "2.4.3": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Check the current PostgreSQL version..."); - boolean versionValid = checkVersion(conn); - if (!versionValid) { - throw new RuntimeException("PostgreSQL version should be at least more than 11, please upgrade your PostgreSQL and restart the script!"); - } else { - log.info("PostgreSQL version is valid!"); - if (isOldSchema(conn, 2004003)) { - log.info("Load upgrade functions ..."); - loadSql(conn, LOAD_FUNCTIONS_SQL, "2.4.3"); - log.info("Updating timescale schema ..."); - executeQuery(conn, CALL_CREATE_TS_KV_LATEST_TABLE); - executeQuery(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE); - - executeQuery(conn, "SELECT create_hypertable('ts_kv', 'ts', chunk_time_interval => " + chunkTimeInterval + ", if_not_exists => true);"); - - executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); - executeQuery(conn, CALL_INSERT_INTO_DICTIONARY); - - Path pathToTempTsKvFile = null; - if (SystemUtils.IS_OS_WINDOWS) { - Path pathToDir; - log.info("Lookup for environment variable: {} ...", THINGSBOARD_WINDOWS_UPGRADE_DIR); - String thingsboardWindowsUpgradeDir = System.getenv(THINGSBOARD_WINDOWS_UPGRADE_DIR); - if (StringUtils.isNotEmpty(thingsboardWindowsUpgradeDir)) { - log.info("Environment variable: {} was found!", THINGSBOARD_WINDOWS_UPGRADE_DIR); - pathToDir = Paths.get(thingsboardWindowsUpgradeDir); - } else { - log.info("Failed to lookup environment variable: {}", THINGSBOARD_WINDOWS_UPGRADE_DIR); - pathToDir = Paths.get(PATH_TO_USERS_PUBLIC_FOLDER); - } - log.info("Directory: {} will be used for creation temporary upgrade file!", pathToDir); - try { - Path tsKvFile = Files.createTempFile(pathToDir, "ts_kv", ".sql"); - pathToTempTsKvFile = tsKvFile.toAbsolutePath(); - try { - executeQuery(conn, "call insert_into_ts_kv('" + pathToTempTsKvFile + "')"); - } catch (Exception e) { - insertTimeseries(conn); - } - } catch (IOException | SecurityException e) { - log.warn("Failed to create time-series upgrade files due to: {}", e.getMessage()); - insertTimeseries(conn); - } - } else { - try { - Path tempDirPath = Files.createTempDirectory("ts_kv"); - File tempDirAsFile = tempDirPath.toFile(); - boolean writable = tempDirAsFile.setWritable(true, false); - boolean readable = tempDirAsFile.setReadable(true, false); - boolean executable = tempDirAsFile.setExecutable(true, false); - pathToTempTsKvFile = tempDirPath.resolve(TS_KV_SQL).toAbsolutePath(); - try { - if (writable && readable && executable) { - executeQuery(conn, "call insert_into_ts_kv('" + pathToTempTsKvFile + "')"); - } else { - throw new RuntimeException("Failed to grant write permissions for the: " + tempDirPath + "folder!"); - } - } catch (Exception e) { - insertTimeseries(conn); - } - } catch (IOException | SecurityException e) { - log.warn("Failed to create time-series upgrade files due to: {}", e.getMessage()); - insertTimeseries(conn); - } - } - removeUpgradeFile(pathToTempTsKvFile); - - executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST); - - executeQuery(conn, DROP_OLD_TENANT_TS_KV_TABLE); - - executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_LATEST_TABLE); - executeQuery(conn, DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY); - executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE); - executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_DICTIONARY); - executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV); - executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_CURSOR); - executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST); - - executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN IF NOT EXISTS json_v json;"); - executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN IF NOT EXISTS json_v json;"); - } - - log.info("Load TTL functions ..."); - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3"); - - executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000"); - log.info("schema timescale updated!"); - } - } - break; - case "2.5.0": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001"); - } - break; - case "3.1.1": - break; - case "3.2.1": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "3.2.1"); - } - break; - case "3.2.2": - break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } } - private void insertTimeseries(Connection conn) { - log.warn("Upgrade script failed using the copy to/from files strategy!" + - " Trying to perfrom the upgrade using Inserts strategy ..."); - executeQuery(conn, CALL_INSERT_INTO_TS_KV_CURSOR); - } - - private void removeUpgradeFile(Path pathToTempTsKvFile) { - if (pathToTempTsKvFile != null && pathToTempTsKvFile.toFile().exists()) { - boolean deleteTsKvFile = pathToTempTsKvFile.toFile().delete(); - if (deleteTsKvFile) { - log.info("Successfully deleted the temp file for ts_kv table upgrade!"); - } - } - } - @Override protected void loadSql(Connection conn, String fileName, String version) { Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, fileName); diff --git a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java deleted file mode 100644 index db5fba831c..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java +++ /dev/null @@ -1,218 +0,0 @@ -/** - * Copyright © 2016-2024 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.install.cql; - -import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; -import com.datastax.oss.driver.api.core.cql.PreparedStatement; -import com.datastax.oss.driver.api.core.cql.ResultSet; -import com.datastax.oss.driver.api.core.cql.Row; -import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; -import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; -import com.datastax.oss.driver.api.core.type.DataType; -import com.datastax.oss.protocol.internal.ProtocolConstants; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVParser; -import org.apache.commons.csv.CSVPrinter; -import org.apache.commons.csv.CSVRecord; -import org.thingsboard.server.dao.cassandra.guava.GuavaSession; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.UUID; - -import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT; - -public class CassandraDbHelper { - - public static Path dumpCfIfExists(KeyspaceMetadata ks, GuavaSession session, String cfName, - String[] columns, String[] defaultValues, String dumpPrefix) throws Exception { - return dumpCfIfExists(ks, session, cfName, columns, defaultValues, dumpPrefix, false); - } - - public static Path dumpCfIfExists(KeyspaceMetadata ks, GuavaSession session, String cfName, - String[] columns, String[] defaultValues, String dumpPrefix, boolean printHeader) throws Exception { - if (ks.getTable(cfName) != null) { - Path dumpFile = Files.createTempFile(dumpPrefix, null); - Files.deleteIfExists(dumpFile); - CSVFormat csvFormat = CSV_DUMP_FORMAT; - if (printHeader) { - csvFormat = csvFormat.withHeader(columns); - } - try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), csvFormat)) { - Statement stmt = SimpleStatement.newInstance("SELECT * FROM " + cfName); - stmt.setPageSize(1000); - ResultSet rs = session.execute(stmt); - Iterator iter = rs.iterator(); - while (iter.hasNext()) { - Row row = iter.next(); - if (row != null) { - dumpRow(row, columns, defaultValues, csvPrinter); - } - } - } - return dumpFile; - } else { - return null; - } - } - - public static void appendToEndOfLine(Path targetDumpFile, String toAppend) throws Exception { - Path tmp = Files.createTempFile(null, null); - try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(targetDumpFile), CSV_DUMP_FORMAT)) { - try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(tmp), CSV_DUMP_FORMAT)) { - csvParser.forEach(record -> { - List newRecord = new ArrayList<>(); - record.forEach(val -> newRecord.add(val)); - newRecord.add(toAppend); - try { - csvPrinter.printRecord(newRecord); - } catch (IOException e) { - throw new RuntimeException("Error appending to EOL", e); - } - }); - } - } - Files.move(tmp, targetDumpFile, StandardCopyOption.REPLACE_EXISTING); - } - - public static void loadCf(KeyspaceMetadata ks, GuavaSession session, String cfName, String[] columns, Path sourceFile) throws Exception { - loadCf(ks, session, cfName, columns, sourceFile, false); - } - - public static void loadCf(KeyspaceMetadata ks, GuavaSession session, String cfName, String[] columns, Path sourceFile, boolean parseHeader) throws Exception { - TableMetadata tableMetadata = ks.getTable(cfName).get(); - PreparedStatement prepared = session.prepare(createInsertStatement(cfName, columns)); - CSVFormat csvFormat = CSV_DUMP_FORMAT; - if (parseHeader) { - csvFormat = csvFormat.withFirstRecordAsHeader(); - } else { - csvFormat = CSV_DUMP_FORMAT.withHeader(columns); - } - try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), csvFormat)) { - csvParser.forEach(record -> { - BoundStatementBuilder boundStatementBuilder = new BoundStatementBuilder(prepared.bind()); - for (String column : columns) { - setColumnValue(tableMetadata, column, record, boundStatementBuilder); - } - session.execute(boundStatementBuilder.build()); - }); - } - } - - - private static void dumpRow(Row row, String[] columns, String[] defaultValues, CSVPrinter csvPrinter) throws Exception { - List record = new ArrayList<>(); - for (int i=0;i -1) { - String str; - DataType type = row.getColumnDefinitions().get(index).getType(); - try { - if (row.isNull(index)) { - return null; - } else if (type.getProtocolCode() == ProtocolConstants.DataType.DOUBLE) { - str = Double.valueOf(row.getDouble(index)).toString(); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.INT) { - str = Integer.valueOf(row.getInt(index)).toString(); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.BIGINT) { - str = Long.valueOf(row.getLong(index)).toString(); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.UUID) { - str = row.getUuid(index).toString(); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.TIMEUUID) { - str = row.getUuid(index).toString(); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.FLOAT) { - str = Float.valueOf(row.getFloat(index)).toString(); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.TIMESTAMP) { - str = ""+row.getInstant(index).toEpochMilli(); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.BOOLEAN) { - str = Boolean.valueOf(row.getBoolean(index)).toString(); - } else { - str = row.getString(index); - } - } catch (Exception e) { - str = ""; - } - return str; - } else { - return defaultValue; - } - } - - private static String createInsertStatement(String cfName, String[] columns) { - StringBuilder insertStatementBuilder = new StringBuilder(); - insertStatementBuilder.append("INSERT INTO ").append(cfName).append(" ("); - for (String column : columns) { - insertStatementBuilder.append(column).append(","); - } - insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1); - insertStatementBuilder.append(") VALUES ("); - for (String column : columns) { - insertStatementBuilder.append("?").append(","); - } - insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1); - insertStatementBuilder.append(")"); - return insertStatementBuilder.toString(); - } - - private static void setColumnValue(TableMetadata tableMetadata, String column, - CSVRecord record, BoundStatementBuilder boundStatementBuilder) { - String value = record.get(column); - DataType type = tableMetadata.getColumn(column).get().getType(); - if (value == null) { - boundStatementBuilder.setToNull(column); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.DOUBLE) { - boundStatementBuilder.setDouble(column, Double.valueOf(value)); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.INT) { - boundStatementBuilder.setInt(column, Integer.valueOf(value)); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.BIGINT) { - boundStatementBuilder.setLong(column, Long.valueOf(value)); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.UUID) { - boundStatementBuilder.setUuid(column, UUID.fromString(value)); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.TIMEUUID) { - boundStatementBuilder.setUuid(column, UUID.fromString(value)); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.FLOAT) { - boundStatementBuilder.setFloat(column, Float.valueOf(value)); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.TIMESTAMP) { - boundStatementBuilder.setInstant(column, Instant.ofEpochMilli(Long.valueOf(value))); - } else if (type.getProtocolCode() == ProtocolConstants.DataType.BOOLEAN) { - boundStatementBuilder.setBoolean(column, Boolean.valueOf(value)); - } else { - boundStatementBuilder.setString(column, value); - } - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java deleted file mode 100644 index 7b6944ebdd..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java +++ /dev/null @@ -1,327 +0,0 @@ -/** - * Copyright © 2016-2024 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.install.migrate; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.UUIDConverter; -import org.thingsboard.server.dao.cassandra.CassandraCluster; -import org.thingsboard.server.dao.util.NoSqlAnyDao; -import org.thingsboard.server.service.install.EntityDatabaseSchemaService; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.util.Arrays; -import java.util.List; - -import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.bigintColumn; -import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.booleanColumn; -import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.doubleColumn; -import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.enumToIntColumn; -import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.idColumn; -import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.jsonColumn; -import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.stringColumn; - -@Service -@Profile("install") -@NoSqlAnyDao -@Slf4j -public class CassandraEntitiesToSqlMigrateService implements EntitiesMigrateService { - - @Autowired - private EntityDatabaseSchemaService entityDatabaseSchemaService; - - @Autowired - protected CassandraCluster cluster; - - @Value("${spring.datasource.url}") - protected String dbUrl; - - @Value("${spring.datasource.username}") - protected String dbUserName; - - @Value("${spring.datasource.password}") - protected String dbPassword; - - @Override - public void migrate() throws Exception { - log.info("Performing migration of entities data from cassandra to SQL database ..."); - entityDatabaseSchemaService.createDatabaseSchema(false); - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - conn.setAutoCommit(false); - for (CassandraToSqlTable table: tables) { - table.migrateToSql(cluster.getSession(), conn); - } - } catch (Exception e) { - log.error("Unexpected error during ThingsBoard entities data migration!", e); - throw e; - } - entityDatabaseSchemaService.createDatabaseIndexes(); - } - - private static List tables = Arrays.asList( - new CassandraToSqlTable("admin_settings", - idColumn("id"), - stringColumn("key"), - stringColumn("json_value")), - new CassandraToSqlTable("alarm", - idColumn("id"), - idColumn("tenant_id"), - stringColumn("type"), - idColumn("originator_id"), - enumToIntColumn("originator_type", EntityType.class), - stringColumn("severity"), - stringColumn("status"), - bigintColumn("start_ts"), - bigintColumn("end_ts"), - bigintColumn("ack_ts"), - bigintColumn("clear_ts"), - stringColumn("details", "additional_info"), - booleanColumn("propagate"), - stringColumn("propagate_relation_types")), - new CassandraToSqlTable("asset", - idColumn("id"), - idColumn("tenant_id"), - idColumn("customer_id"), - stringColumn("name"), - stringColumn("type"), - stringColumn("label"), - stringColumn("search_text"), - stringColumn("additional_info")) { - @Override - protected boolean onConstraintViolation(List batchData, - CassandraToSqlColumnData[] data, String constraint) { - if (constraint.equalsIgnoreCase("asset_name_unq_key")) { - this.handleUniqueNameViolation(data, "asset"); - return true; - } - return super.onConstraintViolation(batchData, data, constraint); - } - }, - new CassandraToSqlTable("audit_log_by_tenant_id", "audit_log", - idColumn("id"), - idColumn("tenant_id"), - idColumn("customer_id"), - idColumn("entity_id"), - stringColumn("entity_type"), - stringColumn("entity_name"), - idColumn("user_id"), - stringColumn("user_name"), - stringColumn("action_type"), - stringColumn("action_data"), - stringColumn("action_status"), - stringColumn("action_failure_details")), - new CassandraToSqlTable("attributes_kv_cf", "attribute_kv", - idColumn("entity_id"), - stringColumn("entity_type"), - stringColumn("attribute_type"), - stringColumn("attribute_key"), - booleanColumn("bool_v", true), - stringColumn("str_v"), - bigintColumn("long_v"), - doubleColumn("dbl_v"), - jsonColumn("json_v"), - bigintColumn("last_update_ts")), - new CassandraToSqlTable("component_descriptor", - idColumn("id"), - stringColumn("type"), - stringColumn("scope"), - stringColumn("name"), - stringColumn("search_text"), - stringColumn("clazz"), - stringColumn("configuration_descriptor"), - stringColumn("actions")) { - @Override - protected boolean onConstraintViolation(List batchData, - CassandraToSqlColumnData[] data, String constraint) { - if (constraint.equalsIgnoreCase("component_descriptor_clazz_key")) { - String clazz = this.getColumnData(data, "clazz").getValue(); - log.warn("Found component_descriptor record with duplicate clazz [{}]. Record will be ignored!", clazz); - this.ignoreRecord(batchData, data); - return true; - } - return super.onConstraintViolation(batchData, data, constraint); - } - }, - new CassandraToSqlTable("customer", - idColumn("id"), - idColumn("tenant_id"), - stringColumn("title"), - stringColumn("search_text"), - stringColumn("country"), - stringColumn("state"), - stringColumn("city"), - stringColumn("address"), - stringColumn("address2"), - stringColumn("zip"), - stringColumn("phone"), - stringColumn("email"), - stringColumn("additional_info")), - new CassandraToSqlTable("dashboard", - idColumn("id"), - idColumn("tenant_id"), - stringColumn("title"), - stringColumn("search_text"), - stringColumn("assigned_customers"), - stringColumn("configuration")), - new CassandraToSqlTable("device", - idColumn("id"), - idColumn("tenant_id"), - idColumn("customer_id"), - stringColumn("name"), - stringColumn("type"), - stringColumn("label"), - stringColumn("search_text"), - stringColumn("additional_info")) { - @Override - protected boolean onConstraintViolation(List batchData, - CassandraToSqlColumnData[] data, String constraint) { - if (constraint.equalsIgnoreCase("device_name_unq_key")) { - this.handleUniqueNameViolation(data, "device"); - return true; - } - return super.onConstraintViolation(batchData, data, constraint); - } - }, - new CassandraToSqlTable("device_credentials", - idColumn("id"), - idColumn("device_id"), - stringColumn("credentials_type"), - stringColumn("credentials_id"), - stringColumn("credentials_value")), - new CassandraToSqlTable("event", - idColumn("id"), - idColumn("tenant_id"), - idColumn("entity_id"), - stringColumn("entity_type"), - stringColumn("event_type"), - stringColumn("event_uid"), - stringColumn("body"), - new CassandraToSqlEventTsColumn()), - new CassandraToSqlTable("relation", - idColumn("from_id"), - stringColumn("from_type"), - idColumn("to_id"), - stringColumn("to_type"), - stringColumn("relation_type_group"), - stringColumn("relation_type"), - stringColumn("additional_info")), - new CassandraToSqlTable("user", "tb_user", - idColumn("id"), - idColumn("tenant_id"), - idColumn("customer_id"), - stringColumn("email"), - stringColumn("search_text"), - stringColumn("authority"), - stringColumn("first_name"), - stringColumn("last_name"), - stringColumn("additional_info")) { - @Override - protected boolean onConstraintViolation(List batchData, - CassandraToSqlColumnData[] data, String constraint) { - if (constraint.equalsIgnoreCase("tb_user_email_key")) { - this.handleUniqueEmailViolation(data); - return true; - } - return super.onConstraintViolation(batchData, data, constraint); - } - }, - new CassandraToSqlTable("tenant", - idColumn("id"), - stringColumn("title"), - stringColumn("search_text"), - stringColumn("region"), - stringColumn("country"), - stringColumn("state"), - stringColumn("city"), - stringColumn("address"), - stringColumn("address2"), - stringColumn("zip"), - stringColumn("phone"), - stringColumn("email"), - stringColumn("additional_info"), - booleanColumn("isolated_tb_core"), - booleanColumn("isolated_tb_rule_engine")), - new CassandraToSqlTable("user_credentials", - idColumn("id"), - idColumn("user_id"), - booleanColumn("enabled"), - stringColumn("password"), - stringColumn("activate_token"), - stringColumn("reset_token")) { - @Override - protected boolean onConstraintViolation(List batchData, - CassandraToSqlColumnData[] data, String constraint) { - if (constraint.equalsIgnoreCase("user_credentials_user_id_key")) { - String id = UUIDConverter.fromString(this.getColumnData(data, "id").getValue()).toString(); - log.warn("Found user credentials record with duplicate user_id [id:[{}]]. Record will be ignored!", id); - this.ignoreRecord(batchData, data); - return true; - } - return super.onConstraintViolation(batchData, data, constraint); - } - }, - new CassandraToSqlTable("widget_type", - idColumn("id"), - idColumn("tenant_id"), - stringColumn("bundle_alias"), - stringColumn("alias"), - stringColumn("name"), - stringColumn("descriptor")), - new CassandraToSqlTable("widgets_bundle", - idColumn("id"), - idColumn("tenant_id"), - stringColumn("alias"), - stringColumn("title"), - stringColumn("search_text")), - new CassandraToSqlTable("rule_chain", - idColumn("id"), - idColumn("tenant_id"), - stringColumn("name"), - stringColumn("search_text"), - idColumn("first_rule_node_id"), - booleanColumn("root"), - booleanColumn("debug_mode"), - stringColumn("configuration"), - stringColumn("additional_info")), - new CassandraToSqlTable("rule_node", - idColumn("id"), - idColumn("rule_chain_id"), - stringColumn("type"), - stringColumn("name"), - booleanColumn("debug_mode"), - stringColumn("search_text"), - stringColumn("configuration"), - stringColumn("additional_info")), - new CassandraToSqlTable("entity_view", - idColumn("id"), - idColumn("tenant_id"), - idColumn("customer_id"), - idColumn("entity_id"), - stringColumn("entity_type"), - stringColumn("name"), - stringColumn("type"), - stringColumn("keys"), - bigintColumn("start_ts"), - bigintColumn("end_ts"), - stringColumn("search_text"), - stringColumn("additional_info")) - ); -} diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java deleted file mode 100644 index 4ceea39cf2..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright © 2016-2024 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.install.migrate; - -import com.datastax.oss.driver.api.core.cql.Row; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.EPOCH_DIFF; - -public class CassandraToSqlEventTsColumn extends CassandraToSqlColumn { - - CassandraToSqlEventTsColumn() { - super("id", "ts", CassandraToSqlColumnType.BIGINT, null, false); - } - - @Override - public String getColumnValue(Row row) { - UUID id = row.getUuid(getIndex()); - long ts = getTs(id); - return ts + ""; - } - - private long getTs(UUID uuid) { - return (uuid.timestamp() - EPOCH_DIFF) / 10000; - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java index e0bdc75ee8..3cc4001624 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java @@ -64,6 +64,8 @@ public class CassandraTsLatestToSqlMigrateService implements TsLatestMigrateServ private static final int MAX_KEY_LENGTH = 255; private static final int MAX_STR_V_LENGTH = 10000000; + private static final String SQL_DIR = "sql"; + @Autowired private InsertLatestTsRepository insertLatestTsRepository; @@ -93,7 +95,7 @@ public class CassandraTsLatestToSqlMigrateService implements TsLatestMigrateServ public void migrate() throws Exception { log.info("Performing migration of latest timeseries data from cassandra to SQL database ..."); try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.0.1", "schema_ts_latest.sql"); + Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), SQL_DIR, "schema-ts-latest-psql.sql"); loadSql(schemaUpdateFile, conn); conn.setAutoCommit(false); for (CassandraToSqlTable table : tables) { diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/EntitiesMigrateService.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/EntitiesMigrateService.java deleted file mode 100644 index 7e3a1e1ca0..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/EntitiesMigrateService.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright © 2016-2024 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.install.migrate; - -public interface EntitiesMigrateService { - - void migrate() throws Exception; - -} diff --git a/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java deleted file mode 100644 index 4e861aa174..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Copyright © 2016-2024 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.install.sql; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVParser; -import org.apache.commons.csv.CSVPrinter; -import org.apache.commons.csv.CSVRecord; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT; - -/** - * Created by igor on 2/27/18. - */ -@Slf4j -public class SqlDbHelper { - - public static Path dumpTableIfExists(Connection conn, String tableName, - String[] columns, String[] defaultValues, String dumpPrefix) throws Exception { - return dumpTableIfExists(conn, tableName, columns, defaultValues, dumpPrefix, false); - } - - public static Path dumpTableIfExists(Connection conn, String tableName, - String[] columns, String[] defaultValues, String dumpPrefix, boolean printHeader) throws Exception { - - if (tableExists(conn, tableName)) { - Path dumpFile = Files.createTempFile(dumpPrefix, null); - Files.deleteIfExists(dumpFile); - CSVFormat csvFormat = CSV_DUMP_FORMAT; - if (printHeader) { - csvFormat = csvFormat.withHeader(columns); - } - try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), csvFormat)) { - try (PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + tableName)) { - try (ResultSet tableRes = stmt.executeQuery()) { - ResultSetMetaData resMetaData = tableRes.getMetaData(); - Map columnIndexMap = new HashMap<>(); - for (int i = 1; i <= resMetaData.getColumnCount(); i++) { - String columnName = resMetaData.getColumnName(i); - columnIndexMap.put(columnName.toUpperCase(), i); - } - while(tableRes.next()) { - dumpRow(tableRes, columnIndexMap, columns, defaultValues, csvPrinter); - } - } - } - } - return dumpFile; - } else { - return null; - } - } - - private static boolean tableExists(Connection conn, String tableName) { - try (Statement stmt = conn.createStatement()) { - stmt.executeQuery("select * from " + tableName + " where 1=0"); - return true; - } catch (Exception e) { - return false; - } - } - - public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile) throws Exception { - loadTable(conn, tableName, columns, sourceFile, false); - } - - public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile, boolean parseHeader) throws Exception { - CSVFormat csvFormat = CSV_DUMP_FORMAT; - if (parseHeader) { - csvFormat = csvFormat.withFirstRecordAsHeader(); - } else { - csvFormat = CSV_DUMP_FORMAT.withHeader(columns); - } - try (PreparedStatement prepared = conn.prepareStatement(createInsertStatement(tableName, columns))) { - try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), csvFormat)) { - csvParser.forEach(record -> { - try { - for (int i = 0; i < columns.length; i++) { - setColumnValue(i, columns[i], record, prepared); - } - prepared.execute(); - } catch (SQLException e) { - log.error("Unable to load table record!", e); - } - }); - } - } - } - - private static void dumpRow(ResultSet res, Map columnIndexMap, String[] columns, - String[] defaultValues, CSVPrinter csvPrinter) throws Exception { - List record = new ArrayList<>(); - for (int i=0;i columnIndexMap, ResultSet res) { - int index = columnIndexMap.containsKey(column.toUpperCase()) ? columnIndexMap.get(column.toUpperCase()) : -1; - if (index > -1) { - String str; - try { - Object obj = res.getObject(index); - if (obj == null) { - return null; - } else { - str = obj.toString(); - } - } catch (Exception e) { - str = ""; - } - return str; - } else { - return defaultValue; - } - } - - private static void setColumnValue(int index, String column, - CSVRecord record, PreparedStatement preparedStatement) throws SQLException { - String value = record.get(column); - int type = preparedStatement.getParameterMetaData().getParameterType(index + 1); - preparedStatement.setObject(index + 1, value, type); - } - - private static String createInsertStatement(String tableName, String[] columns) { - StringBuilder insertStatementBuilder = new StringBuilder(); - insertStatementBuilder.append("INSERT INTO ").append(tableName).append(" ("); - for (String column : columns) { - insertStatementBuilder.append(column).append(","); - } - insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1); - insertStatementBuilder.append(") VALUES ("); - for (String column : columns) { - insertStatementBuilder.append("?").append(","); - } - insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1); - insertStatementBuilder.append(")"); - return insertStatementBuilder.toString(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java index b20a3d961b..90e3d4bb04 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java @@ -48,47 +48,6 @@ public class DefaultCacheCleanupService implements CacheCleanupService { @Override public void clearCache(String fromVersion) throws Exception { switch (fromVersion) { - case "3.0.1": - log.info("Clear cache to upgrade from version 3.0.1 to 3.1.0 ..."); - clearAllCaches(); - //do not break to show explicit calls for next versions - case "3.1.1": - log.info("Clear cache to upgrade from version 3.1.1 to 3.2.0 ..."); - clearCacheByName("devices"); - clearCacheByName("deviceProfiles"); - clearCacheByName("tenantProfiles"); - case "3.2.2": - log.info("Clear cache to upgrade from version 3.2.2 to 3.3.0 ..."); - clearCacheByName("devices"); - clearCacheByName("deviceProfiles"); - clearCacheByName("tenantProfiles"); - clearCacheByName("relations"); - break; - case "3.3.2": - log.info("Clear cache to upgrade from version 3.3.2 to 3.3.3 ..."); - clearAll(); - break; - case "3.3.3": - log.info("Clear cache to upgrade from version 3.3.3 to 3.3.4 ..."); - clearAll(); - break; - case "3.3.4": - log.info("Clear cache to upgrade from version 3.3.4 to 3.4.0 ..."); - clearAll(); - break; - case "3.4.1": - log.info("Clear cache to upgrade from version 3.4.1 to 3.4.2 ..."); - clearCacheByName("assets"); - clearCacheByName("repositorySettings"); - break; - case "3.4.2": - log.info("Clearing cache to upgrade from version 3.4.2 to 3.4.3 ..."); - clearCacheByName("repositorySettings"); - break; - case "3.4.4": - log.info("Clearing cache to upgrade from version 3.4.4 to 3.5.0"); - clearAll(); - break; case "3.6.1": log.info("Clearing cache to upgrade from version 3.6.1 to 3.6.2"); clearCacheByName(SECURITY_SETTINGS_CACHE); diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index fe467df541..3f8fa76fd9 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -20,85 +20,29 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; -import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; -import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; -import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; import org.thingsboard.server.common.data.AdminSettings; -import org.thingsboard.server.common.data.DataConstants; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmInfo; -import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; -import org.thingsboard.server.common.data.kv.ReadTsKvQuery; -import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.common.data.msg.TbNodeConnectionType; -import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterable; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.FilterPredicateValue; -import org.thingsboard.server.common.data.queue.ProcessingStrategy; -import org.thingsboard.server.common.data.queue.ProcessingStrategyType; -import org.thingsboard.server.common.data.queue.Queue; -import org.thingsboard.server.common.data.queue.SubmitStrategy; -import org.thingsboard.server.common.data.queue.SubmitStrategyType; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; -import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.common.data.rule.RuleChainType; -import org.thingsboard.server.common.data.rule.RuleNode; -import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.alarm.AlarmDao; -import org.thingsboard.server.dao.audit.AuditLogDao; import org.thingsboard.server.dao.device.DeviceConnectivityConfiguration; -import org.thingsboard.server.dao.edge.EdgeEventDao; -import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.dao.entityview.EntityViewService; -import org.thingsboard.server.dao.event.EventService; -import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; -import org.thingsboard.server.dao.queue.QueueService; -import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.sql.JpaExecutorService; -import org.thingsboard.server.dao.sql.device.DeviceProfileRepository; -import org.thingsboard.server.dao.tenant.TenantProfileService; -import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.component.RuleNodeClassInfo; -import org.thingsboard.server.service.install.InstallScripts; -import org.thingsboard.server.service.install.SystemDataLoaderService; import org.thingsboard.server.utils.TbNodeUpgradeUtils; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static org.thingsboard.server.common.data.StringUtils.isBlank; @Service @Profile("install") @@ -108,58 +52,12 @@ public class DefaultDataUpdateService implements DataUpdateService { private static final int MAX_PENDING_SAVE_RULE_NODE_FUTURES = 256; private static final int DEFAULT_PAGE_SIZE = 1024; - @Autowired - private TenantService tenantService; - - @Autowired - private RelationService relationService; - @Autowired private RuleChainService ruleChainService; - @Autowired - private InstallScripts installScripts; - - @Autowired - private EntityViewService entityViewService; - - @Autowired - private TimeseriesService tsService; - - @Autowired - private EntityService entityService; - - @Autowired - private AlarmDao alarmDao; - - @Autowired - private DeviceProfileRepository deviceProfileRepository; - - @Autowired - private RateLimitsUpdater rateLimitsUpdater; - - @Autowired - private TenantProfileService tenantProfileService; - - @Lazy - @Autowired - private QueueService queueService; - @Autowired private ComponentDiscoveryService componentDiscoveryService; - @Autowired - private SystemDataLoaderService systemDataLoaderService; - - @Autowired - private EventService eventService; - - @Autowired - private AuditLogDao auditLogDao; - - @Autowired - private EdgeEventDao edgeEventDao; - @Autowired JpaExecutorService jpaExecutorService; @@ -172,57 +70,6 @@ public class DefaultDataUpdateService implements DataUpdateService { @Override public void updateData(String fromVersion) throws Exception { switch (fromVersion) { - case "1.4.0": - log.info("Updating data from version 1.4.0 to 2.0.0 ..."); - tenantsDefaultRuleChainUpdater.updateEntities(null); - break; - case "3.0.1": - log.info("Updating data from version 3.0.1 to 3.1.0 ..."); - tenantsEntityViewsUpdater.updateEntities(null); - break; - case "3.1.1": - log.info("Updating data from version 3.1.1 to 3.2.0 ..."); - tenantsRootRuleChainUpdater.updateEntities(null); - break; - case "3.2.2": - log.info("Updating data from version 3.2.2 to 3.3.0 ..."); - tenantsDefaultEdgeRuleChainUpdater.updateEntities(null); - tenantsAlarmsCustomerUpdater.updateEntities(null); - deviceProfileEntityDynamicConditionsUpdater.updateEntities(null); - updateOAuth2Params(); - break; - case "3.3.2": - log.info("Updating data from version 3.3.2 to 3.3.3 ..."); - updateNestedRuleChains(); - break; - case "3.3.4": - log.info("Updating data from version 3.3.4 to 3.4.0 ..."); - tenantsProfileQueueConfigurationUpdater.updateEntities(); - rateLimitsUpdater.updateEntities(); - break; - case "3.4.0": - boolean skipEventsMigration = getEnv("TB_SKIP_EVENTS_MIGRATION", false); - if (!skipEventsMigration) { - log.info("Updating data from version 3.4.0 to 3.4.1 ..."); - eventService.migrateEvents(); - } - break; - case "3.4.1": - log.info("Updating data from version 3.4.1 to 3.4.2 ..."); - systemDataLoaderService.saveLegacyYmlSettings(); - boolean skipAuditLogsMigration = getEnv("TB_SKIP_AUDIT_LOGS_MIGRATION", false); - if (!skipAuditLogsMigration) { - log.info("Starting audit logs migration. Can be skipped with TB_SKIP_AUDIT_LOGS_MIGRATION env variable set to true"); - auditLogDao.migrateAuditLogs(); - } else { - log.info("Skipping audit logs migration"); - } - migrateEdgeEvents("Starting edge events migration. "); - break; - case "3.5.1": - log.info("Updating data from version 3.5.1 to 3.6.0 ..."); - migrateEdgeEvents("Starting edge events migration - adding seq_id column. "); - break; case "3.6.0": log.info("Updating data from version 3.6.0 to 3.6.1 ..."); migrateDeviceConnectivity(); @@ -232,16 +79,6 @@ public class DefaultDataUpdateService implements DataUpdateService { } } - private void migrateEdgeEvents(String logPrefix) { - boolean skipEdgeEventsMigration = getEnv("TB_SKIP_EDGE_EVENTS_MIGRATION", false); - if (!skipEdgeEventsMigration) { - log.info(logPrefix + "Can be skipped with TB_SKIP_EDGE_EVENTS_MIGRATION env variable set to true"); - edgeEventDao.migrateEdgeEvents(); - } else { - log.info("Skipping edge events migration"); - } - } - private void migrateDeviceConnectivity() { if (adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "connectivity") == null) { AdminSettings connectivitySettings = new AdminSettings(); @@ -320,27 +157,6 @@ public class DefaultDataUpdateService implements DataUpdateService { return ruleNodeIds; } - private final PaginatedUpdater deviceProfileEntityDynamicConditionsUpdater = - new PaginatedUpdater<>() { - - @Override - protected String getName() { - return "Device Profile Entity Dynamic Conditions Updater"; - } - - @Override - protected PageData findEntities(String id, PageLink pageLink) { - return DaoUtil.pageToPageData(deviceProfileRepository.findAll(DaoUtil.toPageable(pageLink))); - } - - @Override - protected void updateEntity(DeviceProfileEntity deviceProfile) { - if (convertDeviceProfileForVersion330(deviceProfile.getProfileData())) { - deviceProfileRepository.save(deviceProfile); - } - } - }; - boolean convertDeviceProfileForVersion330(JsonNode profileData) { boolean isUpdated = false; if (profileData.has("alarms") && !profileData.get("alarms").isNull()) { @@ -368,327 +184,6 @@ public class DefaultDataUpdateService implements DataUpdateService { return isUpdated; } - private final PaginatedUpdater tenantsDefaultRuleChainUpdater = - new PaginatedUpdater<>() { - - @Override - protected String getName() { - return "Tenants default rule chain updater"; - } - - @Override - protected boolean forceReportTotal() { - return true; - } - - @Override - protected PageData findEntities(String region, PageLink pageLink) { - return tenantService.findTenants(pageLink); - } - - @Override - protected void updateEntity(Tenant tenant) { - try { - RuleChain ruleChain = ruleChainService.getRootTenantRuleChain(tenant.getId()); - if (ruleChain == null) { - installScripts.createDefaultRuleChains(tenant.getId()); - } - } catch (Exception e) { - log.error("Unable to update Tenant", e); - } - } - }; - - private void updateNestedRuleChains() { - try { - var updated = 0; - boolean hasNext = true; - while (hasNext) { - List relations = relationService.findRuleNodeToRuleChainRelations(TenantId.SYS_TENANT_ID, RuleChainType.CORE, DEFAULT_PAGE_SIZE); - hasNext = relations.size() == DEFAULT_PAGE_SIZE; - for (EntityRelation relation : relations) { - try { - RuleNodeId sourceNodeId = new RuleNodeId(relation.getFrom().getId()); - RuleNode sourceNode = ruleChainService.findRuleNodeById(TenantId.SYS_TENANT_ID, sourceNodeId); - if (sourceNode == null) { - log.info("Skip processing of relation for non existing source rule node: [{}]", sourceNodeId); - relationService.deleteRelation(TenantId.SYS_TENANT_ID, relation); - continue; - } - RuleChainId sourceRuleChainId = sourceNode.getRuleChainId(); - RuleChainId targetRuleChainId = new RuleChainId(relation.getTo().getId()); - RuleChain targetRuleChain = ruleChainService.findRuleChainById(TenantId.SYS_TENANT_ID, targetRuleChainId); - if (targetRuleChain == null) { - log.info("Skip processing of relation for non existing target rule chain: [{}]", targetRuleChainId); - relationService.deleteRelation(TenantId.SYS_TENANT_ID, relation); - continue; - } - TenantId tenantId = targetRuleChain.getTenantId(); - RuleNode targetNode = new RuleNode(); - targetNode.setName(targetRuleChain.getName()); - targetNode.setRuleChainId(sourceRuleChainId); - targetNode.setType(TbRuleChainInputNode.class.getName()); - TbRuleChainInputNodeConfiguration configuration = new TbRuleChainInputNodeConfiguration(); - configuration.setRuleChainId(targetRuleChain.getId().toString()); - targetNode.setConfiguration(JacksonUtil.valueToTree(configuration)); - targetNode.setAdditionalInfo(relation.getAdditionalInfo()); - targetNode.setDebugMode(false); - targetNode = ruleChainService.saveRuleNode(tenantId, targetNode); - - EntityRelation sourceRuleChainToRuleNode = new EntityRelation(); - sourceRuleChainToRuleNode.setFrom(sourceRuleChainId); - sourceRuleChainToRuleNode.setTo(targetNode.getId()); - sourceRuleChainToRuleNode.setType(EntityRelation.CONTAINS_TYPE); - sourceRuleChainToRuleNode.setTypeGroup(RelationTypeGroup.RULE_CHAIN); - relationService.saveRelation(tenantId, sourceRuleChainToRuleNode); - - EntityRelation sourceRuleNodeToTargetRuleNode = new EntityRelation(); - sourceRuleNodeToTargetRuleNode.setFrom(sourceNode.getId()); - sourceRuleNodeToTargetRuleNode.setTo(targetNode.getId()); - sourceRuleNodeToTargetRuleNode.setType(relation.getType()); - sourceRuleNodeToTargetRuleNode.setTypeGroup(RelationTypeGroup.RULE_NODE); - sourceRuleNodeToTargetRuleNode.setAdditionalInfo(relation.getAdditionalInfo()); - relationService.saveRelation(tenantId, sourceRuleNodeToTargetRuleNode); - - //Delete old relation - relationService.deleteRelation(tenantId, relation); - updated++; - } catch (Exception e) { - log.info("Failed to update RuleNodeToRuleChainRelation: {}", relation, e); - } - } - if (updated > 0) { - log.info("RuleNodeToRuleChainRelations: {} entities updated so far...", updated); - } - } - } catch (Exception e) { - log.error("Unable to update Tenant", e); - } - } - - private final PaginatedUpdater tenantsDefaultEdgeRuleChainUpdater = - new PaginatedUpdater<>() { - - @Override - protected String getName() { - return "Tenants default edge rule chain updater"; - } - - @Override - protected boolean forceReportTotal() { - return true; - } - - @Override - protected PageData findEntities(String region, PageLink pageLink) { - return tenantService.findTenants(pageLink); - } - - @Override - protected void updateEntity(Tenant tenant) { - try { - RuleChain defaultEdgeRuleChain = ruleChainService.getEdgeTemplateRootRuleChain(tenant.getId()); - if (defaultEdgeRuleChain == null) { - installScripts.createDefaultEdgeRuleChains(tenant.getId()); - } - } catch (Exception e) { - log.error("Unable to update Tenant", e); - } - } - }; - - private final PaginatedUpdater tenantsRootRuleChainUpdater = - new PaginatedUpdater<>() { - - @Override - protected String getName() { - return "Tenants root rule chain updater"; - } - - @Override - protected boolean forceReportTotal() { - return true; - } - - @Override - protected PageData findEntities(String region, PageLink pageLink) { - return tenantService.findTenants(pageLink); - } - - @Override - protected void updateEntity(Tenant tenant) { - try { - RuleChain ruleChain = ruleChainService.getRootTenantRuleChain(tenant.getId()); - if (ruleChain == null) { - installScripts.createDefaultRuleChains(tenant.getId()); - } else { - RuleChainMetaData md = ruleChainService.loadRuleChainMetaData(tenant.getId(), ruleChain.getId()); - int oldIdx = md.getFirstNodeIndex(); - int newIdx = md.getNodes().size(); - - if (md.getNodes().size() < oldIdx) { - // Skip invalid rule chains - return; - } - - RuleNode oldFirstNode = md.getNodes().get(oldIdx); - if (oldFirstNode.getType().equals(TbDeviceProfileNode.class.getName())) { - // No need to update the rule node twice. - return; - } - - RuleNode ruleNode = new RuleNode(); - ruleNode.setRuleChainId(ruleChain.getId()); - ruleNode.setName("Device Profile Node"); - ruleNode.setType(TbDeviceProfileNode.class.getName()); - ruleNode.setDebugMode(false); - TbDeviceProfileNodeConfiguration ruleNodeConfiguration = new TbDeviceProfileNodeConfiguration().defaultConfiguration(); - ruleNode.setConfiguration(JacksonUtil.valueToTree(ruleNodeConfiguration)); - ObjectNode additionalInfo = JacksonUtil.newObjectNode(); - additionalInfo.put("description", "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type."); - additionalInfo.put("layoutX", 204); - additionalInfo.put("layoutY", 240); - ruleNode.setAdditionalInfo(additionalInfo); - - md.getNodes().add(ruleNode); - md.setFirstNodeIndex(newIdx); - md.addConnectionInfo(newIdx, oldIdx, TbNodeConnectionType.SUCCESS); - ruleChainService.saveRuleChainMetaData(tenant.getId(), md, Function.identity()); - } - } catch (Exception e) { - log.error("[{}] Unable to update Tenant: {}", tenant.getId(), tenant.getName(), e); - } - } - }; - - private final PaginatedUpdater tenantsEntityViewsUpdater = - new PaginatedUpdater<>() { - - @Override - protected String getName() { - return "Tenants entity views updater"; - } - - @Override - protected boolean forceReportTotal() { - return true; - } - - @Override - protected PageData findEntities(String region, PageLink pageLink) { - return tenantService.findTenants(pageLink); - } - - @Override - protected void updateEntity(Tenant tenant) { - updateTenantEntityViews(tenant.getId()); - } - }; - - private void updateTenantEntityViews(TenantId tenantId) { - PageLink pageLink = new PageLink(100); - PageData pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink); - boolean hasNext = true; - while (hasNext) { - List>> updateFutures = new ArrayList<>(); - for (EntityView entityView : pageData.getData()) { - updateFutures.add(updateEntityViewLatestTelemetry(entityView)); - } - - try { - Futures.allAsList(updateFutures).get(); - } catch (InterruptedException | ExecutionException e) { - log.error("Failed to copy latest telemetry to entity view", e); - } - - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink); - } else { - hasNext = false; - } - } - } - - private ListenableFuture> updateEntityViewLatestTelemetry(EntityView entityView) { - EntityViewId entityId = entityView.getId(); - List keys = entityView.getKeys() != null && entityView.getKeys().getTimeseries() != null ? - entityView.getKeys().getTimeseries() : Collections.emptyList(); - long startTs = entityView.getStartTimeMs(); - long endTs = entityView.getEndTimeMs() == 0 ? Long.MAX_VALUE : entityView.getEndTimeMs(); - ListenableFuture> keysFuture; - if (keys.isEmpty()) { - keysFuture = Futures.transform(tsService.findAllLatest(TenantId.SYS_TENANT_ID, - entityView.getEntityId()), latest -> latest.stream().map(TsKvEntry::getKey).collect(Collectors.toList()), MoreExecutors.directExecutor()); - } else { - keysFuture = Futures.immediateFuture(keys); - } - ListenableFuture> latestFuture = Futures.transformAsync(keysFuture, fetchKeys -> { - List queries = fetchKeys.stream().filter(key -> !isBlank(key)).map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, "DESC")).collect(Collectors.toList()); - if (!queries.isEmpty()) { - return tsService.findAll(TenantId.SYS_TENANT_ID, entityView.getEntityId(), queries); - } else { - return Futures.immediateFuture(null); - } - }, MoreExecutors.directExecutor()); - return Futures.transformAsync(latestFuture, latestValues -> { - if (latestValues != null && !latestValues.isEmpty()) { - ListenableFuture> saveFuture = tsService.saveLatest(TenantId.SYS_TENANT_ID, entityId, latestValues); - return saveFuture; - } - return Futures.immediateFuture(null); - }, MoreExecutors.directExecutor()); - } - - private final PaginatedUpdater tenantsAlarmsCustomerUpdater = - new PaginatedUpdater<>() { - - final AtomicLong processed = new AtomicLong(); - - @Override - protected String getName() { - return "Tenants alarms customer updater"; - } - - @Override - protected boolean forceReportTotal() { - return true; - } - - @Override - protected PageData findEntities(String region, PageLink pageLink) { - return tenantService.findTenants(pageLink); - } - - @Override - protected void updateEntity(Tenant tenant) { - updateTenantAlarmsCustomer(tenant.getId(), getName(), processed); - } - }; - - private void updateTenantAlarmsCustomer(TenantId tenantId, String name, AtomicLong processed) { - AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(1000), null, null, null, false); - PageData alarms = alarmDao.findAlarms(tenantId, alarmQuery); - boolean hasNext = true; - while (hasNext) { - for (Alarm alarm : alarms.getData()) { - if (alarm.getCustomerId() == null && alarm.getOriginator() != null) { - alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator()).get()); - alarmDao.save(tenantId, alarm); - } - if (processed.incrementAndGet() % 1000 == 0) { - log.info("{}: {} alarms processed so far...", name, processed); - } - } - if (alarms.hasNext()) { - alarmQuery.setPageLink(alarmQuery.getPageLink().nextPageLink()); - alarms = alarmDao.findAlarms(tenantId, alarmQuery); - } else { - hasNext = false; - } - } - } - boolean convertDeviceProfileAlarmRulesForVersion330(JsonNode spec) { if (spec != null) { if (spec.has("type") && spec.get("type").asText().equals("DURATION")) { @@ -716,73 +211,6 @@ public class DefaultDataUpdateService implements DataUpdateService { return false; } - private void updateOAuth2Params() { - log.warn("CAUTION: Update of Oauth2 parameters from 3.2.2 to 3.3.0 available only in ThingsBoard versions 3.3.0/3.3.1"); - } - - private final PaginatedUpdater tenantsProfileQueueConfigurationUpdater = - new PaginatedUpdater<>() { - - @Override - protected String getName() { - return "Tenant profiles queue configuration updater"; - } - - @Override - protected boolean forceReportTotal() { - return true; - } - - @Override - protected PageData findEntities(String id, PageLink pageLink) { - return tenantProfileService.findTenantProfiles(TenantId.SYS_TENANT_ID, pageLink); - } - - @Override - protected void updateEntity(TenantProfile tenantProfile) { - updateTenantProfileQueueConfiguration(tenantProfile); - } - }; - - private void updateTenantProfileQueueConfiguration(TenantProfile profile) { - try { - List queueConfiguration = profile.getProfileData().getQueueConfiguration(); - if (profile.isIsolatedTbRuleEngine() && (queueConfiguration == null || queueConfiguration.isEmpty())) { - TenantProfileQueueConfiguration mainQueueConfig = getMainQueueConfiguration(); - profile.getProfileData().setQueueConfiguration(Collections.singletonList((mainQueueConfig))); - tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, profile); - List isolatedTenants = tenantService.findTenantIdsByTenantProfileId(profile.getId()); - isolatedTenants.forEach(tenantId -> { - queueService.saveQueue(new Queue(tenantId, mainQueueConfig)); - }); - } - } catch (Exception e) { - log.error("Failed to update tenant profile queue configuration name=[" + profile.getName() + "], id=[" + profile.getId().getId() + "]", e); - } - } - - private TenantProfileQueueConfiguration getMainQueueConfiguration() { - TenantProfileQueueConfiguration mainQueueConfiguration = new TenantProfileQueueConfiguration(); - mainQueueConfiguration.setName(DataConstants.MAIN_QUEUE_NAME); - mainQueueConfiguration.setTopic(DataConstants.MAIN_QUEUE_TOPIC); - mainQueueConfiguration.setPollInterval(25); - mainQueueConfiguration.setPartitions(10); - mainQueueConfiguration.setConsumerPerPartition(true); - mainQueueConfiguration.setPackProcessingTimeout(2000); - SubmitStrategy mainQueueSubmitStrategy = new SubmitStrategy(); - mainQueueSubmitStrategy.setType(SubmitStrategyType.BURST); - mainQueueSubmitStrategy.setBatchSize(1000); - mainQueueConfiguration.setSubmitStrategy(mainQueueSubmitStrategy); - ProcessingStrategy mainQueueProcessingStrategy = new ProcessingStrategy(); - mainQueueProcessingStrategy.setType(ProcessingStrategyType.SKIP_ALL_FAILURES); - mainQueueProcessingStrategy.setRetries(3); - mainQueueProcessingStrategy.setFailurePercentage(0); - mainQueueProcessingStrategy.setPauseBetweenRetries(3); - mainQueueProcessingStrategy.setMaxPauseBetweenRetries(3); - mainQueueConfiguration.setProcessingStrategy(mainQueueProcessingStrategy); - return mainQueueConfiguration; - } - public static boolean getEnv(String name, boolean defaultValue) { String env = System.getenv(name); if (env == null) { diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java deleted file mode 100644 index 3f2a80f4e2..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright © 2016-2024 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.install.update; - -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; - -@Slf4j -public abstract class PaginatedUpdater { - - private static final int DEFAULT_LIMIT = 100; - private int updated = 0; - - public void updateEntities(I id) { - updated = 0; - PageLink pageLink = new PageLink(DEFAULT_LIMIT); - boolean hasNext = true; - while (hasNext) { - PageData entities = findEntities(id, pageLink); - for (D entity : entities.getData()) { - updateEntity(entity); - } - updated += entities.getData().size(); - hasNext = entities.hasNext(); - if (hasNext) { - log.info("{}: {} entities updated so far...", getName(), updated); - pageLink = pageLink.nextPageLink(); - } else { - if (updated > DEFAULT_LIMIT || forceReportTotal()) { - log.info("{}: {} total entities updated.", getName(), updated); - } - } - } - } - - public void updateEntities() { - updateEntities(null); - } - - protected boolean forceReportTotal() { - return false; - } - - protected abstract String getName(); - - protected abstract PageData findEntities(I id, PageLink pageLink); - - protected abstract void updateEntity(D entity); - -} diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java deleted file mode 100644 index 4a2abcc6cf..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright © 2016-2024 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.install.update; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.TenantProfile; -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.tenant.TenantProfileService; - -@Component -class RateLimitsUpdater extends PaginatedUpdater { - - @Value("#{ environment.getProperty('TB_SERVER_REST_LIMITS_TENANT_ENABLED') ?: environment.getProperty('server.rest.limits.tenant.enabled') ?: 'false' }") - boolean tenantServerRestLimitsEnabled; - @Value("#{ environment.getProperty('TB_SERVER_REST_LIMITS_TENANT_CONFIGURATION') ?: environment.getProperty('server.rest.limits.tenant.configuration') ?: '100:1,2000:60' }") - String tenantServerRestLimitsConfiguration; - @Value("#{ environment.getProperty('TB_SERVER_REST_LIMITS_CUSTOMER_ENABLED') ?: environment.getProperty('server.rest.limits.customer.enabled') ?: 'false' }") - boolean customerServerRestLimitsEnabled; - @Value("#{ environment.getProperty('TB_SERVER_REST_LIMITS_CUSTOMER_CONFIGURATION') ?: environment.getProperty('server.rest.limits.customer.configuration') ?: '50:1,1000:60' }") - String customerServerRestLimitsConfiguration; - - @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_TENANT') ?: environment.getProperty('server.ws.limits.max_sessions_per_tenant') ?: '0' }") - private int maxWsSessionsPerTenant; - @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_CUSTOMER') ?: environment.getProperty('server.ws.limits.max_sessions_per_customer') ?: '0' }") - private int maxWsSessionsPerCustomer; - @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_REGULAR_USER') ?: environment.getProperty('server.ws.limits.max_sessions_per_regular_user') ?: '0' }") - private int maxWsSessionsPerRegularUser; - @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_PUBLIC_USER') ?: environment.getProperty('server.ws.limits.max_sessions_per_public_user') ?: '0' }") - private int maxWsSessionsPerPublicUser; - @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_QUEUE_PER_WS_SESSION') ?: environment.getProperty('server.ws.limits.max_queue_per_ws_session') ?: '500' }") - private int wsMsgQueueLimitPerSession; - @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_TENANT') ?: environment.getProperty('server.ws.limits.max_subscriptions_per_tenant') ?: '0' }") - private long maxWsSubscriptionsPerTenant; - @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_CUSTOMER') ?: environment.getProperty('server.ws.limits.max_subscriptions_per_customer') ?: '0' }") - private long maxWsSubscriptionsPerCustomer; - @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_REGULAR_USER') ?: environment.getProperty('server.ws.limits.max_subscriptions_per_regular_user') ?: '0' }") - private long maxWsSubscriptionsPerRegularUser; - @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_PUBLIC_USER') ?: environment.getProperty('server.ws.limits.max_subscriptions_per_public_user') ?: '0' }") - private long maxWsSubscriptionsPerPublicUser; - @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_UPDATES_PER_SESSION') ?: environment.getProperty('server.ws.limits.max_updates_per_session') ?: '300:1,3000:60' }") - private String wsUpdatesPerSessionRateLimit; - - @Value("#{ environment.getProperty('CASSANDRA_QUERY_TENANT_RATE_LIMITS_ENABLED') ?: environment.getProperty('cassandra.query.tenant_rate_limits.enabled') ?: 'false' }") - private boolean cassandraQueryTenantRateLimitsEnabled; - @Value("#{ environment.getProperty('CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION') ?: environment.getProperty('cassandra.query.tenant_rate_limits.configuration') ?: '1000:1,30000:60' }") - private String cassandraQueryTenantRateLimitsConfiguration; - - @Autowired - private TenantProfileService tenantProfileService; - - @Override - protected boolean forceReportTotal() { - return true; - } - - @Override - protected String getName() { - return "Rate limits updater"; - } - - @Override - protected PageData findEntities(String id, PageLink pageLink) { - return tenantProfileService.findTenantProfiles(TenantId.SYS_TENANT_ID, pageLink); - } - - @Override - protected void updateEntity(TenantProfile tenantProfile) { - var profileConfiguration = tenantProfile.getDefaultProfileConfiguration(); - - if (tenantServerRestLimitsEnabled && StringUtils.isNotEmpty(tenantServerRestLimitsConfiguration)) { - profileConfiguration.setTenantServerRestLimitsConfiguration(tenantServerRestLimitsConfiguration); - } - if (customerServerRestLimitsEnabled && StringUtils.isNotEmpty(customerServerRestLimitsConfiguration)) { - profileConfiguration.setCustomerServerRestLimitsConfiguration(customerServerRestLimitsConfiguration); - } - - profileConfiguration.setMaxWsSessionsPerTenant(maxWsSessionsPerTenant); - profileConfiguration.setMaxWsSessionsPerCustomer(maxWsSessionsPerCustomer); - profileConfiguration.setMaxWsSessionsPerPublicUser(maxWsSessionsPerPublicUser); - profileConfiguration.setMaxWsSessionsPerRegularUser(maxWsSessionsPerRegularUser); - profileConfiguration.setMaxWsSubscriptionsPerTenant(maxWsSubscriptionsPerTenant); - profileConfiguration.setMaxWsSubscriptionsPerCustomer(maxWsSubscriptionsPerCustomer); - profileConfiguration.setMaxWsSubscriptionsPerPublicUser(maxWsSubscriptionsPerPublicUser); - profileConfiguration.setMaxWsSubscriptionsPerRegularUser(maxWsSubscriptionsPerRegularUser); - profileConfiguration.setWsMsgQueueLimitPerSession(wsMsgQueueLimitPerSession); - if (StringUtils.isNotEmpty(wsUpdatesPerSessionRateLimit)) { - profileConfiguration.setWsUpdatesPerSessionRateLimit(wsUpdatesPerSessionRateLimit); - } - - if (cassandraQueryTenantRateLimitsEnabled && StringUtils.isNotEmpty(cassandraQueryTenantRateLimitsConfiguration)) { - profileConfiguration.setCassandraQueryTenantRateLimitsConfiguration(cassandraQueryTenantRateLimitsConfiguration); - } - - tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java index ba4b4283e7..96b638a7a3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java @@ -42,6 +42,4 @@ public interface AuditLogDao extends Dao { void cleanUpAuditLogs(long expTime); - void migrateAuditLogs(); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java index f78dc339dc..21324d7f75 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java @@ -151,35 +151,4 @@ public class JpaAuditLogDao extends JpaAbstractDao imp partitioningRepository.dropPartitionsBefore(TABLE_NAME, expTime, TimeUnit.HOURS.toMillis(partitionSizeInHours)); } - @Override - public void migrateAuditLogs() { - long startTime = ttlInSec > 0 ? System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec) : 1480982400000L; - - long currentTime = System.currentTimeMillis(); - var partitionStepInMs = TimeUnit.HOURS.toMillis(partitionSizeInHours); - long numberOfPartitions = (currentTime - startTime) / partitionStepInMs; - - if (numberOfPartitions > 1000) { - String error = "Please adjust your audit logs partitioning configuration. Configuration with partition size " + - "of " + partitionSizeInHours + " hours and corresponding TTL will use " + numberOfPartitions + " " + - "(> 1000) partitions which is not recommended!"; - log.error(error); - throw new RuntimeException(error); - } - - while (startTime < currentTime) { - var endTime = startTime + partitionStepInMs; - log.info("Migrating audit logs for time period: {} - {}", startTime, endTime); - callMigrationFunction(startTime, endTime, partitionStepInMs); - startTime = endTime; - } - log.info("Audit logs migration finished"); - - jdbcTemplate.execute("DROP TABLE IF EXISTS old_audit_log"); - } - - private void callMigrationFunction(long startTime, long endTime, long partitionSizeInMs) { - jdbcTemplate.update("CALL migrate_audit_logs(?, ?, ?)", startTime, endTime, partitionSizeInMs); - } - } diff --git a/application/src/main/data/upgrade/3.0.1/schema_ts_latest.sql b/dao/src/main/resources/sql/schema-ts-latest-psql.sql similarity index 100% rename from application/src/main/data/upgrade/3.0.1/schema_ts_latest.sql rename to dao/src/main/resources/sql/schema-ts-latest-psql.sql From 1fce9582af8efce945c76e295493d3d98d536db3 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 29 Jan 2024 17:53:39 +0200 Subject: [PATCH 02/11] deleted redundant code, yml parameters, reverted deleted PaginatedUpdater for future development --- .../install/update/PaginatedUpdater.java | 64 +++++++++++++++++++ .../src/main/resources/thingsboard.yml | 52 --------------- .../resources/application-test.properties | 15 ----- .../server/dao/event/EventService.java | 1 - .../server/dao/event/BaseEventService.java | 5 -- .../resources/application-test.properties | 15 ----- dao/src/test/resources/nosql-test.properties | 11 ---- dao/src/test/resources/sql-test.properties | 11 ---- 8 files changed, 64 insertions(+), 110 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java new file mode 100644 index 0000000000..3f2a80f4e2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java @@ -0,0 +1,64 @@ +/** + * Copyright © 2016-2024 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.install.update; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; + +@Slf4j +public abstract class PaginatedUpdater { + + private static final int DEFAULT_LIMIT = 100; + private int updated = 0; + + public void updateEntities(I id) { + updated = 0; + PageLink pageLink = new PageLink(DEFAULT_LIMIT); + boolean hasNext = true; + while (hasNext) { + PageData entities = findEntities(id, pageLink); + for (D entity : entities.getData()) { + updateEntity(entity); + } + updated += entities.getData().size(); + hasNext = entities.hasNext(); + if (hasNext) { + log.info("{}: {} entities updated so far...", getName(), updated); + pageLink = pageLink.nextPageLink(); + } else { + if (updated > DEFAULT_LIMIT || forceReportTotal()) { + log.info("{}: {} total entities updated.", getName(), updated); + } + } + } + } + + public void updateEntities() { + updateEntities(null); + } + + protected boolean forceReportTotal() { + return false; + } + + protected abstract String getName(); + + protected abstract PageData findEntities(I id, PageLink pageLink); + + protected abstract void updateEntity(D entity); + +} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 936f30af60..70a5146bb7 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1601,58 +1601,6 @@ queue: print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}" # Max length of the error message that is printed by statistics max-error-message-length: "${TB_QUEUE_RULE_ENGINE_MAX_ERROR_MESSAGE_LENGTH:4096}" - queues: - - name: "${TB_QUEUE_RE_MAIN_QUEUE_NAME:Main}" # queue name - topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}" # queue topic - poll-interval: "${TB_QUEUE_RE_MAIN_POLL_INTERVAL_MS:25}" # poll interval - partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}" # number queue partitions - consumer-per-partition: "${TB_QUEUE_RE_MAIN_CONSUMER_PER_PARTITION:true}" # if true - use for each customer different partition - pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:2000}" # Timeout for processing a message pack - submit-strategy: - type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL - # For BATCH only - batch-size: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_BATCH_SIZE:1000}" # Maximum number of messages in batch - processing-strategy: - type: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_TYPE:SKIP_ALL_FAILURES}" # SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT - # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT - retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited - failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less than X percentage of messages; - pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}" # Time in seconds to wait in consumer thread before retries; - max-pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:3}" # Max allowed time in seconds for pause between retries. - - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}" # queue name - topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}" # queue topic - poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}" # poll interval - partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}" # number queue partitions - consumer-per-partition: "${TB_QUEUE_RE_HP_CONSUMER_PER_PARTITION:true}" # if true - use for each customer different partition - pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:2000}" # Timeout for processing a message pack - submit-strategy: - type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL - # For BATCH only - batch-size: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch - processing-strategy: - type: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT - # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT - retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited - failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less than X percentage of messages; - pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}" # Time in seconds to wait in consumer thread before retries; - max-pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}" # Max allowed time in seconds for pause between retries. - - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}" # queue name - topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}" # queue topic - poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}" # poll interval - partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}" # number queue partitions - consumer-per-partition: "${TB_QUEUE_RE_SQ_CONSUMER_PER_PARTITION:true}" # if true - use for each customer different partition - pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:2000}" # Timeout for processing a message pack - submit-strategy: - type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL - # For BATCH only - batch-size: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch - processing-strategy: - type: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT - # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT - retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited - failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less than X percentage of messages; - pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}" # Time in seconds to wait in consumer thread before retries; - max-pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}" # Max allowed time in seconds for pause between retries. # After a queue is deleted (or the profile's isolation option was disabled), Rule Engine will continue reading related topics during this period before deleting the actual topics topic-deletion-delay: "${TB_QUEUE_RULE_ENGINE_TOPIC_DELETION_DELAY_SEC:15}" # Size of the thread pool that handles such operations as partition changes, config updates, queue deletion diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index b01ffe0d72..92959e4552 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -42,21 +42,6 @@ queue.transport.poll_interval=5 queue.core.poll-interval=5 queue.core.partitions=2 queue.rule-engine.poll-interval=5 -queue.rule-engine.queues[0].poll-interval=5 -queue.rule-engine.queues[0].partitions=2 -queue.rule-engine.queues[0].processing-strategy.retries=1 -queue.rule-engine.queues[0].processing-strategy.pause-between-retries=0 -queue.rule-engine.queues[0].processing-strategy.max-pause-between-retries=0 -queue.rule-engine.queues[1].poll-interval=5 -queue.rule-engine.queues[1].partitions=2 -queue.rule-engine.queues[1].processing-strategy.retries=1 -queue.rule-engine.queues[1].processing-strategy.pause-between-retries=0 -queue.rule-engine.queues[1].processing-strategy.max-pause-between-retries=0 -queue.rule-engine.queues[2].poll-interval=5 -queue.rule-engine.queues[2].partitions=2 -queue.rule-engine.queues[2].processing-strategy.retries=1 -queue.rule-engine.queues[2].processing-strategy.pause-between-retries=0 -queue.rule-engine.queues[2].processing-strategy.max-pause-between-retries=0 queue.rule-engine.stats.enabled=true usage.stats.report.enabled=false diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java index 219b4163f9..9b0d5a280d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java @@ -43,5 +43,4 @@ public interface EventService { void cleanupEvents(long regularEventExpTs, long debugEventExpTs, boolean cleanupDb); - void migrateEvents(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java index b9d7f7df86..88834eecbc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java @@ -130,11 +130,6 @@ public class BaseEventService implements EventService { eventDao.cleanupEvents(regularEventExpTs, debugEventExpTs, cleanupDb); } - @Override - public void migrateEvents() { - eventDao.migrateEvents(ttlInSec > 0 ? (System.currentTimeMillis() - ttlInSec * 1000) : 0, debugTtlInSec > 0 ? (System.currentTimeMillis() - debugTtlInSec * 1000) : 0); - } - private PageData convert(EntityType entityType, PageData pd) { return new PageData<>(pd.getData() == null ? null : pd.getData().stream().map(e -> e.toInfo(entityType)).collect(Collectors.toList()) diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index 05f2cf7670..1b5c4b93da 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -124,20 +124,5 @@ queue.transport.poll_interval=5 queue.core.poll-interval=5 queue.core.partitions=2 queue.rule-engine.poll-interval=5 -queue.rule-engine.queues[0].poll-interval=5 -queue.rule-engine.queues[0].partitions=2 -queue.rule-engine.queues[0].processing-strategy.retries=1 -queue.rule-engine.queues[0].processing-strategy.pause-between-retries=0 -queue.rule-engine.queues[0].processing-strategy.max-pause-between-retries=0 -queue.rule-engine.queues[1].poll-interval=5 -queue.rule-engine.queues[1].partitions=2 -queue.rule-engine.queues[1].processing-strategy.retries=1 -queue.rule-engine.queues[1].processing-strategy.pause-between-retries=0 -queue.rule-engine.queues[1].processing-strategy.max-pause-between-retries=0 -queue.rule-engine.queues[2].poll-interval=5 -queue.rule-engine.queues[2].partitions=2 -queue.rule-engine.queues[2].processing-strategy.retries=1 -queue.rule-engine.queues[2].processing-strategy.pause-between-retries=0 -queue.rule-engine.queues[2].processing-strategy.max-pause-between-retries=0 spring.jpa.properties.hibernate.dialect=org.thingsboard.server.dao.ThingsboardPostgreSQLDialect \ No newline at end of file diff --git a/dao/src/test/resources/nosql-test.properties b/dao/src/test/resources/nosql-test.properties index d63a9f7f82..d72bb10b82 100644 --- a/dao/src/test/resources/nosql-test.properties +++ b/dao/src/test/resources/nosql-test.properties @@ -16,14 +16,3 @@ spring.datasource.password=postgres spring.datasource.url=jdbc:tc:postgresql:12.8:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver spring.datasource.hikari.maximumPoolSize=16 - -queue.rule-engine.queues[0].name=Main -queue.rule-engine.queues[0].topic=tb_rule_engine.main -queue.rule-engine.queues[0].poll-interval=5 -queue.rule-engine.queues[0].partitions=2 -queue.rule-engine.queues[0].pack-processing-timeout=3000 -queue.rule-engine.queues[0].processing-strategy.type=SKIP_ALL_FAILURES -queue.rule-engine.queues[0].processing-strategy.retries=1 -queue.rule-engine.queues[0].processing-strategy.pause-between-retries=0 -queue.rule-engine.queues[0].processing-strategy.max-pause-between-retries=0 -queue.rule-engine.queues[0].submit-strategy.type=BURST diff --git a/dao/src/test/resources/sql-test.properties b/dao/src/test/resources/sql-test.properties index 2fed33b43a..4292db28d6 100644 --- a/dao/src/test/resources/sql-test.properties +++ b/dao/src/test/resources/sql-test.properties @@ -41,15 +41,4 @@ service.type=monolith queue.core.pack-processing-timeout=3000 queue.rule-engine.pack-processing-timeout=3000 -queue.rule-engine.queues[0].name=Main -queue.rule-engine.queues[0].topic=tb_rule_engine.main -queue.rule-engine.queues[0].poll-interval=5 -queue.rule-engine.queues[0].partitions=2 -queue.rule-engine.queues[0].pack-processing-timeout=3000 -queue.rule-engine.queues[0].processing-strategy.type=SKIP_ALL_FAILURES -queue.rule-engine.queues[0].processing-strategy.retries=1 -queue.rule-engine.queues[0].processing-strategy.pause-between-retries=0 -queue.rule-engine.queues[0].processing-strategy.max-pause-between-retries=0 -queue.rule-engine.queues[0].submit-strategy.type=BURST - sql.log_entity_queries=true From 30399d8ff6f185ac82192c22854a040f8a91a8d9 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 29 Jan 2024 18:07:51 +0200 Subject: [PATCH 03/11] deleted unused service --- .../TbRuleEngineQueueConfigService.java | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 application/src/main/java/org/thingsboard/server/service/install/TbRuleEngineQueueConfigService.java diff --git a/application/src/main/java/org/thingsboard/server/service/install/TbRuleEngineQueueConfigService.java b/application/src/main/java/org/thingsboard/server/service/install/TbRuleEngineQueueConfigService.java deleted file mode 100644 index 890912a4b5..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/install/TbRuleEngineQueueConfigService.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright © 2016-2024 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.install; - -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.thingsboard.server.common.data.DataConstants; -import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; - -import javax.annotation.PostConstruct; -import java.util.List; - -@Slf4j -@Data -@EnableAutoConfiguration -@Configuration -@ConfigurationProperties(prefix = "queue.rule-engine") -@Profile("install") -public class TbRuleEngineQueueConfigService { - - private String topic; - private List queues; - - @PostConstruct - public void validate() { - queues.stream().filter(queue -> queue.getName().equals(DataConstants.MAIN_QUEUE_NAME)).findFirst().orElseThrow(() -> { - log.error("Main queue is not configured in thingsboard.yml"); - return new RuntimeException("No \"Main\" queue configured!"); - }); - } - -} From ce9351ba276f40f5e9f080519def16239621ec0d Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 30 Jan 2024 18:20:23 +0200 Subject: [PATCH 04/11] deleted unused classes --- ...leEngineQueueAckStrategyConfiguration.java | 30 ----------------- .../TbRuleEngineQueueConfiguration.java | 33 ------------------- ...ngineQueueSubmitStrategyConfiguration.java | 27 --------------- 3 files changed, 90 deletions(-) delete mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/settings/TbRuleEngineQueueAckStrategyConfiguration.java delete mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/settings/TbRuleEngineQueueConfiguration.java delete mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/settings/TbRuleEngineQueueSubmitStrategyConfiguration.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbRuleEngineQueueAckStrategyConfiguration.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbRuleEngineQueueAckStrategyConfiguration.java deleted file mode 100644 index 8edb224fec..0000000000 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbRuleEngineQueueAckStrategyConfiguration.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2024 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.queue.settings; - -import lombok.Data; - -@Data -@Deprecated -public class TbRuleEngineQueueAckStrategyConfiguration { - - private String type; - private int retries; - private double failurePercentage; - private long pauseBetweenRetries; - private long maxPauseBetweenRetries; - -} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbRuleEngineQueueConfiguration.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbRuleEngineQueueConfiguration.java deleted file mode 100644 index 8199977618..0000000000 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbRuleEngineQueueConfiguration.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright © 2016-2024 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.queue.settings; - -import lombok.Data; - -@Data -@Deprecated -public class TbRuleEngineQueueConfiguration { - - private String name; - private String topic; - private int pollInterval; - private int partitions; - private boolean consumerPerPartition; - private long packProcessingTimeout; - private TbRuleEngineQueueSubmitStrategyConfiguration submitStrategy; - private TbRuleEngineQueueAckStrategyConfiguration processingStrategy; - -} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbRuleEngineQueueSubmitStrategyConfiguration.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbRuleEngineQueueSubmitStrategyConfiguration.java deleted file mode 100644 index f073afad17..0000000000 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbRuleEngineQueueSubmitStrategyConfiguration.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2024 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.queue.settings; - -import lombok.Data; - -@Data -@Deprecated -public class TbRuleEngineQueueSubmitStrategyConfiguration { - - private String type; - private int batchSize; - -} From 46585240227a56db505dc63b0311d6a9ada0371e Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 31 Jan 2024 14:37:16 +0200 Subject: [PATCH 05/11] fixed license --- .../thingsboard/server/dao/sql/JpaPartitionedAbstractDao.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaPartitionedAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaPartitionedAbstractDao.java index 76db367ffb..a933837f5d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaPartitionedAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaPartitionedAbstractDao.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 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. From 4b29a8f3ff3aec1777828630e03e7ea20841ba0f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 1 Feb 2024 19:16:37 +0200 Subject: [PATCH 06/11] UI: Implement action button widget. --- .../json/system/widget_bundles/buttons.json | 13 + .../system/widget_types/action_button.json | 27 ++ ui-ngx/src/app/core/api/widget-api.models.ts | 1 + ui-ngx/src/app/core/services/utils.service.ts | 26 +- ui-ngx/src/app/modules/common/modules-map.ts | 16 +- .../dashboard/dashboard.component.html | 1 + .../dashboard/dashboard.component.ts | 3 + .../action/widget-action-dialog.component.ts | 11 +- .../basic/basic-widget-config.module.ts | 12 +- .../action-button-basic-config.component.html | 77 ++++++ .../action-button-basic-config.component.ts | 139 ++++++++++ .../single-switch-basic-config.component.html | 18 +- .../widget/config/datasource.component.html | 4 +- .../widget/config/datasource.component.ts | 30 +- .../widget/config/datasources.component.ts | 5 +- .../config/widget-config-components.module.ts | 17 +- .../config/widget-config.component.models.ts | 14 +- .../widget/lib/action/action-widget.scss | 46 ++++ .../action-button-widget.component.html | 35 +++ .../action-button-widget.component.scss | 28 ++ .../button/action-button-widget.component.ts | 100 +++++++ .../lib/button/action-button-widget.models.ts | 67 +++++ .../rpc/single-switch-widget.component.html | 8 +- .../rpc/single-switch-widget.component.scss | 30 -- .../lib/rpc/single-switch-widget.component.ts | 26 +- ... => action-settings-button.component.html} | 4 +- ...utton.scss => action-settings-button.scss} | 2 +- ...s => action-settings-panel.component.scss} | 8 +- ...custom-action-pretty-editor.component.html | 0 ...custom-action-pretty-editor.component.scss | 2 +- .../custom-action-pretty-editor.component.ts | 4 +- ...ction-pretty-resources-tabs.component.html | 0 ...ction-pretty-resources-tabs.component.scss | 0 ...-action-pretty-resources-tabs.component.ts | 2 +- .../common}/action/custom-action.models.ts | 0 .../common}/action/custom-sample-css.raw | 0 .../common}/action/custom-sample-html.raw | 0 .../common}/action/custom-sample-js.raw | 0 ...value-action-settings-panel.component.html | 14 +- ...t-value-action-settings-panel.component.ts | 21 +- .../get-value-action-settings.component.ts | 29 +- .../mobile-action-editor.component.html | 0 .../action/mobile-action-editor.component.ts | 4 +- .../action/mobile-action-editor.models.ts | 0 ...value-action-settings-panel.component.html | 8 +- ...t-value-action-settings-panel.component.ts | 12 +- .../set-value-action-settings.component.ts | 14 +- ...idget-action-settings-panel.component.html | 42 +++ .../widget-action-settings-panel.component.ts | 88 ++++++ .../widget-action-settings.component.ts | 136 +++++++++ .../action/widget-action.component.html | 0 .../common}/action/widget-action.component.ts | 2 +- .../widget-button-appearance.component.html | 97 +++++++ .../widget-button-appearance.component.ts | 141 ++++++++++ ...t-button-custom-style-panel.component.html | 93 +++++++ ...t-button-custom-style-panel.component.scss | 68 +++++ ...get-button-custom-style-panel.component.ts | 171 ++++++++++++ .../widget-button-custom-style.component.html | 44 +++ .../widget-button-custom-style.component.scss | 46 ++++ .../widget-button-custom-style.component.ts | 157 +++++++++++ .../common/widget-settings-common.module.ts | 47 +++- ...ngle-switch-widget-settings.component.html | 18 +- ...single-switch-widget-settings.component.ts | 6 +- .../widget/widget-component.service.ts | 3 + .../widget/widget-components.module.ts | 7 +- .../widget/widget-container.component.html | 2 + .../widget/widget-container.component.scss | 7 + .../widget/widget-container.component.ts | 24 +- .../widget/widget-preview.component.html | 1 + .../components/widget/widget.component.ts | 16 +- .../home/models/widget-component.models.ts | 2 + .../button/widget-button.component.html | 39 +++ .../button/widget-button.component.scss | 189 +++++++++++++ .../button/widget-button.component.ts | 178 ++++++++++++ .../components/button/widget-button.models.ts | 259 ++++++++++++++++++ .../models/action-widget-settings.models.ts | 19 +- .../shared/models/widget-settings.models.ts | 27 +- ui-ngx/src/app/shared/models/widget.models.ts | 26 ++ ui-ngx/src/app/shared/shared.module.ts | 7 +- .../assets/locale/locale.constant-en_US.json | 47 +++- ui-ngx/src/assets/widget/button/basic.svg | 17 ++ ui-ngx/src/assets/widget/button/filled.svg | 19 ++ ui-ngx/src/assets/widget/button/outlined.svg | 20 ++ .../src/assets/widget/button/underlined.svg | 23 ++ ui-ngx/src/form.scss | 8 + 85 files changed, 2790 insertions(+), 184 deletions(-) create mode 100644 application/src/main/data/json/system/widget_bundles/buttons.json create mode 100644 application/src/main/data/json/system/widget_types/action_button.json create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/button/action-button-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/button/action-button-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.models.ts rename ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/{value-action-settings-button.component.html => action-settings-button.component.html} (88%) rename ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/{value-action-settings-button.scss => action-settings-button.scss} (95%) rename ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/{value-action-settings-panel.component.scss => action-settings-panel.component.scss} (87%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/custom-action-pretty-editor.component.html (100%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/custom-action-pretty-editor.component.scss (95%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/custom-action-pretty-editor.component.ts (95%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/custom-action-pretty-resources-tabs.component.html (100%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/custom-action-pretty-resources-tabs.component.scss (100%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/custom-action-pretty-resources-tabs.component.ts (99%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/custom-action.models.ts (100%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/custom-sample-css.raw (100%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/custom-sample-html.raw (100%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/custom-sample-js.raw (100%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/mobile-action-editor.component.html (100%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/mobile-action-editor.component.ts (98%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/mobile-action-editor.models.ts (100%) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings.component.ts rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/widget-action.component.html (100%) rename ui-ngx/src/app/modules/home/components/widget/{config => lib/settings/common}/action/widget-action.component.ts (99%) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts create mode 100644 ui-ngx/src/app/shared/components/button/widget-button.component.html create mode 100644 ui-ngx/src/app/shared/components/button/widget-button.component.scss create mode 100644 ui-ngx/src/app/shared/components/button/widget-button.component.ts create mode 100644 ui-ngx/src/app/shared/components/button/widget-button.models.ts create mode 100644 ui-ngx/src/assets/widget/button/basic.svg create mode 100644 ui-ngx/src/assets/widget/button/filled.svg create mode 100644 ui-ngx/src/assets/widget/button/outlined.svg create mode 100644 ui-ngx/src/assets/widget/button/underlined.svg diff --git a/application/src/main/data/json/system/widget_bundles/buttons.json b/application/src/main/data/json/system/widget_bundles/buttons.json new file mode 100644 index 0000000000..046e16dd7e --- /dev/null +++ b/application/src/main/data/json/system/widget_bundles/buttons.json @@ -0,0 +1,13 @@ +{ + "widgetsBundle": { + "alias": "buttons", + "title": "Buttons", + "image": null, + "description": null, + "order": 7500, + "name": "Buttons" + }, + "widgetTypeFqns": [ + "action_button" + ] +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/action_button.json b/application/src/main/data/json/system/widget_types/action_button.json new file mode 100644 index 0000000000..3ad518ce23 --- /dev/null +++ b/application/src/main/data/json/system/widget_types/action_button.json @@ -0,0 +1,27 @@ +{ + "fqn": "action_button", + "name": "Action button", + "deprecated": false, + "image": "tb-image:YWN0aW9uLWJ1dHRvbi5zdmc=:IkFjdGlvbiBidXR0b24iIHN5c3RlbSB3aWRnZXQgaW1hZ2U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9IjAuNzUiIHk9IjUwLjc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIGZpbGw9IndoaXRlIi8+CjxyZWN0IHg9IjAuNzUiIHk9IjUwLjc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIHN0cm9rZT0iIzNGNTJERCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KPHBhdGggZD0iTTYyLjE2NzMgODkuMzMzM1Y4Mi4zMzMzSDY2LjgzNFY4OS4zMzMzSDcyLjY2NzNWODBINzYuMTY3M0w2NC41MDA3IDY5LjVMNTIuODM0IDgwSDU2LjMzNFY4OS4zMzMzSDYyLjE2NzNaIiBmaWxsPSIjM0Y1MkREIi8+CjxwYXRoIGQ9Ik05MC4xOTUzIDgwLjkzMTZIODYuMjFMODYuMTg4NSA3OC45NjU4SDg5LjY2ODlDOTAuMjU2MiA3OC45NjU4IDkwLjc1MzkgNzguODc5OSA5MS4xNjIxIDc4LjcwOEM5MS41Nzc1IDc4LjUyOSA5MS44OTI2IDc4LjI3NDcgOTIuMTA3NCA3Ny45NDUzQzkyLjMyMjMgNzcuNjA4NyA5Mi40Mjk3IDc3LjIwNDEgOTIuNDI5NyA3Ni43MzE0QzkyLjQyOTcgNzYuMjA4NyA5Mi4zMjk0IDc1Ljc4MjYgOTIuMTI4OSA3NS40NTMxQzkxLjkyODQgNzUuMTIzNyA5MS42MjA0IDc0Ljg4MzggOTEuMjA1MSA3NC43MzM0QzkwLjc5NjkgNzQuNTgzIDkwLjI3NDEgNzQuNTA3OCA4OS42MzY3IDc0LjUwNzhIODcuMDI2NFY4OEg4NC4zMzAxVjcyLjM1OTRIODkuNjM2N0M5MC40OTYxIDcyLjM1OTQgOTEuMjYyNCA3Mi40NDE3IDkxLjkzNTUgNzIuNjA2NEM5Mi42MTU5IDcyLjc3MTIgOTMuMTkyNCA3My4wMjkgOTMuNjY1IDczLjM3OTlDOTQuMTQ0OSA3My43MjM2IDk0LjUwNjUgNzQuMTYwNSA5NC43NSA3NC42OTA0Qzk1LjAwMDcgNzUuMjIwNCA5NS4xMjYgNzUuODUwNiA5NS4xMjYgNzYuNTgxMUM5NS4xMjYgNzcuMjI1NiA5NC45NzIgNzcuODE2NCA5NC42NjQxIDc4LjM1MzVDOTQuMzU2MSA3OC44ODM1IDkzLjkwMTQgNzkuMzE2NyA5My4yOTk4IDc5LjY1MzNDOTIuNjk4MiA3OS45ODk5IDkxLjk0OTkgODAuMTkwNCA5MS4wNTQ3IDgwLjI1NDlMOTAuMTk1MyA4MC45MzE2Wk05MC4wNzcxIDg4SDg1LjM2MTNMODYuNTc1MiA4NS44NjIzSDkwLjA3NzFDOTAuNjg1OSA4NS44NjIzIDkxLjE5NDMgODUuNzYyIDkxLjYwMjUgODUuNTYxNUM5Mi4wMTA3IDg1LjM1MzggOTIuMzE1MSA4NS4wNzEgOTIuNTE1NiA4NC43MTI5QzkyLjcyMzMgODQuMzQ3NyA5Mi44MjcxIDgzLjkyMTUgOTIuODI3MSA4My40MzQ2QzkyLjgyNzEgODIuOTI2MSA5Mi43Mzc2IDgyLjQ4NTcgOTIuNTU4NiA4Mi4xMTMzQzkyLjM3OTYgODEuNzMzNyA5Mi4wOTY3IDgxLjQ0MzcgOTEuNzEgODEuMjQzMkM5MS4zMjMyIDgxLjAzNTUgOTAuODE4NCA4MC45MzE2IDkwLjE5NTMgODAuOTMxNkg4Ny4xNjZMODcuMTg3NSA3OC45NjU4SDkxLjEyOTlMOTEuNzQyMiA3OS43MDdDOTIuNjAxNiA3OS43MzU3IDkzLjMwNyA3OS45MjU1IDkzLjg1ODQgODAuMjc2NEM5NC40MTcgODAuNjI3MyA5NC44MzI0IDgxLjA4MiA5NS4xMDQ1IDgxLjY0MDZDOTUuMzc2NiA4Mi4xOTkyIDk1LjUxMjcgODIuODAwOCA5NS41MTI3IDgzLjQ0NTNDOTUuNTEyNyA4NC40NDA4IDk1LjI5NDMgODUuMjc1MSA5NC44NTc0IDg1Ljk0ODJDOTQuNDI3NyA4Ni42MjE0IDkzLjgwODMgODcuMTMzNSA5Mi45OTkgODcuNDg0NEM5Mi4xODk4IDg3LjgyODEgOTEuMjE1OCA4OCA5MC4wNzcxIDg4Wk0xMDUuMjE2IDg1LjI2MDdWNzYuMzc3SDEwNy44MTVWODhIMTA1LjM2NkwxMDUuMjE2IDg1LjI2MDdaTTEwNS41ODEgODIuODQzOEwxMDYuNDUxIDgyLjgyMjNDMTA2LjQ1MSA4My42MDI5IDEwNi4zNjUgODQuMzIyNiAxMDYuMTkzIDg0Ljk4MTRDMTA2LjAyMSA4NS42MzMxIDEwNS43NTcgODYuMjAyNSAxMDUuMzk4IDg2LjY4OTVDMTA1LjA0IDg3LjE2OTMgMTA0LjU4MiA4Ny41NDUyIDEwNC4wMjMgODcuODE3NEMxMDMuNDY1IDg4LjA4MjQgMTAyLjc5NSA4OC4yMTQ4IDEwMi4wMTUgODguMjE0OEMxMDEuNDQ5IDg4LjIxNDggMTAwLjkzIDg4LjEzMjUgMTAwLjQ1NyA4Ny45Njc4Qzk5Ljk4NDQgODcuODAzMSA5OS41NzYyIDg3LjU0ODggOTkuMjMyNCA4Ny4yMDUxQzk4Ljg5NTggODYuODYxMyA5OC42MzQ0IDg2LjQxMzcgOTguNDQ4MiA4NS44NjIzQzk4LjI2MiA4NS4zMTA5IDk4LjE2ODkgODQuNjUyIDk4LjE2ODkgODMuODg1N1Y3Ni4zNzdIMTAwLjc1OFY4My45MDcyQzEwMC43NTggODQuMzI5OCAxMDAuODA4IDg0LjY4NDIgMTAwLjkwOCA4NC45NzA3QzEwMS4wMDggODUuMjUgMTAxLjE0NSA4NS40NzU2IDEwMS4zMTYgODUuNjQ3NUMxMDEuNDg4IDg1LjgxOTMgMTAxLjY4OSA4NS45NDExIDEwMS45MTggODYuMDEyN0MxMDIuMTQ3IDg2LjA4NDMgMTAyLjM5MSA4Ni4xMjAxIDEwMi42NDggODYuMTIwMUMxMDMuMzg2IDg2LjEyMDEgMTAzLjk2NiA4NS45NzY5IDEwNC4zODkgODUuNjkwNEMxMDQuODE4IDg1LjM5NjggMTA1LjEyMyA4NS4wMDI5IDEwNS4zMDIgODQuNTA4OEMxMDUuNDg4IDg0LjAxNDYgMTA1LjU4MSA4My40NTk2IDEwNS41ODEgODIuODQzOFpNMTE2LjA0NyA3Ni4zNzdWNzguMjY3NkgxMDkuNDk0Vjc2LjM3N0gxMTYuMDQ3Wk0xMTEuMzg1IDczLjUzMDNIMTEzLjk3NFY4NC43ODgxQzExMy45NzQgODUuMTQ2MiAxMTQuMDI0IDg1LjQyMTkgMTE0LjEyNCA4NS42MTUyQzExNC4yMzEgODUuODAxNCAxMTQuMzc4IDg1LjkyNjggMTE0LjU2NCA4NS45OTEyQzExNC43NTEgODYuMDU1NyAxMTQuOTY5IDg2LjA4NzkgMTE1LjIyIDg2LjA4NzlDMTE1LjM5OSA4Ni4wODc5IDExNS41NzEgODYuMDc3MSAxMTUuNzM1IDg2LjA1NTdDMTE1LjkgODYuMDM0MiAxMTYuMDMzIDg2LjAxMjcgMTE2LjEzMyA4NS45OTEyTDExNi4xNDQgODcuOTY3OEMxMTUuOTI5IDg4LjAzMjIgMTE1LjY3OCA4OC4wODk1IDExNS4zOTIgODguMTM5NkMxMTUuMTEyIDg4LjE4OTggMTE0Ljc5IDg4LjIxNDggMTE0LjQyNSA4OC4yMTQ4QzExMy44MyA4OC4yMTQ4IDExMy4zMDQgODguMTExIDExMi44NDYgODcuOTAzM0MxMTIuMzg3IDg3LjY4ODUgMTEyLjAyOSA4Ny4zNDExIDExMS43NzEgODYuODYxM0MxMTEuNTE0IDg2LjM4MTUgMTExLjM4NSA4NS43NDQxIDExMS4zODUgODQuOTQ5MlY3My41MzAzWk0xMjMuNjIzIDc2LjM3N1Y3OC4yNjc2SDExNy4wN1Y3Ni4zNzdIMTIzLjYyM1pNMTE4Ljk2MSA3My41MzAzSDEyMS41NVY4NC43ODgxQzEyMS41NSA4NS4xNDYyIDEyMS42IDg1LjQyMTkgMTIxLjcgODUuNjE1MkMxMjEuODA4IDg1LjgwMTQgMTIxLjk1NCA4NS45MjY4IDEyMi4xNDEgODUuOTkxMkMxMjIuMzI3IDg2LjA1NTcgMTIyLjU0NSA4Ni4wODc5IDEyMi43OTYgODYuMDg3OUMxMjIuOTc1IDg2LjA4NzkgMTIzLjE0NyA4Ni4wNzcxIDEyMy4zMTIgODYuMDU1N0MxMjMuNDc2IDg2LjAzNDIgMTIzLjYwOSA4Ni4wMTI3IDEyMy43MDkgODUuOTkxMkwxMjMuNzIgODcuOTY3OEMxMjMuNTA1IDg4LjAzMjIgMTIzLjI1NCA4OC4wODk1IDEyMi45NjggODguMTM5NkMxMjIuNjg4IDg4LjE4OTggMTIyLjM2NiA4OC4yMTQ4IDEyMi4wMDEgODguMjE0OEMxMjEuNDA3IDg4LjIxNDggMTIwLjg4IDg4LjExMSAxMjAuNDIyIDg3LjkwMzNDMTE5Ljk2NCA4Ny42ODg1IDExOS42MDUgODcuMzQxMSAxMTkuMzQ4IDg2Ljg2MTNDMTE5LjA5IDg2LjM4MTUgMTE4Ljk2MSA4NS43NDQxIDExOC45NjEgODQuOTQ5MlY3My41MzAzWk0xMjUuMTE5IDgyLjMxNzRWODIuMDcwM0MxMjUuMTE5IDgxLjIzMjQgMTI1LjI0MSA4MC40NTU0IDEyNS40ODQgNzkuNzM5M0MxMjUuNzI4IDc5LjAxNiAxMjYuMDc5IDc4LjM4OTMgMTI2LjUzNyA3Ny44NTk0QzEyNy4wMDMgNzcuMzIyMyAxMjcuNTY4IDc2LjkwNjkgMTI4LjIzNCA3Ni42MTMzQzEyOC45MDggNzYuMzEyNSAxMjkuNjY3IDc2LjE2MjEgMTMwLjUxMiA3Ni4xNjIxQzEzMS4zNjQgNzYuMTYyMSAxMzIuMTIzIDc2LjMxMjUgMTMyLjc4OSA3Ni42MTMzQzEzMy40NjIgNzYuOTA2OSAxMzQuMDMyIDc3LjMyMjMgMTM0LjQ5NyA3Ny44NTk0QzEzNC45NjMgNzguMzg5MyAxMzUuMzE3IDc5LjAxNiAxMzUuNTYxIDc5LjczOTNDMTM1LjgwNCA4MC40NTU0IDEzNS45MjYgODEuMjMyNCAxMzUuOTI2IDgyLjA3MDNWODIuMzE3NEMxMzUuOTI2IDgzLjE1NTMgMTM1LjgwNCA4My45MzIzIDEzNS41NjEgODQuNjQ4NEMxMzUuMzE3IDg1LjM2NDYgMTM0Ljk2MyA4NS45OTEyIDEzNC40OTcgODYuNTI4M0MxMzQuMDMyIDg3LjA1ODMgMTMzLjQ2NiA4Ny40NzM2IDEzMi44IDg3Ljc3NDRDMTMyLjEzNCA4OC4wNjggMTMxLjM3OCA4OC4yMTQ4IDEzMC41MzMgODguMjE0OEMxMjkuNjgxIDg4LjIxNDggMTI4LjkxOCA4OC4wNjggMTI4LjI0NSA4Ny43NzQ0QzEyNy41NzkgODcuNDczNiAxMjcuMDEzIDg3LjA1ODMgMTI2LjU0OCA4Ni41MjgzQzEyNi4wODIgODUuOTkxMiAxMjUuNzI4IDg1LjM2NDYgMTI1LjQ4NCA4NC42NDg0QzEyNS4yNDEgODMuOTMyMyAxMjUuMTE5IDgzLjE1NTMgMTI1LjExOSA4Mi4zMTc0Wk0xMjcuNzA4IDgyLjA3MDNWODIuMzE3NEMxMjcuNzA4IDgyLjg0MDIgMTI3Ljc2MiA4My4zMzQzIDEyNy44NjkgODMuNzk5OEMxMjcuOTc3IDg0LjI2NTMgMTI4LjE0NSA4NC42NzM1IDEyOC4zNzQgODUuMDI0NEMxMjguNjAzIDg1LjM3NTMgMTI4Ljg5NyA4NS42NTEgMTI5LjI1NSA4NS44NTE2QzEyOS42MTMgODYuMDUyMSAxMzAuMDM5IDg2LjE1MjMgMTMwLjUzMyA4Ni4xNTIzQzEzMS4wMTMgODYuMTUyMyAxMzEuNDI4IDg2LjA1MjEgMTMxLjc3OSA4NS44NTE2QzEzMi4xMzcgODUuNjUxIDEzMi40MzEgODUuMzc1MyAxMzIuNjYgODUuMDI0NEMxMzIuODg5IDg0LjY3MzUgMTMzLjA1OCA4NC4yNjUzIDEzMy4xNjUgODMuNzk5OEMxMzMuMjggODMuMzM0MyAxMzMuMzM3IDgyLjg0MDIgMTMzLjMzNyA4Mi4zMTc0VjgyLjA3MDNDMTMzLjMzNyA4MS41NTQ3IDEzMy4yOCA4MS4wNjc3IDEzMy4xNjUgODAuNjA5NEMxMzMuMDU4IDgwLjE0MzkgMTMyLjg4NiA3OS43MzIxIDEzMi42NDkgNzkuMzc0QzEzMi40MiA3OS4wMTYgMTMyLjEyNyA3OC43MzY3IDEzMS43NjkgNzguNTM2MUMxMzEuNDE4IDc4LjMyODUgMTMwLjk5OSA3OC4yMjQ2IDEzMC41MTIgNzguMjI0NkMxMzAuMDI1IDc4LjIyNDYgMTI5LjYwMiA3OC4zMjg1IDEyOS4yNDQgNzguNTM2MUMxMjguODkzIDc4LjczNjcgMTI4LjYwMyA3OS4wMTYgMTI4LjM3NCA3OS4zNzRDMTI4LjE0NSA3OS43MzIxIDEyNy45NzcgODAuMTQzOSAxMjcuODY5IDgwLjYwOTRDMTI3Ljc2MiA4MS4wNjc3IDEyNy43MDggODEuNTU0NyAxMjcuNzA4IDgyLjA3MDNaTTE0MC45MTMgNzguODU4NFY4OEgxMzguMzI0Vjc2LjM3N0gxNDAuNzYzTDE0MC45MTMgNzguODU4NFpNMTQwLjQ1MSA4MS43NTg4TDEzOS42MTMgODEuNzQ4QzEzOS42MiA4MC45MjQ1IDEzOS43MzUgODAuMTY4OSAxMzkuOTU3IDc5LjQ4MTRDMTQwLjE4NiA3OC43OTM5IDE0MC41MDEgNzguMjAzMSAxNDAuOTAyIDc3LjcwOUMxNDEuMzExIDc3LjIxNDggMTQxLjc5OCA3Ni44MzUzIDE0Mi4zNjMgNzYuNTcwM0MxNDIuOTI5IDc2LjI5ODIgMTQzLjU1OSA3Ni4xNjIxIDE0NC4yNTQgNzYuMTYyMUMxNDQuODEyIDc2LjE2MjEgMTQ1LjMxNyA3Ni4yNDA5IDE0NS43NjkgNzYuMzk4NEMxNDYuMjI3IDc2LjU0ODggMTQ2LjYxNyA3Ni43OTU5IDE0Ni45MzkgNzcuMTM5NkMxNDcuMjY5IDc3LjQ4MzQgMTQ3LjUyIDc3LjkzMSAxNDcuNjkxIDc4LjQ4MjRDMTQ3Ljg2MyA3OS4wMjY3IDE0Ny45NDkgNzkuNjk2MyAxNDcuOTQ5IDgwLjQ5MTJWODhIMTQ1LjM1VjgwLjQ4MDVDMTQ1LjM1IDc5LjkyMTkgMTQ1LjI2NyA3OS40ODE0IDE0NS4xMDMgNzkuMTU5MkMxNDQuOTQ1IDc4LjgyOTggMTQ0LjcxMiA3OC41OTcgMTQ0LjQwNCA3OC40NjA5QzE0NC4xMDQgNzguMzE3NyAxNDMuNzI4IDc4LjI0NjEgMTQzLjI3NiA3OC4yNDYxQzE0Mi44MzIgNzguMjQ2MSAxNDIuNDM1IDc4LjMzOTIgMTQyLjA4NCA3OC41MjU0QzE0MS43MzMgNzguNzExNiAxNDEuNDM2IDc4Ljk2NTggMTQxLjE5MiA3OS4yODgxQzE0MC45NTYgNzkuNjEwNCAxNDAuNzczIDc5Ljk4MjcgMTQwLjY0NSA4MC40MDUzQzE0MC41MTYgODAuODI3OCAxNDAuNDUxIDgxLjI3OSAxNDAuNDUxIDgxLjc1ODhaIiBmaWxsPSIjM0Y1MkREIi8+Cjwvc3ZnPgo=", + "description": "Action button.", + "descriptor": { + "type": "latest", + "sizeX": 3, + "sizeY": 1, + "resources": [], + "templateHtml": "\n", + "templateCss": "#container tb-markdown-widget {\n height: 100%;\n display: block;\n}\n\n#container tb-markdown-widget .tb-markdown-view {\n height: 100%;\n overflow: auto;\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.actionSources = function() {\n return {\n 'click': {\n name: 'widget-action.click',\n multiple: false\n }\n };\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n datasourcesOptional: true,\n maxDatasources: 1,\n maxDataKeys: 0,\n singleEntity: true,\n previewWidth: '200px',\n previewHeight: '80px',\n embedTitlePanel: true,\n overflowVisible: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n", + "settingsSchema": "", + "dataKeySettingsSchema": "", + "settingsDirective": "", + "hasBasicMode": true, + "basicModeDirective": "tb-action-button-basic-config", + "defaultConfig": "{\"datasources\":[],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#FFFFFF01\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Action button\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":false,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false,\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"borderRadius\":\"4px\",\"configMode\":\"basic\"}" + }, + "tags": [ + "button", + "action", + "navigation" + ] +} \ No newline at end of file diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index 548cbc7041..90d74a4c16 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -91,6 +91,7 @@ export interface WidgetActionsApi { entityId?: EntityId, entityName?: string, additionalParams?: any, entityLabel?: string) => void; elementClick: ($event: Event) => void; cardClick: ($event: Event) => void; + click: ($event: Event) => void; getActiveEntityInfo: () => SubscriptionEntityInfo; openDashboardStateInSeparateDialog: (targetDashboardStateId: string, params?: StateParams, dialogTitle?: string, hideDashboardToolbar?: boolean, dialogWidth?: number, dialogHeight?: number) => void; diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 440f9c1d3d..70b04ee4eb 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -17,7 +17,7 @@ // eslint-disable-next-line @typescript-eslint/triple-slash-reference /// -import { Inject, Injectable, NgZone } from '@angular/core'; +import { Inject, Injectable, NgZone, Renderer2 } from '@angular/core'; import { WINDOW } from '@core/services/window.service'; import { ExceptionData } from '@app/shared/models/error.models'; import { @@ -55,8 +55,9 @@ import { TelemetryType } from '@shared/models/telemetry/telemetry.models'; import { EntityId } from '@shared/models/id/entity-id'; -import { DatePipe } from '@angular/common'; +import { DatePipe, DOCUMENT } from '@angular/common'; import { entityTypeTranslations } from '@shared/models/entity-type.models'; +import cssjs from '@core/css/css'; const i18nRegExp = new RegExp(`{${i18nPrefix}:[^{}]+}`, 'g'); @@ -116,6 +117,7 @@ export class UtilsService { defaultAlarmDataKeys: Array = []; constructor(@Inject(WINDOW) private window: Window, + @Inject(DOCUMENT) private document: Document, private zone: NgZone, private datePipe: DatePipe, private translate: TranslateService) { @@ -502,4 +504,24 @@ export class UtilsService { return base64toObj(b64Encoded); } + public applyCssToElement(renderer: Renderer2, element: any, cssClassPrefix: string, css: string): string { + const cssParser = new cssjs(); + cssParser.testMode = false; + const cssClass = `${cssClassPrefix}-${guid()}`; + cssParser.cssPreviewNamespace = cssClass; + cssParser.createStyleElement(cssClass, css); + renderer.addClass(element, cssClass); + return cssClass; + } + + public clearCssElement(renderer: Renderer2, cssClass: string, element?: any): void { + if (element) { + renderer.removeClass(element, cssClass); + } + const el = this.document.getElementById(cssClass); + if (el) { + el.parentNode.removeChild(el); + } + } + } diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index f8dab27af9..a12829f7c9 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -14,6 +14,8 @@ /// limitations under the License. /// +/* eslint-disable max-len */ + import * as AngularAnimations from '@angular/animations'; import * as AngularCore from '@angular/core'; import * as AngularCommon from '@angular/common'; @@ -226,9 +228,9 @@ import * as DataKeyConfigComponent from '@home/components/widget/config/data-key import * as LegendConfigComponent from '@home/components/widget/lib/settings/common/legend-config.component'; import * as ManageWidgetActionsComponent from '@home/components/widget/action/manage-widget-actions.component'; import * as WidgetActionDialogComponent from '@home/components/widget/action/widget-action-dialog.component'; -import * as CustomActionPrettyResourcesTabsComponent from '@home/components/widget/config/action/custom-action-pretty-resources-tabs.component'; -import * as CustomActionPrettyEditorComponent from '@home/components/widget/config/action/custom-action-pretty-editor.component'; -import * as MobileActionEditorComponent from '@home/components/widget/config/action/mobile-action-editor.component'; +import * as CustomActionPrettyResourcesTabsComponent from '@home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component'; +import * as CustomActionPrettyEditorComponent from '@home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component'; +import * as MobileActionEditorComponent from '@home/components/widget/lib/settings/common/action/mobile-action-editor.component'; import * as CustomDialogService from '@home/components/widget/dialog/custom-dialog.service'; import * as CustomDialogContainerComponent from '@home/components/widget/dialog/custom-dialog-container.component'; import * as ImportDialogComponent from '@shared/import-export/import-dialog.component'; @@ -261,7 +263,6 @@ import * as FilterPredicateValueComponent from '@home/components/filter/filter-p import * as TenantProfileComponent from '@home/components/profile/tenant-profile.component'; import * as TenantProfileDialogComponent from '@home/components/profile/tenant-profile-dialog.component'; import * as TenantProfileDataComponent from '@home/components/profile/tenant-profile-data.component'; -// eslint-disable-next-line max-len import * as DefaultDeviceProfileConfigurationComponent from '@home/components/profile/device/default-device-profile-configuration.component'; import * as DeviceProfileConfigurationComponent from '@home/components/profile/device/device-profile-configuration.component'; import * as DeviceProfileComponent from '@home/components/profile/device-profile.component'; @@ -286,7 +287,6 @@ import * as AlarmScheduleInfoComponent from '@home/components/profile/alarm/alar import * as AlarmScheduleDialogComponent from '@home/components/profile/alarm/alarm-schedule-dialog.component'; import * as EditAlarmDetailsDialogComponent from '@home/components/profile/alarm/edit-alarm-details-dialog.component'; import * as AlarmRuleConditionDialogComponent from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; -// eslint-disable-next-line max-len import * as DefaultTenantProfileConfigurationComponent from '@home/components/profile/tenant/default-tenant-profile-configuration.component'; import * as TenantProfileConfigurationComponent from '@home/components/profile/tenant/tenant-profile-configuration.component'; import * as SmsProviderConfigurationComponent from '@home/components/sms/sms-provider-configuration.component'; @@ -541,9 +541,9 @@ class ModulesMap implements IModulesMap { '@home/components/widget/lib/settings/common/legend-config.component': LegendConfigComponent, '@home/components/widget/action/manage-widget-actions.component': ManageWidgetActionsComponent, '@home/components/widget/action/widget-action-dialog.component': WidgetActionDialogComponent, - '@home/components/widget/config/action/custom-action-pretty-resources-tabs.component': CustomActionPrettyResourcesTabsComponent, - '@home/components/widget/config/action/custom-action-pretty-editor.component': CustomActionPrettyEditorComponent, - '@home/components/widget/config/action/mobile-action-editor.component': MobileActionEditorComponent, + '@home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component': CustomActionPrettyResourcesTabsComponent, + '@home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component': CustomActionPrettyEditorComponent, + '@home/components/widget/lib/settings/common/action/mobile-action-editor.component': MobileActionEditorComponent, '@home/components/widget/dialog/custom-dialog.service': CustomDialogService, '@home/components/widget/dialog/custom-dialog-container.component': CustomDialogContainerComponent, '@home/components/attribute/add-widget-to-dashboard-dialog.component': AddWidgetToDashboardDialogComponent, diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index 5ca942f33c..c69a2a4de9 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -71,6 +71,7 @@ [dashboardStyle]="dashboardStyle" [backgroundImage]="backgroundImage" [isEdit]="isEdit" + [isPreview]="isPreview" [isMobile]="isMobileSize" [isEditActionEnabled]="isEditActionEnabled" [isExportActionEnabled]="isExportActionEnabled" diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index 469630c212..2d143f4e16 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -97,6 +97,9 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo @Input() isEdit: boolean; + @Input() + isPreview: boolean; + @Input() autofillHeight: boolean; diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts index 1eec44b714..b10085c294 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts @@ -38,13 +38,12 @@ import { } from '@home/components/widget/action/manage-widget-actions.component.models'; import { UtilsService } from '@core/services/utils.service'; import { - actionDescriptorToAction, + actionDescriptorToAction, defaultWidgetAction, WidgetActionSource, - WidgetActionType, widgetType } from '@shared/models/widget.models'; import { takeUntil } from 'rxjs/operators'; -import { CustomActionEditorCompleter } from '@home/components/widget/config/action/custom-action.models'; +import { CustomActionEditorCompleter } from '@home/components/widget/lib/settings/common/action/custom-action.models'; import { WidgetService } from '@core/http/widget.service'; export interface WidgetActionDialogData { @@ -92,11 +91,7 @@ export class WidgetActionDialogComponent extends DialogComponent + + + +
+
widgets.action-button.behavior
+
+
widgets.action-button.on-click
+ + +
+
+
widgets.button-state.activated-state
+ +
+
+
widgets.button-state.disabled-state
+ +
+
+
+
widget-config.appearance
+ + +
+
+
widget-config.card-appearance
+
+
{{ 'widget-config.card-border-radius' | translate }}
+ + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/button/action-button-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/action-button-basic-config.component.ts new file mode 100644 index 0000000000..4485966ac7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/action-button-basic-config.component.ts @@ -0,0 +1,139 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { + actionDescriptorToAction, + Datasource, + defaultWidgetAction, + TargetDevice, + WidgetAction, + WidgetConfig, +} from '@shared/models/widget.models'; +import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; +import { guid } from '@core/utils'; +import { ValueType } from '@shared/models/constants'; +import { getTargetDeviceFromDatasources } from '@shared/models/widget-settings.models'; +import { + actionButtonDefaultSettings, + ActionButtonWidgetSettings +} from '@home/components/widget/lib/button/action-button-widget.models'; + +@Component({ + selector: 'tb-action-button-basic-config', + templateUrl: './action-button-basic-config.component.html', + styleUrls: ['../basic-config.scss'] +}) +export class ActionButtonBasicConfigComponent extends BasicWidgetConfigComponent { + + get targetDevice(): TargetDevice { + const datasources: Datasource[] = this.actionButtonWidgetConfigForm.get('datasources').value; + return getTargetDeviceFromDatasources(datasources); + } + + valueType = ValueType; + + actionButtonWidgetConfigForm: UntypedFormGroup; + + constructor(protected store: Store, + protected widgetConfigComponent: WidgetConfigComponent, + private fb: UntypedFormBuilder) { + super(store, widgetConfigComponent); + } + + protected configForm(): UntypedFormGroup { + return this.actionButtonWidgetConfigForm; + } + + protected onConfigSet(configData: WidgetConfigComponentData) { + const settings: ActionButtonWidgetSettings = {...actionButtonDefaultSettings, ...(configData.config.settings || {})}; + const onClickAction = this.getOnClickAction(configData.config); + this.actionButtonWidgetConfigForm = this.fb.group({ + datasources: [configData.config.datasources, []], + + onClickAction: [onClickAction, []], + activatedState: [settings.activatedState, []], + disabledState: [settings.disabledState, []], + + appearance: [settings.appearance, []], + + borderRadius: [configData.config.borderRadius, []] + }); + } + + protected prepareOutputConfig(config: any): WidgetConfigComponentData { + + this.widgetConfig.config.datasources = config.datasources; + this.setOnClickAction(this.widgetConfig.config, config.onClickAction); + + this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; + + this.widgetConfig.config.settings.activatedState = config.activatedState; + this.widgetConfig.config.settings.disabledState = config.disabledState; + + this.widgetConfig.config.settings.appearance = config.appearance; + + this.widgetConfig.config.borderRadius = config.borderRadius; + + return this.widgetConfig; + } + + private getOnClickAction(config: WidgetConfig): WidgetAction { + let clickAction: WidgetAction; + const actions = config.actions; + if (actions && actions.click) { + const descriptors = actions.click; + if (descriptors?.length) { + const descriptor = descriptors[0]; + clickAction = actionDescriptorToAction(descriptor); + } + } + if (!clickAction) { + clickAction = defaultWidgetAction(); + } + return clickAction; + } + + private setOnClickAction(config: WidgetConfig, clickAction: WidgetAction): void { + let actions = config.actions; + if (!actions) { + actions = {}; + config.actions = actions; + } + let descriptors = actions.click; + if (!descriptors) { + descriptors = []; + actions.click = descriptors; + } + let descriptor = descriptors[0]; + if (!descriptor) { + descriptor = { + id: guid(), + name: 'onClick', + icon: 'more_horiz', + ...clickAction + }; + descriptors[0] = descriptor; + } else { + descriptors[0] = {...descriptor, ...clickAction}; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html index 4f030986f1..4ac5934e93 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html @@ -20,30 +20,36 @@
widgets.single-switch.behavior
-
widgets.value-action.initial-state
+
widgets.rpc-state.initial-state
-
widgets.value-action.turn-on
+
widgets.rpc-state.turn-on
-
widgets.value-action.turn-off
+
widgets.rpc-state.turn-off
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html b/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html index 7777c6fdf9..b72f9f0e5d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html @@ -42,14 +42,14 @@ style="height: 56px; margin-bottom: 22px;" formControlName="alarmFilterConfig"> diff --git a/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.ts index 11a871de08..1dd3aa3507 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.ts @@ -95,6 +95,10 @@ export class DatasourceComponent implements ControlValueAccessor, OnInit, Valida return this.widgetConfigComponent.modelValue?.typeParameters?.dataKeysOptional; } + public get datasourcesOptional(): boolean { + return this.widgetConfigComponent.modelValue?.typeParameters?.datasourcesOptional; + } + public get maxDataKeys(): number { return this.widgetConfigComponent.modelValue?.typeParameters?.maxDataKeys; } @@ -276,18 +280,20 @@ export class DatasourceComponent implements ControlValueAccessor, OnInit, Valida } private updateValidators() { - const type: DatasourceType = this.datasourceFormGroup.get('type').value; - this.datasourceFormGroup.get('deviceId').setValidators( - type === DatasourceType.device ? [Validators.required] : [] - ); - this.datasourceFormGroup.get('entityAliasId').setValidators( - (type === DatasourceType.entity || type === DatasourceType.entityCount) ? [Validators.required] : [] - ); - const newDataKeysRequired = !this.isDataKeysOptional(type); - this.datasourceFormGroup.get('dataKeys').setValidators(newDataKeysRequired ? [Validators.required] : []); - this.datasourceFormGroup.get('deviceId').updateValueAndValidity({emitEvent: false}); - this.datasourceFormGroup.get('entityAliasId').updateValueAndValidity({emitEvent: false}); - this.datasourceFormGroup.get('dataKeys').updateValueAndValidity({emitEvent: false}); + if (!this.datasourcesOptional) { + const type: DatasourceType = this.datasourceFormGroup.get('type').value; + this.datasourceFormGroup.get('deviceId').setValidators( + type === DatasourceType.device ? [Validators.required] : [] + ); + this.datasourceFormGroup.get('entityAliasId').setValidators( + (type === DatasourceType.entity || type === DatasourceType.entityCount) ? [Validators.required] : [] + ); + const newDataKeysRequired = !this.isDataKeysOptional(type); + this.datasourceFormGroup.get('dataKeys').setValidators(newDataKeysRequired ? [Validators.required] : []); + this.datasourceFormGroup.get('deviceId').updateValueAndValidity({emitEvent: false}); + this.datasourceFormGroup.get('entityAliasId').updateValueAndValidity({emitEvent: false}); + this.datasourceFormGroup.get('dataKeys').updateValueAndValidity({emitEvent: false}); + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/datasources.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/datasources.component.ts index 4cb274c3ef..a83d58a25e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/datasources.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/datasources.component.ts @@ -30,7 +30,7 @@ import { import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { Datasource, - DatasourceType, + DatasourceType, datasourceValid, JsonSettingsSchema, WidgetConfigMode, widgetType @@ -317,6 +317,9 @@ export class DatasourcesComponent implements ControlValueAccessor, OnInit, Valid } private datasourcesUpdated(datasources: Datasource[]) { + if (this.datasourcesOptional) { + datasources = datasources ? datasources.filter(d => datasourceValid(d)) : []; + } this.propagateChange(datasources); } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts index 7b80b4b088..4470fc1327 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts @@ -33,11 +33,6 @@ import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings import { TimewindowStyleComponent } from '@home/components/widget/config/timewindow-style.component'; import { TimewindowStylePanelComponent } from '@home/components/widget/config/timewindow-style-panel.component'; import { TargetDeviceComponent } from '@home/components/widget/config/target-device.component'; -import { WidgetActionComponent } from '@home/components/widget/config/action/widget-action.component'; -import { CustomActionPrettyResourcesTabsComponent } - from '@home/components/widget/config/action/custom-action-pretty-resources-tabs.component'; -import { CustomActionPrettyEditorComponent } from '@home/components/widget/config/action/custom-action-pretty-editor.component'; -import { MobileActionEditorComponent } from '@home/components/widget/config/action/mobile-action-editor.component'; @NgModule({ declarations: @@ -55,11 +50,7 @@ import { MobileActionEditorComponent } from '@home/components/widget/config/acti TimewindowStyleComponent, TimewindowStylePanelComponent, TimewindowConfigPanelComponent, - WidgetSettingsComponent, - WidgetActionComponent, - CustomActionPrettyResourcesTabsComponent, - CustomActionPrettyEditorComponent, - MobileActionEditorComponent + WidgetSettingsComponent ], imports: [ CommonModule, @@ -82,11 +73,7 @@ import { MobileActionEditorComponent } from '@home/components/widget/config/acti TimewindowStylePanelComponent, TimewindowConfigPanelComponent, WidgetSettingsComponent, - WidgetSettingsCommonModule, - WidgetActionComponent, - CustomActionPrettyResourcesTabsComponent, - CustomActionPrettyEditorComponent, - MobileActionEditorComponent + WidgetSettingsCommonModule ] }) export class WidgetConfigComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-config.component.models.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-config.component.models.ts index 1132e8bf91..441983a2e3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-config.component.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-config.component.models.ts @@ -23,7 +23,7 @@ import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { AbstractControl, UntypedFormGroup } from '@angular/forms'; -import { DataKey, DatasourceType, KeyInfo, WidgetConfigMode } from '@shared/models/widget.models'; +import { DataKey, DatasourceType, KeyInfo, WidgetConfigMode, widgetType } from '@shared/models/widget.models'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { isDefinedAndNotNull } from '@core/utils'; @@ -63,6 +63,18 @@ export abstract class BasicWidgetConfigComponent extends PageComponent implement return this.widgetConfigComponent.aliasController; } + get callbacks(): WidgetConfigCallbacks { + return this.widgetConfigComponent.widgetConfigCallbacks; + } + + get widgetType(): widgetType { + return this.widgetConfigComponent.widgetType; + } + + get widgetEditMode(): boolean { + return this.widgetConfigComponent.widgetEditMode; + } + widgetConfigChangedEmitter = new EventEmitter(); widgetConfigChanged = this.widgetConfigChangedEmitter.asObservable(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.scss b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.scss new file mode 100644 index 0000000000..276fa725b8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.scss @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2024 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. + */ +.tb-action-widget-error-container { + position: absolute; + bottom: 0; + left: 0; + right: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 1; + .tb-action-widget-error-panel { + display: flex; + padding: 4px 4px 4px 12px; + justify-content: center; + align-items: center; + gap: 4px; + border-radius: 4px; + background-color: #fff2f3; + box-shadow: -2px 2px 4px 0px rgba(0,0,0,0.2); + .tb-action-widget-error-text { + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; + color: rgba(209, 39, 48, 1); + } + .tb-action-widget-error-clear { + color: rgba(209, 39, 48, 1); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.html new file mode 100644 index 0000000000..f667e63356 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.html @@ -0,0 +1,35 @@ + +
+
+ +
+ + +
+
+
+ +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.scss new file mode 100644 index 0000000000..fcdc355ef9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.scss @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2024 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. + */ +.tb-action-button-widget { + width: 100%; + height: 100%; + position: relative; + + > div.tb-action-button-widget-title-panel { + position: absolute; + top: 12px; + left: 12px; + right: 12px; + z-index: 2; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.ts new file mode 100644 index 0000000000..21f50d0e9c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.ts @@ -0,0 +1,100 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { AfterViewInit, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; +import { BasicActionWidgetComponent } from '@home/components/widget/lib/action/action-widget.models'; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; +import { ValueType } from '@shared/models/constants'; +import { + actionButtonDefaultSettings, + ActionButtonWidgetSettings +} from '@home/components/widget/lib/button/action-button-widget.models'; +import { WidgetButtonAppearance } from '@shared/components/button/widget-button.models'; + +@Component({ + selector: 'tb-action-button-widget', + templateUrl: './action-button-widget.component.html', + styleUrls: ['../action/action-widget.scss', './action-button-widget.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class ActionButtonWidgetComponent extends + BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy { + + settings: ActionButtonWidgetSettings; + + disabled = false; + activated = false; + + appearance: WidgetButtonAppearance; + borderRadius = '4px'; + + constructor(protected imagePipe: ImagePipe, + protected sanitizer: DomSanitizer, + protected cd: ChangeDetectorRef) { + super(cd); + } + + ngOnInit(): void { + super.ngOnInit(); + this.settings = {...actionButtonDefaultSettings, ...this.ctx.settings}; + + this.appearance = this.settings.appearance; + + const activatedStateSettings = + {...this.settings.activatedState, actionLabel: this.ctx.translate.instant('widgets.button-state.activated-state')}; + this.createValueGetter(activatedStateSettings, ValueType.BOOLEAN, { + next: (value) => this.onActivated(value) + }); + + const disabledStateSettings = + {...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.button-state.disabled-state')}; + this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, { + next: (value) => this.onDisabled(value) + }); + } + + ngAfterViewInit(): void { + super.ngAfterViewInit(); + } + + ngOnDestroy() { + super.ngOnDestroy(); + } + + public onInit() { + super.onInit(); + this.borderRadius = this.ctx.$widgetElement.css('borderRadius'); + this.cd.detectChanges(); + } + + public onClick($event: MouseEvent) { + if (!this.ctx.isEdit && !this.ctx.isPreview) { + this.ctx.actionsApi.click($event); + } + } + + private onActivated(value: boolean): void { + this.activated = !!value; + this.cd.markForCheck(); + } + + private onDisabled(value: boolean): void { + this.disabled = !!value; + this.cd.markForCheck(); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.models.ts new file mode 100644 index 0000000000..f86ef73439 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.models.ts @@ -0,0 +1,67 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + WidgetButtonAppearance, + widgetButtonDefaultAppearance +} from '@shared/components/button/widget-button.models'; +import { DataToValueType, GetValueAction, GetValueSettings } from '@shared/models/action-widget-settings.models'; + +export interface ActionButtonWidgetSettings { + appearance: WidgetButtonAppearance; + activatedState: GetValueSettings; + disabledState: GetValueSettings; +} + +export const actionButtonDefaultSettings: ActionButtonWidgetSettings = { + appearance: widgetButtonDefaultAppearance, + activatedState: { + action: GetValueAction.DO_NOTHING, + defaultValue: false, + getAttribute: { + key: 'state', + scope: null, + subscribeForUpdates: false + }, + getTimeSeries: { + key: 'state', + subscribeForUpdates: false + }, + dataToValue: { + type: DataToValueType.NONE, + compareToValue: true, + dataToValueFunction: '/* Should return boolean value */\nreturn data;' + } + }, + disabledState: { + action: GetValueAction.DO_NOTHING, + defaultValue: false, + getAttribute: { + key: 'state', + scope: null, + subscribeForUpdates: false + }, + getTimeSeries: { + key: 'state', + subscribeForUpdates: false + }, + dataToValue: { + type: DataToValueType.NONE, + compareToValue: true, + dataToValueFunction: '/* Should return boolean value */\nreturn data;' + } + } +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html index 04ec9f22ab..5f085ff7b5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html @@ -33,10 +33,10 @@ -
-
-
- +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.scss index 884bea0e43..92c8f87f09 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.scss @@ -45,36 +45,6 @@ $switchColorDisabled: var(--tb-single-switch-color-disabled, #D5D7E5); left: 0; right: 0; } - .tb-single-switch-error-container { - position: absolute; - bottom: 0; - left: 0; - right: 0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - .tb-single-switch-error-panel { - display: flex; - padding: 4px 4px 4px 12px; - justify-content: center; - align-items: center; - gap: 4px; - border-radius: 4px; - background-color: #fff2f3; - box-shadow: -2px 2px 4px 0px rgba(0,0,0,0.2); - .tb-single-switch-error-text { - font-size: 12px; - font-style: normal; - font-weight: 400; - line-height: 16px; - color: rgba(209, 39, 48, 1); - } - .tb-single-switch-error-clear { - color: rgba(209, 39, 48, 1); - } - } - } > div.tb-single-switch-title-panel { position: absolute; top: 12px; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.ts index 61d4fb9322..1a7274c5b6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.ts @@ -42,9 +42,8 @@ import { Observable } from 'rxjs'; import { ResizeObserver } from '@juggle/resize-observer'; import { ImagePipe } from '@shared/pipe/image.pipe'; import { DomSanitizer } from '@angular/platform-browser'; -import cssjs from '@core/css/css'; -import { hashCode } from '@core/utils'; import { ValueType } from '@shared/models/constants'; +import { UtilsService } from '@core/services/utils.service'; const horizontalLayoutPadding = 48; const verticalLayoutPadding = 36; @@ -52,7 +51,7 @@ const verticalLayoutPadding = 36; @Component({ selector: 'tb-single-switch-widget', templateUrl: './single-switch-widget.component.html', - styleUrls: ['./single-switch-widget.component.scss'], + styleUrls: ['../action/action-widget.scss', './single-switch-widget.component.scss'], encapsulation: ViewEncapsulation.None }) export class SingleSwitchWidgetComponent extends @@ -102,9 +101,12 @@ export class SingleSwitchWidgetComponent extends private onValueSetter: ValueSetter; private offValueSetter: ValueSetter; + private singleSwitchCssClass: string; + constructor(protected imagePipe: ImagePipe, protected sanitizer: DomSanitizer, private renderer: Renderer2, + private utils: UtilsService, protected cd: ChangeDetectorRef, private elementRef: ElementRef) { super(cd); @@ -148,25 +150,21 @@ export class SingleSwitchWidgetComponent extends `--tb-single-switch-color-off: ${this.settings.switchColorOff};\n`+ `--tb-single-switch-color-disabled: ${this.settings.switchColorDisabled};\n`+ `}`; - const cssParser = new cssjs(); - cssParser.testMode = false; - const namespace = 'single-switch-' + hashCode(switchVariablesCss); - cssParser.cssPreviewNamespace = namespace; - cssParser.createStyleElement(namespace, switchVariablesCss); - this.renderer.addClass(this.elementRef.nativeElement, namespace); + this.singleSwitchCssClass = + this.utils.applyCssToElement(this.renderer, this.elementRef.nativeElement, 'tb-single-switch', switchVariablesCss); const getInitialStateSettings = - {...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.value-action.initial-state')}; + {...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.initial-state')}; this.createValueGetter(getInitialStateSettings, ValueType.BOOLEAN, { next: (value) => this.onValue(value) }); const onUpdateStateSettings = {...this.settings.onUpdateState, - actionLabel: this.ctx.translate.instant('widgets.value-action.turn-on')}; + actionLabel: this.ctx.translate.instant('widgets.rpc-state.turn-on')}; this.onValueSetter = this.createValueSetter(onUpdateStateSettings); const offUpdateStateSettings = {...this.settings.offUpdateState, - actionLabel: this.ctx.translate.instant('widgets.value-action.turn-off')}; + actionLabel: this.ctx.translate.instant('widgets.rpc-state.turn-off')}; this.offValueSetter = this.createValueSetter(offUpdateStateSettings); } @@ -190,6 +188,9 @@ export class SingleSwitchWidgetComponent extends if (this.panelResize$) { this.panelResize$.disconnect(); } + if (this.singleSwitchCssClass) { + this.utils.clearCssElement(this.renderer, this.singleSwitchCssClass); + } super.ngOnDestroy(); } @@ -211,7 +212,6 @@ export class SingleSwitchWidgetComponent extends } private onValue(value: boolean): void { - console.log(`onValue: ${value}`); this.value = !!value; this.cd.markForCheck(); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/value-action-settings-button.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/action-settings-button.component.html similarity index 88% rename from ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/value-action-settings-button.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/action-settings-button.component.html index 1e54dc3270..3290099ff9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/value-action-settings-button.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/action-settings-button.component.html @@ -16,9 +16,9 @@ --> + +
+ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.ts new file mode 100644 index 0000000000..8ed53fd95e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.ts @@ -0,0 +1,88 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { merge } from 'rxjs'; +import { + DataToValueType, + GetValueAction, + getValueActions, + getValueActionTranslations, + GetValueSettings +} from '@shared/models/action-widget-settings.models'; +import { ValueType } from '@shared/models/constants'; +import { TargetDevice, WidgetAction, widgetType } from '@shared/models/widget.models'; +import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models'; +import { IAliasController } from '@core/api/widget-api.models'; +import { WidgetService } from '@core/http/widget.service'; +import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models'; + +@Component({ + selector: 'tb-widget-action-settings-panel', + templateUrl: './widget-action-settings-panel.component.html', + providers: [], + styleUrls: ['./action-settings-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class WidgetActionSettingsPanelComponent extends PageComponent implements OnInit { + + @Input() + widgetAction: WidgetAction; + + @Input() + panelTitle: string; + + @Input() + widgetType: widgetType; + + @Input() + callbacks: WidgetActionCallbacks; + + @Input() + popover: TbPopoverComponent; + + @Output() + widgetActionApplied = new EventEmitter(); + + widgetActionFormGroup: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder, + protected store: Store) { + super(store); + } + + ngOnInit(): void { + this.widgetActionFormGroup = this.fb.group( + { + widgetAction: [this.widgetAction, []] + } + ); + } + + cancel() { + this.popover?.hide(); + } + + applyWidgetAction() { + const widgetAction: WidgetAction = this.widgetActionFormGroup.get('widgetAction').getRawValue(); + this.widgetActionApplied.emit(widgetAction); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings.component.ts new file mode 100644 index 0000000000..95ec0b1262 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings.component.ts @@ -0,0 +1,136 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectorRef, + Component, + forwardRef, + HostBinding, + Input, + OnInit, + Renderer2, + ViewContainerRef, + ViewEncapsulation +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { TranslateService } from '@ngx-translate/core'; +import { WidgetAction, widgetActionTypeTranslationMap, widgetType } from '@shared/models/widget.models'; +import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models'; +import { + WidgetActionSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/action/widget-action-settings-panel.component'; + +@Component({ + selector: 'tb-widget-action-settings', + templateUrl: './action-settings-button.component.html', + styleUrls: ['./action-settings-button.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => WidgetActionSettingsComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class WidgetActionSettingsComponent implements OnInit, ControlValueAccessor { + + @HostBinding('style.overflow') + overflow = 'hidden'; + + @Input() + panelTitle: string; + + @Input() + widgetType: widgetType; + + @Input() + callbacks: WidgetActionCallbacks; + + @Input() + disabled = false; + + modelValue: WidgetAction; + + displayValue: string; + + private propagateChange = null; + + constructor(private translate: TranslateService, + private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef, + private cd: ChangeDetectorRef) {} + + ngOnInit(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + if (this.disabled !== isDisabled) { + this.disabled = isDisabled; + } + } + + writeValue(value: WidgetAction): void { + this.modelValue = value; + this.updateDisplayValue(); + } + + openActionSettingsPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + widgetAction: this.modelValue, + panelTitle: this.panelTitle, + widgetType: this.widgetType, + callbacks: this.callbacks + }; + const widgetActionSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, WidgetActionSettingsPanelComponent, + ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], true, null, + ctx, + {}, + {}, {}, true); + widgetActionSettingsPanelPopover.tbComponentRef.instance.popover = widgetActionSettingsPanelPopover; + widgetActionSettingsPanelPopover.tbComponentRef.instance.widgetActionApplied.subscribe((widgetAction) => { + widgetActionSettingsPanelPopover.hide(); + this.modelValue = widgetAction; + this.updateDisplayValue(); + this.propagateChange(this.modelValue); + }); + } + } + + private updateDisplayValue() { + this.displayValue = this.translate.instant(widgetActionTypeTranslationMap.get(this.modelValue.type)); + this.cd.markForCheck(); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/action/widget-action.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/config/action/widget-action.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html diff --git a/ui-ngx/src/app/modules/home/components/widget/config/action/widget-action.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts similarity index 99% rename from ui-ngx/src/app/modules/home/components/widget/config/action/widget-action.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts index ed7690fe28..c93a05fafd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/action/widget-action.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts @@ -44,7 +44,7 @@ import { PopoverPlacement, PopoverPlacements } from '@shared/components/popover. import { CustomActionEditorCompleter, toCustomAction -} from '@home/components/widget/config/action/custom-action.models'; +} from '@home/components/widget/lib/settings/common/action/custom-action.models'; const stateDisplayTypes = ['normal', 'separateDialog', 'popover'] as const; type stateDisplayTypeTuple = typeof stateDisplayTypes; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html new file mode 100644 index 0000000000..b4ce9c8ce2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html @@ -0,0 +1,97 @@ + +
+ + + {{ widgetButtonTypeTranslationMap.get(type) | translate }} + + +
+ + {{ 'widgets.button.auto-scale' | translate }} + +
+
+ + {{ 'widgets.button.label' | translate }} + + + + +
+
+ + {{ 'widgets.button.icon' | translate }} + +
+ + + + + + +
+
+
+
{{ 'widgets.button.color-palette' | translate }}
+
+
+
widgets.button.main
+ + +
+ +
+
widgets.button.background
+ + +
+
+
+
+ + + +
widgets.button.custom-styles
+
+
+ +
+
{{ widgetButtonStateTranslationMap.get(state) | translate }}
+ + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.ts new file mode 100644 index 0000000000..d8756e3e31 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.ts @@ -0,0 +1,141 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { + WidgetButtonAppearance, + widgetButtonStates, widgetButtonStatesTranslations, + widgetButtonTypeImages, + widgetButtonTypes, + widgetButtonTypeTranslations +} from '@shared/components/button/widget-button.models'; +import { merge } from 'rxjs'; + +@Component({ + selector: 'tb-widget-button-appearance', + templateUrl: './widget-button-appearance.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => WidgetButtonAppearanceComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class WidgetButtonAppearanceComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled = false; + + @Input() + borderRadius: string; + + widgetButtonTypes = widgetButtonTypes; + + widgetButtonTypeTranslationMap = widgetButtonTypeTranslations; + widgetButtonTypeImageMap = widgetButtonTypeImages; + + widgetButtonStates = widgetButtonStates; + widgetButtonStateTranslationMap = widgetButtonStatesTranslations; + + modelValue: WidgetButtonAppearance; + + appearanceFormGroup: UntypedFormGroup; + + private propagateChange = (_val: any) => {}; + + constructor(private fb: UntypedFormBuilder) {} + + ngOnInit(): void { + this.appearanceFormGroup = this.fb.group({ + type: [null, []], + autoScale: [null, []], + showLabel: [null, []], + label: [null, []], + showIcon: [null, []], + icon: [null, []], + iconSize: [null, []], + iconSizeUnit: [null, []], + mainColor: [null, []], + backgroundColor: [null, []] + }); + const customStyle = this.fb.group({}); + for (const state of widgetButtonStates) { + customStyle.addControl(state, this.fb.control(null, [])); + } + this.appearanceFormGroup.addControl('customStyle', customStyle); + this.appearanceFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + merge(this.appearanceFormGroup.get('showLabel').valueChanges, + this.appearanceFormGroup.get('showIcon').valueChanges) + .subscribe(() => { + this.updateValidators(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.appearanceFormGroup.disable({emitEvent: false}); + } else { + this.appearanceFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + writeValue(value: WidgetButtonAppearance): void { + this.modelValue = value; + this.appearanceFormGroup.patchValue( + value, {emitEvent: false} + ); + this.updateValidators(); + } + + private updateModel() { + this.modelValue = this.appearanceFormGroup.getRawValue(); + this.propagateChange(this.modelValue); + } + + private updateValidators(): void { + const showLabel: boolean = this.appearanceFormGroup.get('showLabel').value; + const showIcon: boolean = this.appearanceFormGroup.get('showIcon').value; + if (showLabel) { + this.appearanceFormGroup.get('label').enable(); + } else { + this.appearanceFormGroup.get('label').disable(); + } + if (showIcon) { + this.appearanceFormGroup.get('icon').enable(); + this.appearanceFormGroup.get('iconSize').enable(); + this.appearanceFormGroup.get('iconSizeUnit').enable(); + } else { + this.appearanceFormGroup.get('icon').disable(); + this.appearanceFormGroup.get('iconSize').disable(); + this.appearanceFormGroup.get('iconSizeUnit').disable(); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.html new file mode 100644 index 0000000000..7e4a316eef --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.html @@ -0,0 +1,93 @@ + +
+
{{ widgetButtonStateTranslationMap.get(state) | translate }}
+
+
+ + {{ 'widgets.button.main' | translate }} + + + +
+
+ + {{ 'widgets.button.background' | translate }} + + + +
+
+ + {{ 'widgets.button.shadow' | translate }} + + + {{ 'widgets.button.enabled' | translate }} + {{ 'widgets.button.disabled' | translate }} + +
+
+
+ widgets.button.preview +
+ + +
+
+
+ + + +
+ +
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.scss new file mode 100644 index 0000000000..dfbbb91620 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.scss @@ -0,0 +1,68 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../../../../../../scss/constants'; + +.tb-widget-button-custom-style-panel { + width: 530px; + display: flex; + flex-direction: column; + gap: 16px; + @media #{$mat-lt-md} { + width: 90vw; + } + .tb-widget-button-custom-style-panel-content { + display: flex; + flex-direction: column; + gap: 16px; + overflow: auto; + } + .tb-widget-button-custom-style-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-widget-button-custom-style-preview { + flex: 1; + background: rgba(0, 0, 0, 0.04); + display: flex; + flex-direction: column; + padding: 12px 16px 24px 16px; + align-items: center; + gap: 12px; + .tb-widget-button-custom-style-preview-title { + align-self: stretch; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 24px; + color: rgba(0, 0, 0, 0.38); + } + tb-widget-button { + width: 200px; + height: 60px; + } + } + .tb-widget-button-custom-style-panel-buttons { + height: 40px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.ts new file mode 100644 index 0000000000..419ce7e4d4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.ts @@ -0,0 +1,171 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { + defaultBackgroundColorDisabled, + defaultMainColorDisabled, + WidgetButtonAppearance, + WidgetButtonCustomStyle, + WidgetButtonState, + widgetButtonStates, + widgetButtonStatesTranslations, + WidgetButtonType +} from '@shared/components/button/widget-button.models'; +import { merge } from 'rxjs'; +import { deepClone } from '@core/utils'; + +@Component({ + selector: 'tb-widget-button-custom-style-panel', + templateUrl: './widget-button-custom-style-panel.component.html', + providers: [], + styleUrls: ['./widget-button-custom-style-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class WidgetButtonCustomStylePanelComponent extends PageComponent implements OnInit { + + @Input() + appearance: WidgetButtonAppearance; + + @Input() + borderRadius: string; + + @Input() + state: WidgetButtonState; + + @Input() + customStyle: WidgetButtonCustomStyle; + + @Input() + popover: TbPopoverComponent; + + @Output() + customStyleApplied = new EventEmitter(); + + widgetButtonStateTranslationMap = widgetButtonStatesTranslations; + + widgetButtonState = WidgetButtonState; + + previewAppearance: WidgetButtonAppearance; + + copyFromStates: WidgetButtonState[]; + + customStyleFormGroup: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder, + protected store: Store, + private cd: ChangeDetectorRef) { + super(store); + } + + ngOnInit(): void { + this.copyFromStates = widgetButtonStates.filter(state => + state !== this.state && !!this.appearance.customStyle[state]); + this.customStyleFormGroup = this.fb.group( + { + overrideMainColor: [false, []], + mainColor: [null, []], + overrideBackgroundColor: [false, []], + backgroundColor: [null, []], + overrideDropShadow: [false, []], + dropShadow: [false, []] + } + ); + merge(this.customStyleFormGroup.get('overrideMainColor').valueChanges, + this.customStyleFormGroup.get('overrideBackgroundColor').valueChanges, + this.customStyleFormGroup.get('overrideDropShadow').valueChanges) + .subscribe(() => { + this.updateValidators(); + }); + this.customStyleFormGroup.valueChanges.subscribe(() => { + this.updatePreviewAppearance(); + }); + this.setStyle(this.customStyle); + } + + copyStyle(state: WidgetButtonState) { + this.customStyle = deepClone(this.appearance.customStyle[state]); + this.setStyle(this.customStyle); + this.customStyleFormGroup.markAsDirty(); + } + + cancel() { + this.popover?.hide(); + } + + applyCustomStyle() { + const customStyle: WidgetButtonCustomStyle = this.customStyleFormGroup.value; + this.customStyleApplied.emit(customStyle); + } + + private setStyle(customStyle?: WidgetButtonCustomStyle): void { + let mainColor = this.state === WidgetButtonState.disabled ? defaultMainColorDisabled : this.appearance.mainColor; + if (customStyle?.overrideMainColor) { + mainColor = customStyle?.mainColor; + } + let backgroundColor = this.state === WidgetButtonState.disabled ? defaultBackgroundColorDisabled : this.appearance.backgroundColor; + if (customStyle?.overrideBackgroundColor) { + backgroundColor = customStyle?.backgroundColor; + } + let dropShadow = this.appearance.type === WidgetButtonType.basic ? false : true; + if (customStyle?.overrideDropShadow) { + dropShadow = customStyle?.dropShadow; + } + this.customStyleFormGroup.patchValue({ + overrideMainColor: customStyle?.overrideMainColor, + mainColor, + overrideBackgroundColor: customStyle?.overrideBackgroundColor, + backgroundColor, + overrideDropShadow: customStyle?.overrideDropShadow, + dropShadow + }, {emitEvent: false}); + this.updateValidators(); + this.updatePreviewAppearance(); + } + + private updateValidators() { + const overrideMainColor: boolean = this.customStyleFormGroup.get('overrideMainColor').value; + const overrideBackgroundColor: boolean = this.customStyleFormGroup.get('overrideBackgroundColor').value; + const overrideDropShadow: boolean = this.customStyleFormGroup.get('overrideDropShadow').value; + + if (overrideMainColor) { + this.customStyleFormGroup.get('mainColor').enable({emitEvent: false}); + } else { + this.customStyleFormGroup.get('mainColor').disable({emitEvent: false}); + } + if (overrideBackgroundColor) { + this.customStyleFormGroup.get('backgroundColor').enable({emitEvent: false}); + } else { + this.customStyleFormGroup.get('backgroundColor').disable({emitEvent: false}); + } + if (overrideDropShadow) { + this.customStyleFormGroup.get('dropShadow').enable({emitEvent: false}); + } else { + this.customStyleFormGroup.get('dropShadow').disable({emitEvent: false}); + } + } + + private updatePreviewAppearance() { + this.previewAppearance = {...this.appearance}; + this.previewAppearance.customStyle[this.state] = this.customStyleFormGroup.value; + this.cd.markForCheck(); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.html new file mode 100644 index 0000000000..3b78af6d5d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.html @@ -0,0 +1,44 @@ + +
+
+ + + +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.scss new file mode 100644 index 0000000000..d7cdeffec5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.scss @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../../../../../../scss/constants'; + +.tb-widget-button-custom-style { + display: flex; + flex-direction: row; + align-items: center; + gap: 12px; + button.mat-mdc-icon-button { + color: rgba(0,0,0,0.56); + } + .tb-widget-button-preview-panel { + width: 148px; + height: 48px; + padding: 8px 12px; + border-radius: 4px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + tb-widget-button { + width: 84px; + height: 100%; + } + @media #{$mat-gt-xs} { + width: 168px; + tb-widget-button { + width: 104px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts new file mode 100644 index 0000000000..b9a1605b19 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts @@ -0,0 +1,157 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectorRef, + Component, + forwardRef, + Input, + OnChanges, + OnInit, + Renderer2, + SimpleChanges, + ViewContainerRef, + ViewEncapsulation +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { + WidgetButtonAppearance, + WidgetButtonCustomStyle, + WidgetButtonState +} from '@shared/components/button/widget-button.models'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { MatIconButton } from '@angular/material/button'; +import { + WidgetButtonCustomStylePanelComponent +} from '@home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component'; + +@Component({ + selector: 'tb-widget-button-custom-style', + templateUrl: './widget-button-custom-style.component.html', + styleUrls: ['./widget-button-custom-style.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => WidgetButtonCustomStyleComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class WidgetButtonCustomStyleComponent implements OnInit, OnChanges, ControlValueAccessor { + + @Input() + disabled = false; + + @Input() + appearance: WidgetButtonAppearance; + + @Input() + borderRadius: string; + + @Input() + state: WidgetButtonState; + + widgetButtonState = WidgetButtonState; + + modelValue: WidgetButtonCustomStyle; + + previewAppearance: WidgetButtonAppearance; + + private propagateChange = (_val: any) => {}; + + constructor(private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef, + private cd: ChangeDetectorRef) {} + + ngOnInit(): void { + this.updatePreviewAppearance(); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange) { + if (propName === 'appearance') { + this.updatePreviewAppearance(); + } + } + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(_isDisabled: boolean): void { + } + + writeValue(value: WidgetButtonCustomStyle): void { + this.modelValue = value; + this.updatePreviewAppearance(); + } + + clearStyle() { + this.updateModel(null); + } + + openButtonCustomStylePopup($event: Event, matButton: MatIconButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + appearance: this.appearance, + borderRadius: this.borderRadius, + state: this.state, + customStyle: this.modelValue + }; + const widgetButtonCustomStylePanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, WidgetButtonCustomStylePanelComponent, + ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], true, null, + ctx, + {}, + {}, {}, true); + widgetButtonCustomStylePanelPopover.tbComponentRef.instance.popover = widgetButtonCustomStylePanelPopover; + widgetButtonCustomStylePanelPopover.tbComponentRef.instance.customStyleApplied.subscribe((customStyle) => { + widgetButtonCustomStylePanelPopover.hide(); + this.updateModel(customStyle); + }); + } + } + + private updateModel(value: WidgetButtonCustomStyle): void { + this.modelValue = value; + this.updatePreviewAppearance(); + this.propagateChange(this.modelValue); + } + + private updatePreviewAppearance() { + this.previewAppearance = {...this.appearance}; + if (this.modelValue) { + this.previewAppearance.customStyle[this.state] = this.modelValue; + } + this.cd.markForCheck(); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index a0405d3254..e39c079f75 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -67,6 +67,31 @@ import { SetValueActionSettingsPanelComponent } from '@home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component'; import { CssSizeInputComponent } from '@home/components/widget/lib/settings/common/css-size-input.component'; +import { WidgetActionComponent } from '@home/components/widget/lib/settings/common/action/widget-action.component'; +import { + CustomActionPrettyResourcesTabsComponent +} from '@home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component'; +import { + CustomActionPrettyEditorComponent +} from '@home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component'; +import { + MobileActionEditorComponent +} from '@home/components/widget/lib/settings/common/action/mobile-action-editor.component'; +import { + WidgetActionSettingsComponent +} from '@home/components/widget/lib/settings/common/action/widget-action-settings.component'; +import { + WidgetActionSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/action/widget-action-settings-panel.component'; +import { + WidgetButtonAppearanceComponent +} from '@home/components/widget/lib/settings/common/button/widget-button-appearance.component'; +import { + WidgetButtonCustomStyleComponent +} from '@home/components/widget/lib/settings/common/button/widget-button-custom-style.component'; +import { + WidgetButtonCustomStylePanelComponent +} from '@home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component'; @NgModule({ declarations: [ @@ -93,7 +118,16 @@ import { CssSizeInputComponent } from '@home/components/widget/lib/settings/comm GetValueActionSettingsPanelComponent, DeviceKeyAutocompleteComponent, SetValueActionSettingsComponent, - SetValueActionSettingsPanelComponent + SetValueActionSettingsPanelComponent, + WidgetActionComponent, + CustomActionPrettyResourcesTabsComponent, + CustomActionPrettyEditorComponent, + MobileActionEditorComponent, + WidgetActionSettingsComponent, + WidgetActionSettingsPanelComponent, + WidgetButtonAppearanceComponent, + WidgetButtonCustomStyleComponent, + WidgetButtonCustomStylePanelComponent ], imports: [ CommonModule, @@ -124,7 +158,16 @@ import { CssSizeInputComponent } from '@home/components/widget/lib/settings/comm GetValueActionSettingsPanelComponent, DeviceKeyAutocompleteComponent, SetValueActionSettingsComponent, - SetValueActionSettingsPanelComponent + SetValueActionSettingsPanelComponent, + WidgetActionComponent, + CustomActionPrettyResourcesTabsComponent, + CustomActionPrettyEditorComponent, + MobileActionEditorComponent, + WidgetActionSettingsComponent, + WidgetActionSettingsPanelComponent, + WidgetButtonAppearanceComponent, + WidgetButtonCustomStyleComponent, + WidgetButtonCustomStylePanelComponent ], providers: [ ColorSettingsComponentService, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html index 4cdb0be38e..9bdca64bbd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html @@ -19,30 +19,36 @@
widgets.single-switch.behavior
-
widgets.value-action.initial-state
+
widgets.rpc-state.initial-state
-
widgets.value-action.turn-on
+
widgets.rpc-state.turn-on
-
widgets.value-action.turn-off
+
widgets.rpc-state.turn-off
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.ts index 7e4f1d1d96..f4bd08a20c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.ts @@ -15,7 +15,7 @@ /// import { Component } from '@angular/core'; -import { TargetDevice, WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { TargetDevice, WidgetSettings, WidgetSettingsComponent, widgetType } from '@shared/models/widget.models'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -37,6 +37,10 @@ export class SingleSwitchWidgetSettingsComponent extends WidgetSettingsComponent return this.widget?.config?.targetDevice; } + get widgetType(): widgetType { + return this.widget?.type; + } + singleSwitchLayouts = singleSwitchLayouts; singleSwitchLayoutTranslationMap = singleSwitchLayoutTranslations; diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index 377ab8b6ea..6037a4ea43 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -565,6 +565,9 @@ export class WidgetComponentService { if (isUndefined(result.typeParameters.embedTitlePanel)) { result.typeParameters.embedTitlePanel = false; } + if (isUndefined(result.typeParameters.overflowVisible)) { + result.typeParameters.overflowVisible = false; + } if (isUndefined(result.typeParameters.hideDataSettings)) { result.typeParameters.hideDataSettings = false; } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 82a4856f4a..11bf6871f8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -71,6 +71,7 @@ import { BarChartWithLabelsWidgetComponent } from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.component'; import { SingleSwitchWidgetComponent } from '@home/components/widget/lib/rpc/single-switch-widget.component'; +import { ActionButtonWidgetComponent } from '@home/components/widget/lib/button/action-button-widget.component'; @NgModule({ declarations: @@ -114,7 +115,8 @@ import { SingleSwitchWidgetComponent } from '@home/components/widget/lib/rpc/sin DoughnutWidgetComponent, RangeChartWidgetComponent, BarChartWithLabelsWidgetComponent, - SingleSwitchWidgetComponent + SingleSwitchWidgetComponent, + ActionButtonWidgetComponent ], imports: [ CommonModule, @@ -162,7 +164,8 @@ import { SingleSwitchWidgetComponent } from '@home/components/widget/lib/rpc/sin DoughnutWidgetComponent, RangeChartWidgetComponent, BarChartWithLabelsWidgetComponent, - SingleSwitchWidgetComponent + SingleSwitchWidgetComponent, + ActionButtonWidgetComponent ], providers: [ {provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html index 4f428dd983..7235b300bc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html @@ -24,6 +24,7 @@ 'tb-highlighted': isHighlighted(widget), 'tb-not-highlighted': isNotHighlighted(widget), 'mat-elevation-z4': widget.dropShadow, + 'tb-overflow-visible': widgetComponent.widgetContext?.overflowVisible, 'tb-has-timewindow': widget.hasTimewindow, 'tb-edit': isEdit }" @@ -88,6 +89,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss index 124b6c4d99..3143ab1ee2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss @@ -26,6 +26,13 @@ outline: none; transition: all .2s ease-in-out; + + &.tb-overflow-visible { + overflow: visible; + .tb-widget { + overflow: visible; + } + } } div.tb-widget { diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts index f4b54a395d..81c206391a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts @@ -22,7 +22,6 @@ import { ElementRef, EventEmitter, HostBinding, - Inject, Input, OnDestroy, OnInit, @@ -36,10 +35,9 @@ import { DashboardWidget, DashboardWidgets } from '@home/models/dashboard-compon import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { SafeStyle } from '@angular/platform-browser'; -import { guid, isNotEmptyStr } from '@core/utils'; -import cssjs from '@core/css/css'; -import { DOCUMENT } from '@angular/common'; +import { isNotEmptyStr } from '@core/utils'; import { GridsterItemComponent } from 'angular-gridster2'; +import { UtilsService } from '@core/services/utils.service'; export enum WidgetComponentActionType { MOUSE_DOWN, @@ -86,6 +84,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, A @Input() isEdit: boolean; + @Input() + isPreview: boolean; + @Input() isMobile: boolean; @@ -115,7 +116,7 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, A constructor(protected store: Store, private cd: ChangeDetectorRef, private renderer: Renderer2, - @Inject(DOCUMENT) private document: Document) { + private utils: UtilsService) { super(store); } @@ -123,12 +124,8 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, A this.widget.widgetContext.containerChangeDetector = this.cd; const cssString = this.widget.widget.config.widgetCss; if (isNotEmptyStr(cssString)) { - const cssParser = new cssjs(); - cssParser.testMode = false; - this.cssClass = 'tb-widget-css-' + guid(); - this.renderer.addClass(this.gridsterItem.el, this.cssClass); - cssParser.cssPreviewNamespace = this.cssClass; - cssParser.createStyleElement(this.cssClass, cssString); + this.cssClass = + this.utils.applyCssToElement(this.renderer, this.gridsterItem.el, 'tb-widget-css', cssString); } } @@ -138,10 +135,7 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, A ngOnDestroy(): void { if (this.cssClass) { - const el = this.document.getElementById(this.cssClass); - if (el) { - el.parentNode.removeChild(el); - } + this.utils.clearCssElement(this.renderer, this.cssClass); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html index 38c4527074..3a9ced83de 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html @@ -25,6 +25,7 @@ [autofillHeight]="true" [columns]="24" [isEdit]="false" + [isPreview]="true" [isMobileDisabled]="true" [isEditActionEnabled]="false" [isRemoveActionEnabled]="false"> diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index 1c029b6524..15e982c060 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -131,6 +131,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @Input() isEdit: boolean; + @Input() + isPreview: boolean; + @Input() isMobile: boolean; @@ -231,6 +234,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.widgetContext.store = this.store; this.widgetContext.servicesMap = ServicesMap; this.widgetContext.isEdit = this.isEdit; + this.widgetContext.isPreview = this.isPreview; this.widgetContext.isMobile = this.isMobile; this.widgetContext.toastTargetId = this.toastTargetId; @@ -252,6 +256,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI handleWidgetAction: this.handleWidgetAction.bind(this), elementClick: this.elementClick.bind(this), cardClick: this.cardClick.bind(this), + click: this.click.bind(this), getActiveEntityInfo: this.getActiveEntityInfo.bind(this), openDashboardStateInSeparateDialog: this.openDashboardStateInSeparateDialog.bind(this), openDashboardStateInPopover: this.openDashboardStateInPopover.bind(this) @@ -411,6 +416,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.widgetType = this.widgetInfo.widgetTypeFunction; this.typeParameters = this.widgetInfo.typeParameters; this.widgetContext.embedTitlePanel = this.typeParameters.embedTitlePanel; + this.widgetContext.overflowVisible = this.typeParameters.overflowVisible; if (!this.widgetType) { this.widgetTypeInstance = {}; @@ -1423,7 +1429,15 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } private cardClick($event: Event) { - const descriptors = this.getActionDescriptors('cardClick'); + this.onClick($event, 'cardClick'); + } + + private click($event: Event) { + this.onClick($event, 'click'); + } + + private onClick($event: Event, sourceId: string) { + const descriptors = this.getActionDescriptors(sourceId); if (descriptors.length) { $event.stopPropagation(); const descriptor = descriptors[0]; diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 4a7fc32502..a834120dc6 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -263,6 +263,7 @@ export class WidgetContext { height: number; $scope: IDynamicWidgetComponent; isEdit: boolean; + isPreview: boolean; isMobile: boolean; toastTargetId: string; @@ -279,6 +280,7 @@ export class WidgetContext { timeWindow?: WidgetTimewindow; embedTitlePanel?: boolean; + overflowVisible?: boolean; hideTitlePanel = false; diff --git a/ui-ngx/src/app/shared/components/button/widget-button.component.html b/ui-ngx/src/app/shared/components/button/widget-button.component.html new file mode 100644 index 0000000000..9b239db068 --- /dev/null +++ b/ui-ngx/src/app/shared/components/button/widget-button.component.html @@ -0,0 +1,39 @@ + + diff --git a/ui-ngx/src/app/shared/components/button/widget-button.component.scss b/ui-ngx/src/app/shared/components/button/widget-button.component.scss new file mode 100644 index 0000000000..06c875de7f --- /dev/null +++ b/ui-ngx/src/app/shared/components/button/widget-button.component.scss @@ -0,0 +1,189 @@ +/** + * Copyright © 2016-2024 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. + */ +$defaultMainColor: #3F52DD; +$defaultBackgroundColor: #FFFFFF; +$defaultBoxShadowColor: rgba(0, 0, 0, 0.08); +$defaultDisabledBoxShadowColor: rgba(0, 0, 0, 0); + +$defaultMainColorDisabled: rgba(0, 0, 0, 0.38); +$defaultBackgroundColorDisabled: rgba(0, 0, 0, 0.03); + +$mainColorEnabled: var(--tb-widget-button-main-color-enabled, $defaultMainColor); +$backgroundColorEnabled: var(--tb-widget-button-background-color-enabled, $defaultBackgroundColor); +$boxShadowColorEnabled: var(--tb-widget-button-box-shadow-color-enabled, $defaultBoxShadowColor); + +$mainColorHovered: var(--tb-widget-button-main-color-hovered, $defaultMainColor); +$backgroundColorHovered: var(--tb-widget-button-background-color-hovered, $defaultBackgroundColor); +$boxShadowColorHovered: var(--tb-widget-button-box-shadow-color-hovered, $defaultBoxShadowColor); +$mainColorHoveredFilled: var(--tb-widget-button-main-color-hovered-filled, #263BD7); // main.darken(6) + +$mainColorPressed: var(--tb-widget-button-main-color-pressed, $defaultMainColor); +$backgroundColorPressed: var(--tb-widget-button-background-color-pressed, $defaultBackgroundColor); +$boxShadowColorPressed: var(--tb-widget-button-box-shadow-color-pressed, $defaultBoxShadowColor); +$mainColorPressedFilled: var(--tb-widget-button-main-color-pressed-filled, #2234BD); // main.darken(12) +$mainColorPressedRipple: var(--tb-widget-button-main-color-pressed-ripple, rgba(63, 82, 221, 0.1)); // Alpha(Main, 0.1) +$mainColorPressedRippleFilled: var(--tb-widget-button-main-color-pressed-ripple-filled, #1D2DA3); // main.darken(18) + +$mainColorActivated: var(--tb-widget-button-main-color-activated, $defaultMainColor); +$backgroundColorActivated: var(--tb-widget-button-background-color-activated, $defaultBackgroundColor); +$boxShadowColorActivated: var(--tb-widget-button-box-shadow-color-activated, $defaultBoxShadowColor); +$mainColorActivatedFilled: var(--tb-widget-button-main-color-activated-filled, #2234BD); // main.darken(12) + +$mainColorDisabled: var(--tb-widget-button-main-color-disabled, $defaultMainColorDisabled); +$backgroundColorDisabled: var(--tb-widget-button-background-color-disabled, $defaultBackgroundColorDisabled); +$boxShadowColorDisabled: var(--tb-widget-button-box-shadow-color-activated, $defaultBoxShadowColor); + + +@mixin _tb-widget-button-styles($main, $background, $boxShadow) { + color: $main; + background-color: $background; + box-shadow: 0 4px 8px 0 $boxShadow; + &.tb-outlined { + border: 1px solid $main; + } + &.tb-filled { + color: $background; + background-color: $main; + } + &.tb-underlined { + border-bottom: 2px solid $main; + } + &.tb-basic { + background-color: transparent; + } +} + + +.mat-mdc-button.mat-mdc-button-base.tb-widget-button { + width: 100%; + height: 100%; + padding: 8px 12px; + .mdc-button__label { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } + .tb-widget-button-content { + width: 100%; + display: flex; + flex-direction: row; + gap: 4px; + justify-content: center; + align-items: center; + .mat-icon { + margin: 0; + } + span.tb-widget-button-label { + line-height: normal; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + .mat-mdc-button-persistent-ripple::before { + opacity: 0; + } + + @include _tb-widget-button-styles($mainColorEnabled, $backgroundColorEnabled, $boxShadowColorEnabled); + + &:not(:disabled):not(.tb-disabled-state) { + &:hover, &.tb-hover-state { + &:not(:active):not(.tb-active-state) { + &:not(.tb-filled) { + .mat-mdc-button-persistent-ripple::before { + opacity: 0.04; + background-color: $mainColorHovered; + } + } + &.tb-filled { + .mat-mdc-button-persistent-ripple::before { + opacity: 1; + background-color: $mainColorHoveredFilled; + } + } + @include _tb-widget-button-styles($mainColorHovered, $backgroundColorHovered, $boxShadowColorHovered); + } + } + &.tb-pressed-state { + &:not(.tb-filled) { + .mat-mdc-button-ripple { + background-color: $mainColorPressedRipple; + } + } + &.tb-filled { + .mat-mdc-button-ripple { + background-color: $mainColorPressedRippleFilled; + } + } + } + &.tb-pressed { + &:not(.tb-filled) { + .mat-ripple-element { + background-color: $mainColorPressedRipple; + } + } + &.tb-filled { + .mat-ripple-element { + background-color: $mainColorPressedRippleFilled; + } + } + } + &.tb-pressed, &.tb-pressed-state { + &:not(.tb-filled) { + .mat-mdc-button-persistent-ripple::before { + opacity: 0.12; + background-color: $mainColorPressed; + } + } + &.tb-filled { + .mat-mdc-button-persistent-ripple::before { + opacity: 1; + background-color: $mainColorPressedFilled; + } + } + @include _tb-widget-button-styles($mainColorPressed, $backgroundColorPressed, $boxShadowColorPressed); + } + &:active, &.tb-active-state { + &:not(.tb-pressed):not(.tb-pressed-state) { + &:not(.tb-filled) { + .mat-mdc-button-persistent-ripple::before { + opacity: 0.12; + background-color: $mainColorActivated; + } + } + &.tb-filled { + .mat-mdc-button-persistent-ripple::before { + opacity: 1; + background-color: $mainColorActivatedFilled; + } + } + @include _tb-widget-button-styles($mainColorActivated, $backgroundColorActivated, $boxShadowColorActivated); + } + } + } + + &:disabled, &.tb-disabled-state { + &:not(.tb-filled) { + @include _tb-widget-button-styles($mainColorDisabled, $backgroundColorDisabled, $boxShadowColorDisabled); + } + &.tb-filled { + @include _tb-widget-button-styles($backgroundColorDisabled, $mainColorDisabled, $boxShadowColorDisabled); + } + } +} diff --git a/ui-ngx/src/app/shared/components/button/widget-button.component.ts b/ui-ngx/src/app/shared/components/button/widget-button.component.ts new file mode 100644 index 0000000000..d8fe88b48e --- /dev/null +++ b/ui-ngx/src/app/shared/components/button/widget-button.component.ts @@ -0,0 +1,178 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + Component, + ElementRef, + EventEmitter, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + Renderer2, + SimpleChanges, ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { + generateWidgetButtonAppearanceCss, + widgetButtonDefaultAppearance +} from '@shared/components/button/widget-button.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { ComponentStyle, iconStyle } from '@shared/models/widget-settings.models'; +import { UtilsService } from '@core/services/utils.service'; +import { ResizeObserver } from '@juggle/resize-observer'; + +const initialButtonHeight = 60; +const horizontalLayoutPadding = 24; +const verticalLayoutPadding = 16; + +@Component({ + selector: 'tb-widget-button', + templateUrl: './widget-button.component.html', + styleUrls: ['./widget-button.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class WidgetButtonComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges { + + @ViewChild('widgetButton', {read: ElementRef}) + widgetButton: ElementRef; + + @ViewChild('widgetButtonContent', {static: false}) + widgetButtonContent: ElementRef; + + @Input() + appearance = widgetButtonDefaultAppearance; + + @Input() + borderRadius = '4px'; + + @Input() + @coerceBoolean() + disabled = false; + + @Input() + @coerceBoolean() + activated = false; + + @Input() + @coerceBoolean() + hovered = false; + + @Input() + @coerceBoolean() + pressed = false; + + @Input() + @coerceBoolean() + disableEvents = false; + + @Output() + clicked = new EventEmitter(); + + iconStyle: ComponentStyle = {}; + + mousePressed = false; + + private buttonResize$: ResizeObserver; + + private appearanceCssClass: string; + + constructor(private renderer: Renderer2, + private elementRef: ElementRef, + private utils: UtilsService) {} + + ngOnInit(): void { + this.updateAppearance(); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange) { + if (propName === 'appearance') { + this.updateAppearance(); + } + } + } + } + + ngAfterViewInit(): void { + this.updateAutoScale(); + } + + ngOnDestroy(): void { + if (this.buttonResize$) { + this.buttonResize$.disconnect(); + } + this.clearAppearanceCss(); + } + + private updateAppearance(): void { + this.clearAppearanceCss(); + if (this.appearance.showIcon) { + this.iconStyle = iconStyle(this.appearance.iconSize, this.appearance.iconSizeUnit); + } + const appearanceCss = generateWidgetButtonAppearanceCss(this.appearance); + this.appearanceCssClass = this.utils.applyCssToElement(this.renderer, this.elementRef.nativeElement, + 'tb-widget-button', appearanceCss); + this.updateAutoScale(); + } + + private clearAppearanceCss(): void { + if (this.appearanceCssClass) { + this.utils.clearCssElement(this.renderer, this.appearanceCssClass, this.elementRef?.nativeElement); + this.appearanceCssClass = null; + } + } + + private updateAutoScale() { + if (this.buttonResize$) { + this.buttonResize$.disconnect(); + } + if (this.widgetButton && this.widgetButtonContent) { + if (this.appearance.autoScale) { + this.buttonResize$ = new ResizeObserver(() => { + this.onResize(); + }); + this.buttonResize$.observe(this.widgetButton.nativeElement); + this.onResize(); + } else { + this.renderer.setStyle(this.widgetButtonContent.nativeElement, 'transform', 'none'); + this.renderer.setStyle(this.widgetButtonContent.nativeElement, 'width', '100%'); + } + } + } + + private onResize() { + const height = this.widgetButton.nativeElement.getBoundingClientRect().height; + const buttonScale = height / initialButtonHeight; + const paddingScale = Math.min(buttonScale, 1); + const buttonWidth = this.widgetButton.nativeElement.getBoundingClientRect().width - (horizontalLayoutPadding * paddingScale); + const buttonHeight = this.widgetButton.nativeElement.getBoundingClientRect().height - (verticalLayoutPadding * paddingScale); + this.renderer.setStyle(this.widgetButtonContent.nativeElement, 'transform', `scale(1)`); + this.renderer.setStyle(this.widgetButtonContent.nativeElement, 'width', 'auto'); + const contentWidth = this.widgetButtonContent.nativeElement.getBoundingClientRect().width; + const contentHeight = this.widgetButtonContent.nativeElement.getBoundingClientRect().height; + const maxScale = Math.max(1, buttonScale); + const scale = Math.min(Math.min(buttonWidth / contentWidth, buttonHeight / contentHeight), maxScale); + const targetWidth = buttonWidth / scale; + this.renderer.setStyle(this.widgetButtonContent.nativeElement, 'width', targetWidth + 'px'); + this.renderer.setStyle(this.widgetButtonContent.nativeElement, 'transform', `scale(${scale})`); + } + +} diff --git a/ui-ngx/src/app/shared/components/button/widget-button.models.ts b/ui-ngx/src/app/shared/components/button/widget-button.models.ts new file mode 100644 index 0000000000..616af28588 --- /dev/null +++ b/ui-ngx/src/app/shared/components/button/widget-button.models.ts @@ -0,0 +1,259 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { cssUnit } from '@shared/models/widget-settings.models'; +import tinycolor from 'tinycolor2'; + +const defaultMainColor = '#3F52DD'; +const defaultBackgroundColor = '#FFFFFF'; + +export const defaultMainColorDisabled = 'rgba(0, 0, 0, 0.38)'; +export const defaultBackgroundColorDisabled = 'rgba(0, 0, 0, 0.03)'; + +const defaultBoxShadowColor = 'rgba(0, 0, 0, 0.08)'; +const defaultDisabledBoxShadowColor = 'rgba(0, 0, 0, 0)'; + +export enum WidgetButtonType { + outlined = 'outlined', + filled = 'filled', + underlined = 'underlined', + basic = 'basic' +} + +export const widgetButtonTypes = Object.keys(WidgetButtonType) as WidgetButtonType[]; + +export const widgetButtonTypeTranslations = new Map( + [ + [WidgetButtonType.outlined, 'widgets.button.outlined'], + [WidgetButtonType.filled, 'widgets.button.filled'], + [WidgetButtonType.underlined, 'widgets.button.underlined'], + [WidgetButtonType.basic, 'widgets.button.basic'] + ] +); + +export const widgetButtonTypeImages = new Map( + [ + [WidgetButtonType.outlined, 'assets/widget/button/outlined.svg'], + [WidgetButtonType.filled, 'assets/widget/button/filled.svg'], + [WidgetButtonType.underlined, 'assets/widget/button/underlined.svg'], + [WidgetButtonType.basic, 'assets/widget/button/basic.svg'] + ] +); + +export enum WidgetButtonState { + enabled = 'enabled', + hovered = 'hovered', + pressed = 'pressed', + activated = 'activated', + disabled = 'disabled' +} + +export const widgetButtonStates = Object.keys(WidgetButtonState) as WidgetButtonState[]; + +export const widgetButtonStatesTranslations = new Map( + [ + [WidgetButtonState.enabled, 'widgets.button-state.enabled'], + [WidgetButtonState.hovered, 'widgets.button-state.hovered'], + [WidgetButtonState.pressed, 'widgets.button-state.pressed'], + [WidgetButtonState.activated, 'widgets.button-state.activated'], + [WidgetButtonState.disabled, 'widgets.button-state.disabled'] + ] +); + +export interface WidgetButtonCustomStyle { + overrideMainColor?: boolean; + mainColor?: string; + overrideBackgroundColor?: boolean; + backgroundColor?: string; + overrideDropShadow?: boolean; + dropShadow?: boolean; +} + +export type WidgetButtonCustomStyles = Record; + +export interface WidgetButtonAppearance { + type: WidgetButtonType; + autoScale: boolean; + showLabel: boolean; + label: string; + showIcon: boolean; + icon: string; + iconSize: number; + iconSizeUnit: cssUnit; + mainColor: string; + backgroundColor: string; + customStyle: WidgetButtonCustomStyles; +} + +export const widgetButtonDefaultAppearance: WidgetButtonAppearance = { + type: WidgetButtonType.outlined, + autoScale: true, + showLabel: true, + label: 'Button', + showIcon: true, + icon: 'home', + iconSize: 24, + iconSizeUnit: 'px', + mainColor: defaultMainColor, + backgroundColor: defaultBackgroundColor, + customStyle: { + enabled: null, + hovered: null, + pressed: null, + activated: null, + disabled: null + } +}; + +const mainColorVarPrefix = '--tb-widget-button-main-color-'; +const backgroundColorVarPrefix = '--tb-widget-button-background-color-'; +const boxShadowColorVarPrefix = '--tb-widget-button-box-shadow-color-'; + +abstract class ButtonStateCssGenerator { + + constructor() {} + + public generateStateCss(appearance: WidgetButtonAppearance): string { + let mainColor = this.getMainColor(appearance); + let backgroundColor = this.getBackgroundColor(appearance); + const shadowEnabledByDefault = appearance.type !== WidgetButtonType.basic; + let shadowColor = shadowEnabledByDefault ? defaultBoxShadowColor : defaultDisabledBoxShadowColor; + const stateCustomStyle = appearance.customStyle[this.state]; + if (stateCustomStyle?.overrideMainColor && stateCustomStyle?.mainColor) { + mainColor = stateCustomStyle.mainColor; + } + if (stateCustomStyle?.overrideBackgroundColor && stateCustomStyle?.backgroundColor) { + backgroundColor = stateCustomStyle.backgroundColor; + } + if (stateCustomStyle?.overrideDropShadow) { + shadowColor = !!stateCustomStyle.dropShadow ? defaultBoxShadowColor : defaultDisabledBoxShadowColor; + } + + let css = `${mainColorVarPrefix}${this.state}: ${mainColor};\n`+ + `${backgroundColorVarPrefix}${this.state}: ${backgroundColor};\n`+ + `${boxShadowColorVarPrefix}${this.state}: ${shadowColor};`; + const additionalCss = this.generateAdditionalStateCss(mainColor, backgroundColor); + if (additionalCss) { + css += `\n${additionalCss}`; + } + return css; + } + + protected abstract get state(): WidgetButtonState; + + protected getMainColor(appearance: WidgetButtonAppearance): string { + return appearance.mainColor || defaultMainColor; + } + + protected getBackgroundColor(appearance: WidgetButtonAppearance): string { + return appearance.backgroundColor || defaultBackgroundColor; + } + + protected generateAdditionalStateCss(_mainColor: string, _backgroundColor: string): string { + return null; + } +} + +class EnabledButtonStateCssGenerator extends ButtonStateCssGenerator { + + protected get state(): WidgetButtonState { + return WidgetButtonState.enabled; + } +} + +class HoveredButtonStateCssGenerator extends ButtonStateCssGenerator { + + protected get state(): WidgetButtonState { + return WidgetButtonState.hovered; + } + + protected generateAdditionalStateCss(mainColor: string): string { + const mainColorHoveredFilled = darkenColor(mainColor, 6); + return `--tb-widget-button-main-color-hovered-filled: ${mainColorHoveredFilled};`; + } +} + +class PressedButtonStateCssGenerator extends ButtonStateCssGenerator { + + protected get state(): WidgetButtonState { + return WidgetButtonState.pressed; + } + + protected generateAdditionalStateCss(mainColor: string): string { + const mainColorPressedFilled = darkenColor(mainColor, 12); + const mainColorInstance = tinycolor(mainColor); + const mainColorPressedRipple = mainColorInstance.setAlpha(mainColorInstance.getAlpha() * 0.1).toRgbString(); + const mainColorPressedRippleFilled = darkenColor(mainColor, 18); + return `--tb-widget-button-main-color-pressed-filled: ${mainColorPressedFilled};\n`+ + `--tb-widget-button-main-color-pressed-ripple: ${mainColorPressedRipple};\n`+ + `--tb-widget-button-main-color-pressed-ripple-filled: ${mainColorPressedRippleFilled};`; + } +} + +class ActivatedButtonStateCssGenerator extends ButtonStateCssGenerator { + + protected get state(): WidgetButtonState { + return WidgetButtonState.activated; + } + + protected generateAdditionalStateCss(mainColor: string): string { + const mainColorActivatedFilled = darkenColor(mainColor, 12); + return `--tb-widget-button-main-color-activated-filled: ${mainColorActivatedFilled};`; + } +} + +class DisabledButtonStateCssGenerator extends ButtonStateCssGenerator { + + protected get state(): WidgetButtonState { + return WidgetButtonState.disabled; + } + + protected getMainColor(): string { + return defaultMainColorDisabled; + } + + protected getBackgroundColor(): string { + return defaultBackgroundColorDisabled; + } +} + +const buttonStateCssGeneratorsMap = new Map( + [ + [WidgetButtonState.enabled, new EnabledButtonStateCssGenerator()], + [WidgetButtonState.hovered, new HoveredButtonStateCssGenerator()], + [WidgetButtonState.pressed, new PressedButtonStateCssGenerator()], + [WidgetButtonState.activated, new ActivatedButtonStateCssGenerator()], + [WidgetButtonState.disabled, new DisabledButtonStateCssGenerator()] + ] +); + +const widgetButtonCssSelector = '.mat-mdc-button.mat-mdc-button-base.tb-widget-button'; + +export const generateWidgetButtonAppearanceCss = (appearance: WidgetButtonAppearance): string => { + let statesCss = ''; + for (const state of widgetButtonStates) { + const generator = buttonStateCssGeneratorsMap.get(state); + statesCss += `\n${generator.generateStateCss(appearance)}`; + } + return `${widgetButtonCssSelector} {\n`+ + `${statesCss}\n`+ + `}`; +}; + +const darkenColor = (inputColor: string, amount: number): string => { + const input = tinycolor(inputColor); + return input.darken(amount).toRgbString(); +}; diff --git a/ui-ngx/src/app/shared/models/action-widget-settings.models.ts b/ui-ngx/src/app/shared/models/action-widget-settings.models.ts index d1442c09c5..c9a61c5ad0 100644 --- a/ui-ngx/src/app/shared/models/action-widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/action-widget-settings.models.ts @@ -15,6 +15,7 @@ /// import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { widgetType } from '@shared/models/widget.models'; export enum GetValueAction { DO_NOTHING = 'DO_NOTHING', @@ -25,6 +26,14 @@ export enum GetValueAction { export const getValueActions = Object.keys(GetValueAction) as GetValueAction[]; +export const getValueActionsByWidgetType = (type: widgetType): GetValueAction[] => { + if (type !== widgetType.rpc) { + return getValueActions.filter(action => action !== GetValueAction.EXECUTE_RPC); + } else { + return getValueActions; + } +}; + export const getValueActionTranslations = new Map( [ [GetValueAction.DO_NOTHING, 'widgets.value-action.do-nothing'], @@ -75,7 +84,7 @@ export interface ValueActionSettings { export interface GetValueSettings extends ValueActionSettings { action: GetValueAction; defaultValue: V; - executeRpc: RpcSettings; + executeRpc?: RpcSettings; getAttribute: GetAttributeValueSettings; getTimeSeries: GetTelemetryValueSettings; dataToValue: DataToValueSettings; @@ -89,6 +98,14 @@ export enum SetValueAction { export const setValueActions = Object.keys(SetValueAction) as SetValueAction[]; +export const setValueActionsByWidgetType = (type: widgetType): SetValueAction[] => { + if (type !== widgetType.rpc) { + return setValueActions.filter(action => action !== SetValueAction.EXECUTE_RPC); + } else { + return setValueActions; + } +}; + export const setValueActionTranslations = new Map( [ [SetValueAction.EXECUTE_RPC, 'widgets.value-action.execute-rpc'], diff --git a/ui-ngx/src/app/shared/models/widget-settings.models.ts b/ui-ngx/src/app/shared/models/widget-settings.models.ts index cf72e20025..5bd9e26f13 100644 --- a/ui-ngx/src/app/shared/models/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -15,7 +15,14 @@ /// import { isDefinedAndNotNull, isNumber, isNumeric, isUndefinedOrNull, parseFunction } from '@core/utils'; -import { DataEntry, DataKey, Datasource, DatasourceData } from '@shared/models/widget.models'; +import { + DataEntry, + DataKey, + Datasource, + DatasourceData, + DatasourceType, + TargetDevice, TargetDeviceType +} from '@shared/models/widget.models'; import { Injector } from '@angular/core'; import { DatePipe } from '@angular/common'; import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; @@ -600,6 +607,24 @@ export const updateDataKeyByLabel = (datasources: Datasource[], dataKey: DataKey } }; +export const getTargetDeviceFromDatasources = (datasources?: Datasource[]): TargetDevice => { + if (datasources && datasources.length) { + const datasource = datasources[0]; + if (datasource?.type === DatasourceType.device) { + return { + type: TargetDeviceType.device, + deviceId: datasource?.deviceId + }; + } else if (datasource?.type === DatasourceType.entity) { + return { + type: TargetDeviceType.entity, + entityAliasId: datasource?.entityAliasId + }; + } + } + return null; +}; + export const getAlarmFilterConfig = (datasources?: Datasource[]): AlarmFilterConfig => { if (datasources && datasources.length) { const config = datasources[0].alarmFilterConfig; diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 3a13f51b24..625d957cff 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -180,6 +180,7 @@ export interface WidgetTypeParameters { previewWidth?: string; previewHeight?: string; embedTitlePanel?: boolean; + overflowVisible?: boolean; hideDataSettings?: boolean; defaultDataKeysFunction?: (configComponent: any, configData: any) => DataKey[]; defaultLatestDataKeysFunction?: (configComponent: any, configData: any) => DataKey[]; @@ -410,6 +411,23 @@ export interface Datasource { [key: string]: any; } +export const datasourceValid = (datasource: Datasource): boolean => { + const type: DatasourceType = datasource?.type; + if (type) { + switch (type) { + case DatasourceType.function: + case DatasourceType.alarmCount: + return true; + case DatasourceType.device: + return !!datasource.deviceId; + case DatasourceType.entity: + case DatasourceType.entityCount: + return !!datasource.entityAliasId; + } + } + return false; +}; + export enum TargetDeviceType { device = 'device', entity = 'entity' @@ -675,6 +693,14 @@ export const actionDescriptorToAction = (descriptor: WidgetActionDescriptor): Wi return result; }; +export const defaultWidgetAction = (setEntityId = true): WidgetAction => ({ + type: WidgetActionType.updateDashboardState, + targetDashboardStateId: null, + openRightLayout: false, + setEntityId, + stateEntityParamName: null + }); + export interface WidgetComparisonSettings { comparisonEnabled?: boolean; timeForComparison?: moment_.unitOfTime.DurationConstructor; diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 0abf2167f6..71f121ab17 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -217,6 +217,7 @@ import { MultipleGalleryImageInputComponent } from '@shared/components/image/mul import { EmbedImageDialogComponent } from '@shared/components/image/embed-image-dialog.component'; import { ImageGalleryDialogComponent } from '@shared/components/image/image-gallery-dialog.component'; import { RuleChainSelectPanelComponent } from '@shared/components/rule-chain/rule-chain-select-panel.component'; +import { WidgetButtonComponent } from '@shared/components/button/widget-button.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -414,7 +415,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) GalleryImageInputComponent, MultipleGalleryImageInputComponent, EmbedImageDialogComponent, - ImageGalleryDialogComponent + ImageGalleryDialogComponent, + WidgetButtonComponent ], imports: [ CommonModule, @@ -666,7 +668,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) GalleryImageInputComponent, MultipleGalleryImageInputComponent, EmbedImageDialogComponent, - ImageGalleryDialogComponent + ImageGalleryDialogComponent, + WidgetButtonComponent ] }) export class SharedModule { } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index e18d21f609..fa9945dd50 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5182,6 +5182,42 @@ "invalid-widget-file-error": "Unable to import widget: Invalid widget data structure." }, "widgets": { + "action-button": { + "behavior": "Behavior", + "on-click": "On click", + "on-click-hint": "Action performed when the button is clicked." + }, + "button": { + "layout": "Layout", + "outlined": "Outlined", + "filled": "Filled", + "underlined": "Underlined", + "basic": "Basic", + "auto-scale": "Auto scale", + "label": "Label", + "icon": "Icon", + "color-palette": "Color palette", + "main": "Main", + "background": "Background", + "custom-styles": "Custom styles", + "clear-style": "Clear style", + "shadow": "Shadow", + "enabled": "Enabled", + "disabled": "Disabled", + "preview": "Preview", + "copy-style-from": "Copy style from" + }, + "button-state": { + "activated-state": "Activated state", + "activated-state-hint": "Condition under which the button is active.", + "disabled-state": "Disabled state", + "disabled-state-hint": "Condition under which the button is disabled.", + "enabled": "Enabled", + "hovered": "Hovered", + "pressed": "Pressed", + "activated": "Activated", + "disabled": "Disabled" + }, "background": { "background": "Background", "background-settings": "Background settings", @@ -6483,7 +6519,7 @@ "source-entity-alias": "Source entity alias", "source-entity-attribute": "Source entity attribute" }, - "value-action": { + "rpc-state": { "initial-state": "Initial state", "initial-state-hint": "Action to get the initial value of the component.", "turn-on": "Turn 'On'", @@ -6491,7 +6527,9 @@ "turn-off": "Turn 'Off'", "turn-off-hint": "Action performed to turn OFF the component.", "on": "On", - "off": "Off", + "off": "Off" + }, + "value-action": { "do-nothing": "Do nothing", "execute-rpc": "Execute RPC", "get-attribute": "Get attribute", @@ -6530,7 +6568,7 @@ "converter-function": "Function", "converter-constant": "Constant", "parse-value-function": "Parse value function", - "on-when-result-is": "'On' when result is", + "state-when-result-is": "'{{state}}' when result is", "parameters": "Parameters", "convert-value-function": "Convert value function", "error": { @@ -6785,7 +6823,8 @@ "element-click": "On HTML element click", "pie-slice-click": "On slice click", "row-double-click": "On row double click", - "card-click": "On card click" + "card-click": "On card click", + "click": "On click" } }, "paginator" : { diff --git a/ui-ngx/src/assets/widget/button/basic.svg b/ui-ngx/src/assets/widget/button/basic.svg new file mode 100644 index 0000000000..6b005e345e --- /dev/null +++ b/ui-ngx/src/assets/widget/button/basic.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/button/filled.svg b/ui-ngx/src/assets/widget/button/filled.svg new file mode 100644 index 0000000000..c17871583a --- /dev/null +++ b/ui-ngx/src/assets/widget/button/filled.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/button/outlined.svg b/ui-ngx/src/assets/widget/button/outlined.svg new file mode 100644 index 0000000000..63b2232060 --- /dev/null +++ b/ui-ngx/src/assets/widget/button/outlined.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/button/underlined.svg b/ui-ngx/src/assets/widget/button/underlined.svg new file mode 100644 index 0000000000..6638662a21 --- /dev/null +++ b/ui-ngx/src/assets/widget/button/underlined.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index d6fceb20f7..635d330572 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -607,6 +607,14 @@ } } + button.mat-mdc-button-base.tb-nowrap { + .mdc-button__label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + .mat-mdc-chip-listbox.center-stretch { .mat-mdc-standard-chip { flex: 1; From 42edd481e7795b8f5401db70b09697472b83b020 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 2 Feb 2024 13:53:41 +0200 Subject: [PATCH 07/11] UI: Add advanced settings for action button. Update Get attribute/time-series value action to always subscribe for updates. --- .../system/widget_types/action_button.json | 4 +- .../widget/lib/action/action-widget.models.ts | 42 ++--------- .../lib/button/action-button-widget.models.ts | 12 ++-- .../lib/rpc/single-switch-widget.models.ts | 6 +- ...tion-button-widget-settings.component.html | 51 ++++++++++++++ ...action-button-widget-settings.component.ts | 70 +++++++++++++++++++ ...value-action-settings-panel.component.html | 10 --- ...t-value-action-settings-panel.component.ts | 8 +-- ...led-indicator-widget-settings.component.ts | 2 +- .../round-switch-widget-settings.component.ts | 2 +- ...single-switch-widget-settings.component.ts | 9 ++- .../slide-toggle-widget-settings.component.ts | 2 +- ...witch-control-widget-settings.component.ts | 2 +- .../lib/settings/widget-settings.module.ts | 20 ++++-- .../models/action-widget-settings.models.ts | 18 +---- .../assets/locale/locale.constant-en_US.json | 2 - 16 files changed, 161 insertions(+), 99 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/button/action-button-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/button/action-button-widget-settings.component.ts diff --git a/application/src/main/data/json/system/widget_types/action_button.json b/application/src/main/data/json/system/widget_types/action_button.json index 3ad518ce23..e6ffa8084e 100644 --- a/application/src/main/data/json/system/widget_types/action_button.json +++ b/application/src/main/data/json/system/widget_types/action_button.json @@ -11,10 +11,10 @@ "resources": [], "templateHtml": "\n", "templateCss": "#container tb-markdown-widget {\n height: 100%;\n display: block;\n}\n\n#container tb-markdown-widget .tb-markdown-view {\n height: 100%;\n overflow: auto;\n}\n", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.actionSources = function() {\n return {\n 'click': {\n name: 'widget-action.click',\n multiple: false\n }\n };\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n datasourcesOptional: true,\n maxDatasources: 1,\n maxDataKeys: 0,\n singleEntity: true,\n previewWidth: '200px',\n previewHeight: '80px',\n embedTitlePanel: true,\n overflowVisible: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.actionSources = function() {\n return {\n 'click': {\n name: 'widget-action.click',\n multiple: false\n }\n };\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n datasourcesOptional: true,\n maxDatasources: 1,\n maxDataKeys: 0,\n singleEntity: true,\n previewWidth: '200px',\n previewHeight: '80px',\n embedTitlePanel: true,\n overflowVisible: true,\n hideDataSettings: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n", "settingsSchema": "", "dataKeySettingsSchema": "", - "settingsDirective": "", + "settingsDirective": "tb-action-button-widget-settings", "hasBasicMode": true, "basicModeDirective": "tb-action-button-basic-config", "defaultConfig": "{\"datasources\":[],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#FFFFFF01\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Action button\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":false,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false,\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"borderRadius\":\"4px\",\"configMode\":\"basic\"}" diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts index 8557c9cbd3..26dc76866a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts @@ -31,7 +31,6 @@ import { DataToValueSettings, DataToValueType, GetAttributeValueSettings, - GetTelemetryValueSettings, GetValueAction, GetValueSettings, RpcSettings, @@ -405,7 +404,7 @@ export class ExecuteRpcValueGetter extends ValueGetter { } } -export abstract class TelemetryValueGetter extends ValueGetter { +export abstract class TelemetryValueGetter extends ValueGetter { protected targetEntityId: EntityId; private telemetrySubscriber: TelemetrySubscriber; @@ -428,11 +427,7 @@ export abstract class TelemetryValueGetter err); } - if (this.getTelemetryValueSettings().subscribeForUpdates) { - return this.subscribeForTelemetryValue(); - } else { - return this.doGetTelemetryValue(); - } + return this.subscribeForTelemetryValue(); } else { return of(null); } @@ -464,8 +459,6 @@ export abstract class TelemetryValueGetter; - destroy() { if (this.telemetrySubscriber) { this.telemetrySubscriber.unsubscribe(); @@ -492,17 +485,9 @@ export class AttributeValueGetter extends TelemetryValueGetter { - const getAttributeValueSettings = this.getTelemetryValueSettings(); - return this.ctx.attributeService.getEntityAttributes(this.targetEntityId, - getAttributeValueSettings.scope, [getAttributeValueSettings.key], {ignoreLoading: true, ignoreErrors: true}).pipe( - map((data) => data.find(attr => attr.key === getAttributeValueSettings.key)?.value) - ); - } - } -export class TimeSeriesValueGetter extends TelemetryValueGetter { +export class TimeSeriesValueGetter extends TelemetryValueGetter { constructor(protected ctx: WidgetContext, protected settings: GetValueSettings, @@ -511,28 +496,9 @@ export class TimeSeriesValueGetter extends TelemetryValueGetter { - const getTelemetryValueSettings = this.getTelemetryValueSettings(); - return this.ctx.attributeService.getEntityTimeseriesLatest(this.targetEntityId, - [getTelemetryValueSettings.key], true, {ignoreLoading: true, ignoreErrors: true}) - .pipe( - map((data) => { - let value: any = null; - if (data[getTelemetryValueSettings.key]) { - const dataSet = data[getTelemetryValueSettings.key]; - if (dataSet.length) { - value = dataSet[0].value; - } - } - return value; - }) - ); - } - } export class ExecuteRpcValueSetter extends ValueSetter { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.models.ts index f86ef73439..4b3fafa55c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.models.ts @@ -33,12 +33,10 @@ export const actionButtonDefaultSettings: ActionButtonWidgetSettings = { defaultValue: false, getAttribute: { key: 'state', - scope: null, - subscribeForUpdates: false + scope: null }, getTimeSeries: { - key: 'state', - subscribeForUpdates: false + key: 'state' }, dataToValue: { type: DataToValueType.NONE, @@ -51,12 +49,10 @@ export const actionButtonDefaultSettings: ActionButtonWidgetSettings = { defaultValue: false, getAttribute: { key: 'state', - scope: null, - subscribeForUpdates: false + scope: null }, getTimeSeries: { - key: 'state', - subscribeForUpdates: false + key: 'state' }, dataToValue: { type: DataToValueType.NONE, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts index 23510e174a..4f0401b3bd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts @@ -93,12 +93,10 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = { }, getAttribute: { key: 'state', - scope: null, - subscribeForUpdates: false + scope: null }, getTimeSeries: { - key: 'state', - subscribeForUpdates: false + key: 'state' }, dataToValue: { type: DataToValueType.NONE, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/action-button-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/action-button-widget-settings.component.html new file mode 100644 index 0000000000..265a9c2501 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/action-button-widget-settings.component.html @@ -0,0 +1,51 @@ + + +
+
widgets.action-button.behavior
+
+
widgets.button-state.activated-state
+ +
+
+
widgets.button-state.disabled-state
+ +
+
+
+
widget-config.appearance
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/action-button-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/action-button-widget-settings.component.ts new file mode 100644 index 0000000000..26f785fac9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/action-button-widget-settings.component.ts @@ -0,0 +1,70 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { TargetDevice, WidgetSettings, WidgetSettingsComponent, widgetType } from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ValueType } from '@shared/models/constants'; +import { getTargetDeviceFromDatasources } from '@shared/models/widget-settings.models'; +import { actionButtonDefaultSettings } from '@home/components/widget/lib/button/action-button-widget.models'; + +@Component({ + selector: 'tb-action-button-widget-settings', + templateUrl: './action-button-widget-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class ActionButtonWidgetSettingsComponent extends WidgetSettingsComponent { + + get targetDevice(): TargetDevice { + const datasources = this.widgetConfig?.config?.datasources; + return getTargetDeviceFromDatasources(datasources); + } + + get widgetType(): widgetType { + return this.widgetConfig?.widgetType; + } + get borderRadius(): string { + return this.widgetConfig?.config?.borderRadius; + } + + valueType = ValueType; + + actionButtonWidgetSettingsForm: UntypedFormGroup; + + constructor(protected store: Store, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.actionButtonWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return {...actionButtonDefaultSettings}; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.actionButtonWidgetSettingsForm = this.fb.group({ + activatedState: [settings.activatedState, []], + disabledState: [settings.disabledState, []], + + appearance: [settings.appearance, []] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html index 9684be6fd3..faad403e9b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html @@ -84,11 +84,6 @@ [attributeScope]="getValueSettingsFormGroup.get('getAttribute').get('scope').value"> -
- - {{ 'widgets.value-action.subscribe-for-updates' | translate }} - -
@@ -106,11 +101,6 @@ [keyType]="dataKeyType.timeseries"> -
- - {{ 'widgets.value-action.subscribe-for-updates' | translate }} - -
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts index 4b9eb98ae2..f9202b982f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts @@ -24,7 +24,7 @@ import { merge } from 'rxjs'; import { DataToValueType, GetValueAction, - getValueActions, getValueActionsByWidgetType, + getValueActionsByWidgetType, getValueActionTranslations, GetValueSettings } from '@shared/models/action-widget-settings.models'; @@ -117,12 +117,10 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen }), getAttribute: this.fb.group({ scope: [this.getValueSettings?.getAttribute?.scope, []], - key: [this.getValueSettings?.getAttribute?.key, [Validators.required]], - subscribeForUpdates: [this.getValueSettings?.getAttribute?.subscribeForUpdates, []] + key: [this.getValueSettings?.getAttribute?.key, [Validators.required]] }), getTimeSeries: this.fb.group({ - key: [this.getValueSettings?.getTimeSeries?.key, [Validators.required]], - subscribeForUpdates: [this.getValueSettings?.getTimeSeries?.subscribeForUpdates, []] + key: [this.getValueSettings?.getTimeSeries?.key, [Validators.required]] }), dataToValue: this.fb.group({ type: [this.getValueSettings?.dataToValue?.type, [Validators.required]], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/led-indicator-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/led-indicator-widget-settings.component.ts index 22af44f2b4..70f4846b78 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/led-indicator-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/led-indicator-widget-settings.component.ts @@ -32,7 +32,7 @@ export class LedIndicatorWidgetSettingsComponent extends WidgetSettingsComponent functionScopeVariables = this.widgetService.getWidgetScopeVariables(); get targetDevice(): TargetDevice { - return this.widget?.config?.targetDevice; + return this.widgetConfig?.config?.targetDevice; } dataKeyType = DataKeyType; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts index 8bf58a9b22..a8d4d70015 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts @@ -37,7 +37,7 @@ export class RoundSwitchWidgetSettingsComponent extends WidgetSettingsComponent } get targetDevice(): TargetDevice { - return this.widget?.config?.targetDevice; + return this.widgetConfig?.config?.targetDevice; } protected settingsForm(): UntypedFormGroup { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.ts index f4bd08a20c..8c7282e32b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.ts @@ -20,7 +20,8 @@ import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { - singleSwitchDefaultSettings, singleSwitchLayoutImages, + singleSwitchDefaultSettings, + singleSwitchLayoutImages, singleSwitchLayouts, singleSwitchLayoutTranslations } from '@home/components/widget/lib/rpc/single-switch-widget.models'; @@ -34,11 +35,11 @@ import { ValueType } from '@shared/models/constants'; export class SingleSwitchWidgetSettingsComponent extends WidgetSettingsComponent { get targetDevice(): TargetDevice { - return this.widget?.config?.targetDevice; + return this.widgetConfig?.config?.targetDevice; } get widgetType(): widgetType { - return this.widget?.type; + return this.widgetConfig?.widgetType; } singleSwitchLayouts = singleSwitchLayouts; @@ -156,6 +157,4 @@ export class SingleSwitchWidgetSettingsComponent extends WidgetSettingsComponent this.singleSwitchWidgetSettingsForm.get('offLabelColor').disable(); } } - - protected readonly ValueType = ValueType; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts index d14d0fd594..43098a011b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts @@ -37,7 +37,7 @@ export class SlideToggleWidgetSettingsComponent extends WidgetSettingsComponent } get targetDevice(): TargetDevice { - return this.widget?.config?.targetDevice; + return this.widgetConfig?.config?.targetDevice; } protected settingsForm(): UntypedFormGroup { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts index 197fc2d229..e74275b659 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts @@ -37,7 +37,7 @@ export class SwitchControlWidgetSettingsComponent extends WidgetSettingsComponen } get targetDevice(): TargetDevice { - return this.widget?.config?.targetDevice; + return this.widgetConfig?.config?.targetDevice; } protected settingsForm(): UntypedFormGroup { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index 30c1b748de..607cb91318 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -15,7 +15,9 @@ /// import { NgModule, Type } from '@angular/core'; -import { QrCodeWidgetSettingsComponent } from '@home/components/widget/lib/settings/cards/qrcode-widget-settings.component'; +import { + QrCodeWidgetSettingsComponent +} from '@home/components/widget/lib/settings/cards/qrcode-widget-settings.component'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module'; @@ -33,7 +35,9 @@ import { MarkdownWidgetSettingsComponent } from '@home/components/widget/lib/settings/cards/markdown-widget-settings.component'; import { LabelWidgetLabelComponent } from '@home/components/widget/lib/settings/cards/label-widget-label.component'; -import { LabelWidgetSettingsComponent } from '@home/components/widget/lib/settings/cards/label-widget-settings.component'; +import { + LabelWidgetSettingsComponent +} from '@home/components/widget/lib/settings/cards/label-widget-settings.component'; import { SimpleCardWidgetSettingsComponent } from '@home/components/widget/lib/settings/cards/simple-card-widget-settings.component'; @@ -311,6 +315,9 @@ import { import { SingleSwitchWidgetSettingsComponent } from '@home/components/widget/lib/settings/control/single-switch-widget-settings.component'; +import { + ActionButtonWidgetSettingsComponent +} from '@home/components/widget/lib/settings/button/action-button-widget-settings.component'; @NgModule({ declarations: [ @@ -424,7 +431,8 @@ import { DoughnutWidgetSettingsComponent, RangeChartWidgetSettingsComponent, BarChartWithLabelsWidgetSettingsComponent, - SingleSwitchWidgetSettingsComponent + SingleSwitchWidgetSettingsComponent, + ActionButtonWidgetSettingsComponent ], imports: [ CommonModule, @@ -543,7 +551,8 @@ import { DoughnutWidgetSettingsComponent, RangeChartWidgetSettingsComponent, BarChartWithLabelsWidgetSettingsComponent, - SingleSwitchWidgetSettingsComponent + SingleSwitchWidgetSettingsComponent, + ActionButtonWidgetSettingsComponent ] }) export class WidgetSettingsModule { @@ -629,5 +638,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type extends ValueActionSettings { defaultValue: V; executeRpc?: RpcSettings; getAttribute: GetAttributeValueSettings; - getTimeSeries: GetTelemetryValueSettings; + getTimeSeries: TelemetryValueSettings; dataToValue: DataToValueSettings; } @@ -133,13 +129,3 @@ export interface SetValueSettings extends ValueActionSettings { putTimeSeries: TelemetryValueSettings; valueToData: ValueToDataSettings; } - -/*export interface RpcStateBehaviourSettings { - initialState: RpcInitialStateSettings; - updateStateByValue: (value: V) => RpcUpdateStateSettings; -} - -export interface RpcStateWidgetSettings { - initialState: RpcInitialStateSettings; - background: BackgroundSettings; -}*/ diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index fa9945dd50..c9108e9bac 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6561,8 +6561,6 @@ "attribute-key-required": "Attribute key is required.", "time-series-key": "Time-series key", "time-series-key-required": "Time-series key is required.", - "subscribe-for-updates": "Subscribe for updates", - "subscribe-for-updates-hint": "Subscribe for updates", "action-result-converter": "Action result converter", "converter-none": "None", "converter-function": "Function", From ba600a8bd88d2ad3fdef71a0065497573909f781 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 2 Feb 2024 18:27:54 +0200 Subject: [PATCH 08/11] UI: Widget button color calculations improvements. Improve color picker panel overflow. --- .../widget/config/data-keys.component.ts | 2 +- .../button/widget-button.component.scss | 8 +++---- .../components/button/widget-button.models.ts | 22 ++++++++++++++----- .../components/color-input.component.ts | 2 +- .../color-picker/color-picker.component.scss | 2 ++ 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts index 63647fc604..800c831b67 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts @@ -521,7 +521,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange this.popoverService.hidePopover(trigger); } else { const colorPickerPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, ColorPickerPanelComponent, 'left', true, null, + this.viewContainerRef, ColorPickerPanelComponent, ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], true, null, { color: key.color }, diff --git a/ui-ngx/src/app/shared/components/button/widget-button.component.scss b/ui-ngx/src/app/shared/components/button/widget-button.component.scss index 06c875de7f..546a3bed19 100644 --- a/ui-ngx/src/app/shared/components/button/widget-button.component.scss +++ b/ui-ngx/src/app/shared/components/button/widget-button.component.scss @@ -28,19 +28,19 @@ $boxShadowColorEnabled: var(--tb-widget-button-box-shadow-color-enabled, $defaul $mainColorHovered: var(--tb-widget-button-main-color-hovered, $defaultMainColor); $backgroundColorHovered: var(--tb-widget-button-background-color-hovered, $defaultBackgroundColor); $boxShadowColorHovered: var(--tb-widget-button-box-shadow-color-hovered, $defaultBoxShadowColor); -$mainColorHoveredFilled: var(--tb-widget-button-main-color-hovered-filled, #263BD7); // main.darken(6) +$mainColorHoveredFilled: var(--tb-widget-button-main-color-hovered-filled, #3347db); // main.darken(6) $mainColorPressed: var(--tb-widget-button-main-color-pressed, $defaultMainColor); $backgroundColorPressed: var(--tb-widget-button-background-color-pressed, $defaultBackgroundColor); $boxShadowColorPressed: var(--tb-widget-button-box-shadow-color-pressed, $defaultBoxShadowColor); -$mainColorPressedFilled: var(--tb-widget-button-main-color-pressed-filled, #2234BD); // main.darken(12) +$mainColorPressedFilled: var(--tb-widget-button-main-color-pressed-filled, #273cd9); // main.darken(12) $mainColorPressedRipple: var(--tb-widget-button-main-color-pressed-ripple, rgba(63, 82, 221, 0.1)); // Alpha(Main, 0.1) -$mainColorPressedRippleFilled: var(--tb-widget-button-main-color-pressed-ripple-filled, #1D2DA3); // main.darken(18) +$mainColorPressedRippleFilled: var(--tb-widget-button-main-color-pressed-ripple-filled, #2439cd); // main.darken(18) $mainColorActivated: var(--tb-widget-button-main-color-activated, $defaultMainColor); $backgroundColorActivated: var(--tb-widget-button-background-color-activated, $defaultBackgroundColor); $boxShadowColorActivated: var(--tb-widget-button-box-shadow-color-activated, $defaultBoxShadowColor); -$mainColorActivatedFilled: var(--tb-widget-button-main-color-activated-filled, #2234BD); // main.darken(12) +$mainColorActivatedFilled: var(--tb-widget-button-main-color-activated-filled, #273cd9); // main.darken(12) $mainColorDisabled: var(--tb-widget-button-main-color-disabled, $defaultMainColorDisabled); $backgroundColorDisabled: var(--tb-widget-button-background-color-disabled, $defaultBackgroundColorDisabled); diff --git a/ui-ngx/src/app/shared/components/button/widget-button.models.ts b/ui-ngx/src/app/shared/components/button/widget-button.models.ts index 616af28588..34f13864a9 100644 --- a/ui-ngx/src/app/shared/components/button/widget-button.models.ts +++ b/ui-ngx/src/app/shared/components/button/widget-button.models.ts @@ -20,6 +20,11 @@ import tinycolor from 'tinycolor2'; const defaultMainColor = '#3F52DD'; const defaultBackgroundColor = '#FFFFFF'; +const hoveredFilledDarkenAmount = 6; +const pressedFilledDarkenAmount = 12; +const activatedFilledDarkenAmount = 12; +const pressedRippleFilledDarkenAmount = 18; + export const defaultMainColorDisabled = 'rgba(0, 0, 0, 0.38)'; export const defaultBackgroundColorDisabled = 'rgba(0, 0, 0, 0.03)'; @@ -181,7 +186,7 @@ class HoveredButtonStateCssGenerator extends ButtonStateCssGenerator { } protected generateAdditionalStateCss(mainColor: string): string { - const mainColorHoveredFilled = darkenColor(mainColor, 6); + const mainColorHoveredFilled = darkenColor(mainColor, hoveredFilledDarkenAmount); return `--tb-widget-button-main-color-hovered-filled: ${mainColorHoveredFilled};`; } } @@ -193,10 +198,10 @@ class PressedButtonStateCssGenerator extends ButtonStateCssGenerator { } protected generateAdditionalStateCss(mainColor: string): string { - const mainColorPressedFilled = darkenColor(mainColor, 12); + const mainColorPressedFilled = darkenColor(mainColor, pressedFilledDarkenAmount); const mainColorInstance = tinycolor(mainColor); const mainColorPressedRipple = mainColorInstance.setAlpha(mainColorInstance.getAlpha() * 0.1).toRgbString(); - const mainColorPressedRippleFilled = darkenColor(mainColor, 18); + const mainColorPressedRippleFilled = darkenColor(mainColor, pressedRippleFilledDarkenAmount); return `--tb-widget-button-main-color-pressed-filled: ${mainColorPressedFilled};\n`+ `--tb-widget-button-main-color-pressed-ripple: ${mainColorPressedRipple};\n`+ `--tb-widget-button-main-color-pressed-ripple-filled: ${mainColorPressedRippleFilled};`; @@ -210,7 +215,7 @@ class ActivatedButtonStateCssGenerator extends ButtonStateCssGenerator { } protected generateAdditionalStateCss(mainColor: string): string { - const mainColorActivatedFilled = darkenColor(mainColor, 12); + const mainColorActivatedFilled = darkenColor(mainColor, activatedFilledDarkenAmount); return `--tb-widget-button-main-color-activated-filled: ${mainColorActivatedFilled};`; } } @@ -255,5 +260,12 @@ export const generateWidgetButtonAppearanceCss = (appearance: WidgetButtonAppear const darkenColor = (inputColor: string, amount: number): string => { const input = tinycolor(inputColor); - return input.darken(amount).toRgbString(); + const brightness = input.getBrightness() / 255; + let ratio: number; + if (brightness >= 0.4 && brightness <= 0.5) { + ratio = brightness + 0.2; + } else { + ratio = Math.max(0.1, Math.log10(brightness * 8)); + } + return input.darken(ratio * amount).toRgbString(); }; diff --git a/ui-ngx/src/app/shared/components/color-input.component.ts b/ui-ngx/src/app/shared/components/color-input.component.ts index 171e8bf7d8..ad772ba861 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.ts +++ b/ui-ngx/src/app/shared/components/color-input.component.ts @@ -189,7 +189,7 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro this.popoverService.hidePopover(trigger); } else { const colorPickerPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, ColorPickerPanelComponent, 'left', true, null, + this.viewContainerRef, ColorPickerPanelComponent, ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], true, null, { color: this.colorFormGroup.get('color').value, colorClearButton: this.colorClearButton diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss index c55222a7d2..9aca580994 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss @@ -18,9 +18,11 @@ display: flex; flex-direction: column; gap: 32px; + overflow: auto; .saturation-component { height: 238px; + min-height: 80px; border-radius: 8px; } From 8262be6e6451673e4ebd35988585193a82dcb871 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 5 Feb 2024 15:10:37 +0200 Subject: [PATCH 09/11] UI: Single switch widget improvements: Implement disabled state. Improve auto-scale. --- .../single-switch-basic-config.component.html | 11 ++++ .../single-switch-basic-config.component.ts | 2 + .../rpc/single-switch-widget.component.html | 6 +-- .../rpc/single-switch-widget.component.scss | 8 +-- .../lib/rpc/single-switch-widget.component.ts | 54 +++++++++++++++++-- .../lib/rpc/single-switch-widget.models.ts | 17 ++++++ ...ngle-switch-widget-settings.component.html | 11 ++++ ...single-switch-widget-settings.component.ts | 3 +- .../assets/locale/locale.constant-en_US.json | 5 +- 9 files changed, 105 insertions(+), 12 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html index 4ac5934e93..df495e32df 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html @@ -52,6 +52,17 @@ [widgetType]="widgetType" formControlName="offUpdateState"> +
+
widgets.rpc-state.disabled-state
+ +
widget-config.appearance
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.ts index 82c2766a23..75c93bc852 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.ts @@ -70,6 +70,7 @@ export class SingleSwitchBasicConfigComponent extends BasicWidgetConfigComponent initialState: [settings.initialState, []], onUpdateState: [settings.onUpdateState, []], offUpdateState: [settings.offUpdateState, []], + disabledState: [settings.disabledState, []], layout: [settings.layout, []], autoScale: [settings.autoScale, []], @@ -120,6 +121,7 @@ export class SingleSwitchBasicConfigComponent extends BasicWidgetConfigComponent this.widgetConfig.config.settings.initialState = config.initialState; this.widgetConfig.config.settings.onUpdateState = config.onUpdateState; this.widgetConfig.config.settings.offUpdateState = config.offUpdateState; + this.widgetConfig.config.settings.disabledState = config.disabledState; this.widgetConfig.config.settings.layout = config.layout; this.widgetConfig.config.settings.autoScale = config.autoScale; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html index 5f085ff7b5..879902a3ef 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html @@ -15,8 +15,8 @@ limitations under the License. --> -
-
+
+
@@ -27,7 +27,7 @@
{{ offLabel }}
- +
{{ onLabel }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.scss index 92c8f87f09..bb5648a16d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.scss @@ -29,15 +29,15 @@ $switchColorDisabled: var(--tb-single-switch-color-disabled, #D5D7E5); align-items: center; justify-content: center; padding: 18px 24px; + &.auto-scale { + padding: 0; + } > div:not(.tb-single-switch-overlay), > tb-icon { z-index: 1; } .tb-single-switch-overlay { position: absolute; - top: 12px; - left: 12px; - bottom: 12px; - right: 12px; + inset: 12px; } .tb-single-switch-progress { position: absolute; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.ts index 1a7274c5b6..1103afde39 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.ts @@ -45,6 +45,7 @@ import { DomSanitizer } from '@angular/platform-browser'; import { ValueType } from '@shared/models/constants'; import { UtilsService } from '@core/services/utils.service'; +const initialSwitchHeight = 60; const horizontalLayoutPadding = 48; const verticalLayoutPadding = 36; @@ -73,8 +74,10 @@ export class SingleSwitchWidgetComponent extends backgroundStyle$: Observable; overlayStyle: ComponentStyle = {}; + overlayInset = '12px'; value = false; + disabled = false; layout: SingleSwitchLayout; @@ -159,6 +162,12 @@ export class SingleSwitchWidgetComponent extends next: (value) => this.onValue(value) }); + const disabledStateSettings = + {...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.disabled-state')}; + this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, { + next: (value) => this.onDisabled(value) + }); + const onUpdateStateSettings = {...this.settings.onUpdateState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.turn-on')}; this.onValueSetter = this.createValueSetter(onUpdateStateSettings); @@ -216,20 +225,59 @@ export class SingleSwitchWidgetComponent extends this.cd.markForCheck(); } + private onDisabled(value: boolean): void { + this.disabled = !!value; + if (this.disabled) { + if (this.showLabel) { + this.labelStyle = {...this.labelStyle, color: 'rgba(0, 0, 0, 0.38)'}; + } + if (this.showIcon) { + this.iconStyle = {...this.iconStyle, color: 'rgba(0, 0, 0, 0.38)'}; + } + if (this.showOnLabel) { + this.onLabelStyle = {...this.onLabelStyle, color: 'rgba(0, 0, 0, 0.38)'}; + } + if (this.showOffLabel) { + this.offLabelStyle = {...this.offLabelStyle, color: 'rgba(0, 0, 0, 0.38)'}; + } + } else { + if (this.showLabel) { + this.labelStyle = {...this.labelStyle, color: this.settings.labelColor}; + } + if (this.showIcon) { + this.iconStyle = {...this.iconStyle, color: this.settings.iconColor}; + } + if (this.showOnLabel) { + this.onLabelStyle = {...this.onLabelStyle, color: this.settings.onLabelColor}; + } + if (this.showOffLabel) { + this.offLabelStyle = {...this.offLabelStyle, color: this.settings.offLabelColor}; + } + } + this.cd.markForCheck(); + } + private onResize() { - const panelWidth = this.singleSwitchPanel.nativeElement.getBoundingClientRect().width - horizontalLayoutPadding; - const panelHeight = this.singleSwitchPanel.nativeElement.getBoundingClientRect().height - verticalLayoutPadding; + const height = this.singleSwitchPanel.nativeElement.getBoundingClientRect().height; + const switchScale = height / initialSwitchHeight; + const paddingScale = Math.min(switchScale, 1); + const panelWidth = this.singleSwitchPanel.nativeElement.getBoundingClientRect().width - (horizontalLayoutPadding * paddingScale); + const panelHeight = this.singleSwitchPanel.nativeElement.getBoundingClientRect().height - (verticalLayoutPadding * paddingScale); this.renderer.setStyle(this.singleSwitchContent.nativeElement, 'transform', `scale(1)`); + this.renderer.setStyle(this.singleSwitchContent.nativeElement, 'width', 'auto'); let contentWidth = this.singleSwitchToggleRow.nativeElement.getBoundingClientRect().width; let contentHeight = this.singleSwitchToggleRow.nativeElement.getBoundingClientRect().height; if (this.showIcon || this.showLabel) { contentWidth += (8 + this.singleSwitchLabelRow.nativeElement.getBoundingClientRect().width); contentHeight = Math.max(contentHeight, this.singleSwitchLabelRow.nativeElement.getBoundingClientRect().height); } - const scale = Math.min(panelWidth / contentWidth, panelHeight / contentHeight); + const maxScale = Math.max(1, switchScale); + const scale = Math.min(Math.min(panelWidth / contentWidth, panelHeight / contentHeight), maxScale); const width = panelWidth / scale; this.renderer.setStyle(this.singleSwitchContent.nativeElement, 'width', width + 'px'); this.renderer.setStyle(this.singleSwitchContent.nativeElement, 'transform', `scale(${scale})`); + this.overlayInset = (Math.floor(12 * paddingScale * 100) / 100) + 'px'; + this.cd.markForCheck(); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts index 4f0401b3bd..7897574cac 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts @@ -51,6 +51,7 @@ export const singleSwitchLayoutImages = new Map( export interface SingleSwitchWidgetSettings { initialState: GetValueSettings; + disabledState: GetValueSettings; onUpdateState: SetValueSettings; offUpdateState: SetValueSettings; layout: SingleSwitchLayout; @@ -104,6 +105,22 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = { dataToValueFunction: '/* Should return boolean value */\nreturn data;' } }, + disabledState: { + action: GetValueAction.DO_NOTHING, + defaultValue: false, + getAttribute: { + key: 'state', + scope: null + }, + getTimeSeries: { + key: 'state' + }, + dataToValue: { + type: DataToValueType.NONE, + compareToValue: true, + dataToValueFunction: '/* Should return boolean value */\nreturn data;' + } + }, onUpdateState: { action: SetValueAction.EXECUTE_RPC, executeRpc: { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html index 9bdca64bbd..b498116317 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html @@ -51,6 +51,17 @@ [widgetType]="widgetType" formControlName="offUpdateState">
+
+
widgets.rpc-state.disabled-state
+ +
widget-config.card-style
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.ts index 8c7282e32b..a1a3e951f2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.ts @@ -69,6 +69,7 @@ export class SingleSwitchWidgetSettingsComponent extends WidgetSettingsComponent initialState: [settings.initialState, []], onUpdateState: [settings.onUpdateState, []], offUpdateState: [settings.offUpdateState, []], + disabledState: [settings.disabledState, []], layout: [settings.layout, []], autoScale: [settings.autoScale, []], @@ -109,7 +110,7 @@ export class SingleSwitchWidgetSettingsComponent extends WidgetSettingsComponent return ['showLabel', 'showIcon', 'showOnLabel', 'showOffLabel']; } - protected updateValidators(emitEvent: boolean): void { + protected updateValidators(_emitEvent: boolean): void { const showLabel: boolean = this.singleSwitchWidgetSettingsForm.get('showLabel').value; const showIcon: boolean = this.singleSwitchWidgetSettingsForm.get('showIcon').value; const showOnLabel: boolean = this.singleSwitchWidgetSettingsForm.get('showOnLabel').value; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index c9108e9bac..ae3bc28c31 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6522,12 +6522,15 @@ "rpc-state": { "initial-state": "Initial state", "initial-state-hint": "Action to get the initial value of the component.", + "disabled-state": "Disabled state", + "disabled-state-hint": "Condition under which the component is disabled.", "turn-on": "Turn 'On'", "turn-on-hint": "Action performed to turn ON the component.", "turn-off": "Turn 'Off'", "turn-off-hint": "Action performed to turn OFF the component.", "on": "On", - "off": "Off" + "off": "Off", + "disabled": "Disabled" }, "value-action": { "do-nothing": "Do nothing", From 599911546b422355d92de1b2f03e4f7a35e2dafe Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 5 Feb 2024 15:23:30 +0200 Subject: [PATCH 10/11] UI: Widget button style fixes. --- .../button/widget-button-custom-style-panel.component.ts | 2 +- .../common/button/widget-button-custom-style.component.ts | 3 ++- .../app/shared/components/button/widget-button.component.scss | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.ts index 419ce7e4d4..e6af6f2ab9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.ts @@ -164,7 +164,7 @@ export class WidgetButtonCustomStylePanelComponent extends PageComponent impleme } private updatePreviewAppearance() { - this.previewAppearance = {...this.appearance}; + this.previewAppearance = deepClone(this.appearance); this.previewAppearance.customStyle[this.state] = this.customStyleFormGroup.value; this.cd.markForCheck(); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts index b9a1605b19..a67f9a45ab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts @@ -37,6 +37,7 @@ import { MatIconButton } from '@angular/material/button'; import { WidgetButtonCustomStylePanelComponent } from '@home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component'; +import { deepClone } from '@core/utils'; @Component({ selector: 'tb-widget-button-custom-style', @@ -147,7 +148,7 @@ export class WidgetButtonCustomStyleComponent implements OnInit, OnChanges, Cont } private updatePreviewAppearance() { - this.previewAppearance = {...this.appearance}; + this.previewAppearance = deepClone(this.appearance); if (this.modelValue) { this.previewAppearance.customStyle[this.state] = this.modelValue; } diff --git a/ui-ngx/src/app/shared/components/button/widget-button.component.scss b/ui-ngx/src/app/shared/components/button/widget-button.component.scss index 546a3bed19..a0abc95731 100644 --- a/ui-ngx/src/app/shared/components/button/widget-button.component.scss +++ b/ui-ngx/src/app/shared/components/button/widget-button.component.scss @@ -44,7 +44,7 @@ $mainColorActivatedFilled: var(--tb-widget-button-main-color-activated-filled, # $mainColorDisabled: var(--tb-widget-button-main-color-disabled, $defaultMainColorDisabled); $backgroundColorDisabled: var(--tb-widget-button-background-color-disabled, $defaultBackgroundColorDisabled); -$boxShadowColorDisabled: var(--tb-widget-button-box-shadow-color-activated, $defaultBoxShadowColor); +$boxShadowColorDisabled: var(--tb-widget-button-box-shadow-color-disabled, $defaultBoxShadowColor); @mixin _tb-widget-button-styles($main, $background, $boxShadow) { From a8a10fefda0043e789e542c29b81b3bda6e244d9 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 5 Feb 2024 16:45:26 +0200 Subject: [PATCH 11/11] UI: Update buttons widget bundle image. Update single switch widget preview image. --- .../src/main/data/json/system/widget_bundles/buttons.json | 2 +- .../src/main/data/json/system/widget_types/single_switch.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/buttons.json b/application/src/main/data/json/system/widget_bundles/buttons.json index 046e16dd7e..b5f728dc25 100644 --- a/application/src/main/data/json/system/widget_bundles/buttons.json +++ b/application/src/main/data/json/system/widget_bundles/buttons.json @@ -2,7 +2,7 @@ "widgetsBundle": { "alias": "buttons", "title": "Buttons", - "image": null, + "image": "tb-image:YnV0dG9ucy5zdmc=:IkJ1dHRvbnMiIHN5c3RlbSBidW5kbGUgaW1hZ2U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9IjAuNzUiIHk9IjEyLjc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIGZpbGw9IndoaXRlIi8+CjxyZWN0IHg9IjAuNzUiIHk9IjEyLjc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIHN0cm9rZT0iIzNGNTJERCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KPHBhdGggZD0iTTYyLjE2NjMgNTEuMzMzM1Y0NC4zMzMzSDY2LjgzM1Y1MS4zMzMzSDcyLjY2NjNWNDJINzYuMTY2M0w2NC40OTk3IDMxLjVMNTIuODMzIDQySDU2LjMzM1Y1MS4zMzMzSDYyLjE2NjNaIiBmaWxsPSIjM0Y1MkREIi8+CjxwYXRoIGQ9Ik05MC4xOTUzIDQyLjkzMTZIODYuMjFMODYuMTg4NSA0MC45NjU4SDg5LjY2ODlDOTAuMjU2MiA0MC45NjU4IDkwLjc1MzkgNDAuODc5OSA5MS4xNjIxIDQwLjcwOEM5MS41Nzc1IDQwLjUyOSA5MS44OTI2IDQwLjI3NDcgOTIuMTA3NCAzOS45NDUzQzkyLjMyMjMgMzkuNjA4NyA5Mi40Mjk3IDM5LjIwNDEgOTIuNDI5NyAzOC43MzE0QzkyLjQyOTcgMzguMjA4NyA5Mi4zMjk0IDM3Ljc4MjYgOTIuMTI4OSAzNy40NTMxQzkxLjkyODQgMzcuMTIzNyA5MS42MjA0IDM2Ljg4MzggOTEuMjA1MSAzNi43MzM0QzkwLjc5NjkgMzYuNTgzIDkwLjI3NDEgMzYuNTA3OCA4OS42MzY3IDM2LjUwNzhIODcuMDI2NFY1MEg4NC4zMzAxVjM0LjM1OTRIODkuNjM2N0M5MC40OTYxIDM0LjM1OTQgOTEuMjYyNCAzNC40NDE3IDkxLjkzNTUgMzQuNjA2NEM5Mi42MTU5IDM0Ljc3MTIgOTMuMTkyNCAzNS4wMjkgOTMuNjY1IDM1LjM3OTlDOTQuMTQ0OSAzNS43MjM2IDk0LjUwNjUgMzYuMTYwNSA5NC43NSAzNi42OTA0Qzk1LjAwMDcgMzcuMjIwNCA5NS4xMjYgMzcuODUwNiA5NS4xMjYgMzguNTgxMUM5NS4xMjYgMzkuMjI1NiA5NC45NzIgMzkuODE2NCA5NC42NjQxIDQwLjM1MzVDOTQuMzU2MSA0MC44ODM1IDkzLjkwMTQgNDEuMzE2NyA5My4yOTk4IDQxLjY1MzNDOTIuNjk4MiA0MS45ODk5IDkxLjk0OTkgNDIuMTkwNCA5MS4wNTQ3IDQyLjI1NDlMOTAuMTk1MyA0Mi45MzE2Wk05MC4wNzcxIDUwSDg1LjM2MTNMODYuNTc1MiA0Ny44NjIzSDkwLjA3NzFDOTAuNjg1OSA0Ny44NjIzIDkxLjE5NDMgNDcuNzYyIDkxLjYwMjUgNDcuNTYxNUM5Mi4wMTA3IDQ3LjM1MzggOTIuMzE1MSA0Ny4wNzEgOTIuNTE1NiA0Ni43MTI5QzkyLjcyMzMgNDYuMzQ3NyA5Mi44MjcxIDQ1LjkyMTUgOTIuODI3MSA0NS40MzQ2QzkyLjgyNzEgNDQuOTI2MSA5Mi43Mzc2IDQ0LjQ4NTcgOTIuNTU4NiA0NC4xMTMzQzkyLjM3OTYgNDMuNzMzNyA5Mi4wOTY3IDQzLjQ0MzcgOTEuNzEgNDMuMjQzMkM5MS4zMjMyIDQzLjAzNTUgOTAuODE4NCA0Mi45MzE2IDkwLjE5NTMgNDIuOTMxNkg4Ny4xNjZMODcuMTg3NSA0MC45NjU4SDkxLjEyOTlMOTEuNzQyMiA0MS43MDdDOTIuNjAxNiA0MS43MzU3IDkzLjMwNyA0MS45MjU1IDkzLjg1ODQgNDIuMjc2NEM5NC40MTcgNDIuNjI3MyA5NC44MzI0IDQzLjA4MiA5NS4xMDQ1IDQzLjY0MDZDOTUuMzc2NiA0NC4xOTkyIDk1LjUxMjcgNDQuODAwOCA5NS41MTI3IDQ1LjQ0NTNDOTUuNTEyNyA0Ni40NDA4IDk1LjI5NDMgNDcuMjc1MSA5NC44NTc0IDQ3Ljk0ODJDOTQuNDI3NyA0OC42MjE0IDkzLjgwODMgNDkuMTMzNSA5Mi45OTkgNDkuNDg0NEM5Mi4xODk4IDQ5LjgyODEgOTEuMjE1OCA1MCA5MC4wNzcxIDUwWk0xMDUuMjE2IDQ3LjI2MDdWMzguMzc3SDEwNy44MTVWNTBIMTA1LjM2NkwxMDUuMjE2IDQ3LjI2MDdaTTEwNS41ODEgNDQuODQzOEwxMDYuNDUxIDQ0LjgyMjNDMTA2LjQ1MSA0NS42MDI5IDEwNi4zNjUgNDYuMzIyNiAxMDYuMTkzIDQ2Ljk4MTRDMTA2LjAyMSA0Ny42MzMxIDEwNS43NTcgNDguMjAyNSAxMDUuMzk4IDQ4LjY4OTVDMTA1LjA0IDQ5LjE2OTMgMTA0LjU4MiA0OS41NDUyIDEwNC4wMjMgNDkuODE3NEMxMDMuNDY1IDUwLjA4MjQgMTAyLjc5NSA1MC4yMTQ4IDEwMi4wMTUgNTAuMjE0OEMxMDEuNDQ5IDUwLjIxNDggMTAwLjkzIDUwLjEzMjUgMTAwLjQ1NyA0OS45Njc4Qzk5Ljk4NDQgNDkuODAzMSA5OS41NzYyIDQ5LjU0ODggOTkuMjMyNCA0OS4yMDUxQzk4Ljg5NTggNDguODYxMyA5OC42MzQ0IDQ4LjQxMzcgOTguNDQ4MiA0Ny44NjIzQzk4LjI2MiA0Ny4zMTA5IDk4LjE2ODkgNDYuNjUyIDk4LjE2ODkgNDUuODg1N1YzOC4zNzdIMTAwLjc1OFY0NS45MDcyQzEwMC43NTggNDYuMzI5OCAxMDAuODA4IDQ2LjY4NDIgMTAwLjkwOCA0Ni45NzA3QzEwMS4wMDggNDcuMjUgMTAxLjE0NSA0Ny40NzU2IDEwMS4zMTYgNDcuNjQ3NUMxMDEuNDg4IDQ3LjgxOTMgMTAxLjY4OSA0Ny45NDExIDEwMS45MTggNDguMDEyN0MxMDIuMTQ3IDQ4LjA4NDMgMTAyLjM5MSA0OC4xMjAxIDEwMi42NDggNDguMTIwMUMxMDMuMzg2IDQ4LjEyMDEgMTAzLjk2NiA0Ny45NzY5IDEwNC4zODkgNDcuNjkwNEMxMDQuODE4IDQ3LjM5NjggMTA1LjEyMyA0Ny4wMDI5IDEwNS4zMDIgNDYuNTA4OEMxMDUuNDg4IDQ2LjAxNDYgMTA1LjU4MSA0NS40NTk2IDEwNS41ODEgNDQuODQzOFpNMTE2LjA0NyAzOC4zNzdWNDAuMjY3NkgxMDkuNDk0VjM4LjM3N0gxMTYuMDQ3Wk0xMTEuMzg1IDM1LjUzMDNIMTEzLjk3NFY0Ni43ODgxQzExMy45NzQgNDcuMTQ2MiAxMTQuMDI0IDQ3LjQyMTkgMTE0LjEyNCA0Ny42MTUyQzExNC4yMzEgNDcuODAxNCAxMTQuMzc4IDQ3LjkyNjggMTE0LjU2NCA0Ny45OTEyQzExNC43NTEgNDguMDU1NyAxMTQuOTY5IDQ4LjA4NzkgMTE1LjIyIDQ4LjA4NzlDMTE1LjM5OSA0OC4wODc5IDExNS41NzEgNDguMDc3MSAxMTUuNzM1IDQ4LjA1NTdDMTE1LjkgNDguMDM0MiAxMTYuMDMzIDQ4LjAxMjcgMTE2LjEzMyA0Ny45OTEyTDExNi4xNDQgNDkuOTY3OEMxMTUuOTI5IDUwLjAzMjIgMTE1LjY3OCA1MC4wODk1IDExNS4zOTIgNTAuMTM5NkMxMTUuMTEyIDUwLjE4OTggMTE0Ljc5IDUwLjIxNDggMTE0LjQyNSA1MC4yMTQ4QzExMy44MyA1MC4yMTQ4IDExMy4zMDQgNTAuMTExIDExMi44NDYgNDkuOTAzM0MxMTIuMzg3IDQ5LjY4ODUgMTEyLjAyOSA0OS4zNDExIDExMS43NzEgNDguODYxM0MxMTEuNTE0IDQ4LjM4MTUgMTExLjM4NSA0Ny43NDQxIDExMS4zODUgNDYuOTQ5MlYzNS41MzAzWk0xMjMuNjIzIDM4LjM3N1Y0MC4yNjc2SDExNy4wN1YzOC4zNzdIMTIzLjYyM1pNMTE4Ljk2MSAzNS41MzAzSDEyMS41NVY0Ni43ODgxQzEyMS41NSA0Ny4xNDYyIDEyMS42IDQ3LjQyMTkgMTIxLjcgNDcuNjE1MkMxMjEuODA4IDQ3LjgwMTQgMTIxLjk1NCA0Ny45MjY4IDEyMi4xNDEgNDcuOTkxMkMxMjIuMzI3IDQ4LjA1NTcgMTIyLjU0NSA0OC4wODc5IDEyMi43OTYgNDguMDg3OUMxMjIuOTc1IDQ4LjA4NzkgMTIzLjE0NyA0OC4wNzcxIDEyMy4zMTIgNDguMDU1N0MxMjMuNDc2IDQ4LjAzNDIgMTIzLjYwOSA0OC4wMTI3IDEyMy43MDkgNDcuOTkxMkwxMjMuNzIgNDkuOTY3OEMxMjMuNTA1IDUwLjAzMjIgMTIzLjI1NCA1MC4wODk1IDEyMi45NjggNTAuMTM5NkMxMjIuNjg4IDUwLjE4OTggMTIyLjM2NiA1MC4yMTQ4IDEyMi4wMDEgNTAuMjE0OEMxMjEuNDA3IDUwLjIxNDggMTIwLjg4IDUwLjExMSAxMjAuNDIyIDQ5LjkwMzNDMTE5Ljk2NCA0OS42ODg1IDExOS42MDUgNDkuMzQxMSAxMTkuMzQ4IDQ4Ljg2MTNDMTE5LjA5IDQ4LjM4MTUgMTE4Ljk2MSA0Ny43NDQxIDExOC45NjEgNDYuOTQ5MlYzNS41MzAzWk0xMjUuMTE5IDQ0LjMxNzRWNDQuMDcwM0MxMjUuMTE5IDQzLjIzMjQgMTI1LjI0MSA0Mi40NTU0IDEyNS40ODQgNDEuNzM5M0MxMjUuNzI4IDQxLjAxNiAxMjYuMDc5IDQwLjM4OTMgMTI2LjUzNyAzOS44NTk0QzEyNy4wMDMgMzkuMzIyMyAxMjcuNTY4IDM4LjkwNjkgMTI4LjIzNCAzOC42MTMzQzEyOC45MDggMzguMzEyNSAxMjkuNjY3IDM4LjE2MjEgMTMwLjUxMiAzOC4xNjIxQzEzMS4zNjQgMzguMTYyMSAxMzIuMTIzIDM4LjMxMjUgMTMyLjc4OSAzOC42MTMzQzEzMy40NjIgMzguOTA2OSAxMzQuMDMyIDM5LjMyMjMgMTM0LjQ5NyAzOS44NTk0QzEzNC45NjMgNDAuMzg5MyAxMzUuMzE3IDQxLjAxNiAxMzUuNTYxIDQxLjczOTNDMTM1LjgwNCA0Mi40NTU0IDEzNS45MjYgNDMuMjMyNCAxMzUuOTI2IDQ0LjA3MDNWNDQuMzE3NEMxMzUuOTI2IDQ1LjE1NTMgMTM1LjgwNCA0NS45MzIzIDEzNS41NjEgNDYuNjQ4NEMxMzUuMzE3IDQ3LjM2NDYgMTM0Ljk2MyA0Ny45OTEyIDEzNC40OTcgNDguNTI4M0MxMzQuMDMyIDQ5LjA1ODMgMTMzLjQ2NiA0OS40NzM2IDEzMi44IDQ5Ljc3NDRDMTMyLjEzNCA1MC4wNjggMTMxLjM3OCA1MC4yMTQ4IDEzMC41MzMgNTAuMjE0OEMxMjkuNjgxIDUwLjIxNDggMTI4LjkxOCA1MC4wNjggMTI4LjI0NSA0OS43NzQ0QzEyNy41NzkgNDkuNDczNiAxMjcuMDEzIDQ5LjA1ODMgMTI2LjU0OCA0OC41MjgzQzEyNi4wODIgNDcuOTkxMiAxMjUuNzI4IDQ3LjM2NDYgMTI1LjQ4NCA0Ni42NDg0QzEyNS4yNDEgNDUuOTMyMyAxMjUuMTE5IDQ1LjE1NTMgMTI1LjExOSA0NC4zMTc0Wk0xMjcuNzA4IDQ0LjA3MDNWNDQuMzE3NEMxMjcuNzA4IDQ0Ljg0MDIgMTI3Ljc2MiA0NS4zMzQzIDEyNy44NjkgNDUuNzk5OEMxMjcuOTc3IDQ2LjI2NTMgMTI4LjE0NSA0Ni42NzM1IDEyOC4zNzQgNDcuMDI0NEMxMjguNjAzIDQ3LjM3NTMgMTI4Ljg5NyA0Ny42NTEgMTI5LjI1NSA0Ny44NTE2QzEyOS42MTMgNDguMDUyMSAxMzAuMDM5IDQ4LjE1MjMgMTMwLjUzMyA0OC4xNTIzQzEzMS4wMTMgNDguMTUyMyAxMzEuNDI4IDQ4LjA1MjEgMTMxLjc3OSA0Ny44NTE2QzEzMi4xMzcgNDcuNjUxIDEzMi40MzEgNDcuMzc1MyAxMzIuNjYgNDcuMDI0NEMxMzIuODg5IDQ2LjY3MzUgMTMzLjA1OCA0Ni4yNjUzIDEzMy4xNjUgNDUuNzk5OEMxMzMuMjggNDUuMzM0MyAxMzMuMzM3IDQ0Ljg0MDIgMTMzLjMzNyA0NC4zMTc0VjQ0LjA3MDNDMTMzLjMzNyA0My41NTQ3IDEzMy4yOCA0My4wNjc3IDEzMy4xNjUgNDIuNjA5NEMxMzMuMDU4IDQyLjE0MzkgMTMyLjg4NiA0MS43MzIxIDEzMi42NDkgNDEuMzc0QzEzMi40MiA0MS4wMTYgMTMyLjEyNyA0MC43MzY3IDEzMS43NjkgNDAuNTM2MUMxMzEuNDE4IDQwLjMyODUgMTMwLjk5OSA0MC4yMjQ2IDEzMC41MTIgNDAuMjI0NkMxMzAuMDI1IDQwLjIyNDYgMTI5LjYwMiA0MC4zMjg1IDEyOS4yNDQgNDAuNTM2MUMxMjguODkzIDQwLjczNjcgMTI4LjYwMyA0MS4wMTYgMTI4LjM3NCA0MS4zNzRDMTI4LjE0NSA0MS43MzIxIDEyNy45NzcgNDIuMTQzOSAxMjcuODY5IDQyLjYwOTRDMTI3Ljc2MiA0My4wNjc3IDEyNy43MDggNDMuNTU0NyAxMjcuNzA4IDQ0LjA3MDNaTTE0MC45MTMgNDAuODU4NFY1MEgxMzguMzI0VjM4LjM3N0gxNDAuNzYzTDE0MC45MTMgNDAuODU4NFpNMTQwLjQ1MSA0My43NTg4TDEzOS42MTMgNDMuNzQ4QzEzOS42MiA0Mi45MjQ1IDEzOS43MzUgNDIuMTY4OSAxMzkuOTU3IDQxLjQ4MTRDMTQwLjE4NiA0MC43OTM5IDE0MC41MDEgNDAuMjAzMSAxNDAuOTAyIDM5LjcwOUMxNDEuMzExIDM5LjIxNDggMTQxLjc5OCAzOC44MzUzIDE0Mi4zNjMgMzguNTcwM0MxNDIuOTI5IDM4LjI5ODIgMTQzLjU1OSAzOC4xNjIxIDE0NC4yNTQgMzguMTYyMUMxNDQuODEyIDM4LjE2MjEgMTQ1LjMxNyAzOC4yNDA5IDE0NS43NjkgMzguMzk4NEMxNDYuMjI3IDM4LjU0ODggMTQ2LjYxNyAzOC43OTU5IDE0Ni45MzkgMzkuMTM5NkMxNDcuMjY5IDM5LjQ4MzQgMTQ3LjUyIDM5LjkzMSAxNDcuNjkxIDQwLjQ4MjRDMTQ3Ljg2MyA0MS4wMjY3IDE0Ny45NDkgNDEuNjk2MyAxNDcuOTQ5IDQyLjQ5MTJWNTBIMTQ1LjM1VjQyLjQ4MDVDMTQ1LjM1IDQxLjkyMTkgMTQ1LjI2NyA0MS40ODE0IDE0NS4xMDMgNDEuMTU5MkMxNDQuOTQ1IDQwLjgyOTggMTQ0LjcxMiA0MC41OTcgMTQ0LjQwNCA0MC40NjA5QzE0NC4xMDQgNDAuMzE3NyAxNDMuNzI4IDQwLjI0NjEgMTQzLjI3NiA0MC4yNDYxQzE0Mi44MzIgNDAuMjQ2MSAxNDIuNDM1IDQwLjMzOTIgMTQyLjA4NCA0MC41MjU0QzE0MS43MzMgNDAuNzExNiAxNDEuNDM2IDQwLjk2NTggMTQxLjE5MiA0MS4yODgxQzE0MC45NTYgNDEuNjEwNCAxNDAuNzczIDQxLjk4MjcgMTQwLjY0NSA0Mi40MDUzQzE0MC41MTYgNDIuODI3OCAxNDAuNDUxIDQzLjI3OSAxNDAuNDUxIDQzLjc1ODhaIiBmaWxsPSIjM0Y1MkREIi8+CjxyZWN0IHg9IjAuNzUiIHk9Ijg4Ljc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIGZpbGw9IndoaXRlIi8+CjxyZWN0IHg9IjAuNzUiIHk9Ijg4Ljc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIHN0cm9rZT0iIzNGNTJERCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KPHBhdGggZD0iTTY1LjQ5OTcgMTExVjExMy4zMzNINzUuNTIxM0w2NC4zMzMgMTI0LjUyMkw2NS45NzggMTI2LjE2N0w3Ny4xNjYzIDExNC45NzhWMTI1SDc5LjQ5OTdWMTExSDY1LjQ5OTdaIiBmaWxsPSIjM0Y1MkREIi8+CjxwYXRoIGQ9Ik0xMDAuNTY0IDEyMS45NzJDMTAwLjU2NCAxMjEuNjQ5IDEwMC41MTQgMTIxLjM2MyAxMDAuNDE0IDEyMS4xMTJDMTAwLjMyMSAxMjAuODYyIDEwMC4xNTMgMTIwLjYzMiA5OS45MDkyIDEyMC40MjVDOTkuNjY1NyAxMjAuMjE3IDk5LjMyMTkgMTIwLjAxNyA5OC44Nzc5IDExOS44MjNDOTguNDQxMSAxMTkuNjIzIDk3Ljg4MjUgMTE5LjQxOSA5Ny4yMDIxIDExOS4yMTFDOTYuNDU3NCAxMTguOTgyIDk1Ljc2OTkgMTE4LjcyOCA5NS4xMzk2IDExOC40NDhDOTQuNTE2NiAxMTguMTYyIDkzLjk3MjMgMTE3LjgzMiA5My41MDY4IDExNy40NkM5My4wNDEzIDExNy4wOCA5Mi42Nzk3IDExNi42NDcgOTIuNDIxOSAxMTYuMTZDOTIuMTY0MSAxMTUuNjY2IDkyLjAzNTIgMTE1LjA5NyA5Mi4wMzUyIDExNC40NTJDOTIuMDM1MiAxMTMuODE1IDkyLjE2NzYgMTEzLjIzNSA5Mi40MzI2IDExMi43MTJDOTIuNzA0OCAxMTIuMTg5IDkzLjA4NzkgMTExLjczOCA5My41ODIgMTExLjM1OEM5NC4wODMzIDExMC45NzIgOTQuNjc0MiAxMTAuNjc0IDk1LjM1NDUgMTEwLjQ2N0M5Ni4wMzQ4IDExMC4yNTIgOTYuNzg2OCAxMTAuMTQ1IDk3LjYxMDQgMTEwLjE0NUM5OC43NzA1IDExMC4xNDUgOTkuNzY5NSAxMTAuMzU5IDEwMC42MDcgMTEwLjc4OUMxMDEuNDUyIDExMS4yMTkgMTAyLjEwMSAxMTEuNzk1IDEwMi41NTIgMTEyLjUxOUMxMDMuMDEgMTEzLjI0MiAxMDMuMjM5IDExNC4wNCAxMDMuMjM5IDExNC45MTRIMTAwLjU2NEMxMDAuNTY0IDExNC4zOTggMTAwLjQ1MyAxMTMuOTQ0IDEwMC4yMzEgMTEzLjU1QzEwMC4wMTcgMTEzLjE0OSA5OS42ODcyIDExMi44MzQgOTkuMjQzMiAxMTIuNjA0Qzk4LjgwNjMgMTEyLjM3NSA5OC4yNTEzIDExMi4yNjEgOTcuNTc4MSAxMTIuMjYxQzk2Ljk0MDggMTEyLjI2MSA5Ni40MTA4IDExMi4zNTcgOTUuOTg4MyAxMTIuNTUxQzk1LjU2NTggMTEyLjc0NCA5NS4yNTA3IDExMy4wMDYgOTUuMDQzIDExMy4zMzVDOTQuODM1MyAxMTMuNjY0IDk0LjczMTQgMTE0LjAzNyA5NC43MzE0IDExNC40NTJDOTQuNzMxNCAxMTQuNzQ2IDk0Ljc5OTUgMTE1LjAxNCA5NC45MzU1IDExNS4yNThDOTUuMDcxNiAxMTUuNDk0IDk1LjI3OTMgMTE1LjcxNiA5NS41NTg2IDExNS45MjRDOTUuODM3OSAxMTYuMTI0IDk2LjE4ODggMTE2LjMxNCA5Ni42MTEzIDExNi40OTNDOTcuMDMzOSAxMTYuNjcyIDk3LjUzMTYgMTE2Ljg0NCA5OC4xMDQ1IDExNy4wMDlDOTguOTcxIDExNy4yNjcgOTkuNzI2NiAxMTcuNTUzIDEwMC4zNzEgMTE3Ljg2OEMxMDEuMDE2IDExOC4xNzYgMTAxLjU1MyAxMTguNTI3IDEwMS45ODIgMTE4LjkyMUMxMDIuNDEyIDExOS4zMTUgMTAyLjczNCAxMTkuNzYyIDEwMi45NDkgMTIwLjI2NEMxMDMuMTY0IDEyMC43NTggMTAzLjI3MSAxMjEuMzIgMTAzLjI3MSAxMjEuOTVDMTAzLjI3MSAxMjIuNjA5IDEwMy4xMzkgMTIzLjIwMyAxMDIuODc0IDEyMy43MzNDMTAyLjYwOSAxMjQuMjU2IDEwMi4yMjkgMTI0LjcwNCAxMDEuNzM1IDEyNS4wNzZDMTAxLjI0OCAxMjUuNDQxIDEwMC42NjEgMTI1LjcyNCA5OS45NzM2IDEyNS45MjVDOTkuMjkzMyAxMjYuMTE4IDk4LjUzNDIgMTI2LjIxNSA5Ny42OTYzIDEyNi4yMTVDOTYuOTQ0MyAxMjYuMjE1IDk2LjIwMzEgMTI2LjExNSA5NS40NzI3IDEyNS45MTRDOTQuNzQ5MyAxMjUuNzE0IDk0LjA5MDUgMTI1LjQwOSA5My40OTYxIDEyNS4wMDFDOTIuOTAxNyAxMjQuNTg2IDkyLjQyOSAxMjQuMDcgOTIuMDc4MSAxMjMuNDU0QzkxLjcyNzIgMTIyLjgzMSA5MS41NTE4IDEyMi4xMDQgOTEuNTUxOCAxMjEuMjczSDk0LjI0OEM5NC4yNDggMTIxLjc4MiA5NC4zMzQgMTIyLjIxNSA5NC41MDU5IDEyMi41NzNDOTQuNjg0OSAxMjIuOTMxIDk0LjkzMiAxMjMuMjI1IDk1LjI0NzEgMTIzLjQ1NEM5NS41NjIyIDEyMy42NzYgOTUuOTI3NCAxMjMuODQxIDk2LjM0MjggMTIzLjk0OEM5Ni43NjUzIDEyNC4wNTYgOTcuMjE2NSAxMjQuMTA5IDk3LjY5NjMgMTI0LjEwOUM5OC4zMjY1IDEyNC4xMDkgOTguODUyOSAxMjQuMDIgOTkuMjc1NCAxMjMuODQxQzk5LjcwNTEgMTIzLjY2MiAxMDAuMDI3IDEyMy40MTEgMTAwLjI0MiAxMjMuMDg5QzEwMC40NTcgMTIyLjc2NyAxMDAuNTY0IDEyMi4zOTQgMTAwLjU2NCAxMjEuOTcyWk0xMTAuNzcyIDEyNi4yMTVDMTA5LjkxMyAxMjYuMjE1IDEwOS4xMzYgMTI2LjA3NSAxMDguNDQxIDEyNS43OTZDMTA3Ljc1NCAxMjUuNTA5IDEwNy4xNjcgMTI1LjExMiAxMDYuNjggMTI0LjYwNEMxMDYuMiAxMjQuMDk1IDEwNS44MzEgMTIzLjQ5NyAxMDUuNTczIDEyMi44MUMxMDUuMzE1IDEyMi4xMjIgMTA1LjE4NyAxMjEuMzgxIDEwNS4xODcgMTIwLjU4NlYxMjAuMTU2QzEwNS4xODcgMTE5LjI0NyAxMDUuMzE5IDExOC40MjMgMTA1LjU4NCAxMTcuNjg2QzEwNS44NDkgMTE2Ljk0OCAxMDYuMjE4IDExNi4zMTggMTA2LjY5IDExNS43OTVDMTA3LjE2MyAxMTUuMjY1IDEwNy43MjIgMTE0Ljg2IDEwOC4zNjYgMTE0LjU4MUMxMDkuMDExIDExNC4zMDIgMTA5LjcwOSAxMTQuMTYyIDExMC40NjEgMTE0LjE2MkMxMTEuMjkyIDExNC4xNjIgMTEyLjAxOSAxMTQuMzAyIDExMi42NDIgMTE0LjU4MUMxMTMuMjY1IDExNC44NiAxMTMuNzggMTE1LjI1NCAxMTQuMTg4IDExNS43NjNDMTE0LjYwNCAxMTYuMjY0IDExNC45MTIgMTE2Ljg2MiAxMTUuMTEyIDExNy41NTdDMTE1LjMyIDExOC4yNTEgMTE1LjQyNCAxMTkuMDE4IDExNS40MjQgMTE5Ljg1NVYxMjAuOTYySDEwNi40NDNWMTE5LjEwNEgxMTIuODY3VjExOC44OTlDMTEyLjg1MyAxMTguNDM0IDExMi43NiAxMTcuOTk3IDExMi41ODggMTE3LjU4OUMxMTIuNDIzIDExNy4xODEgMTEyLjE2OSAxMTYuODUxIDExMS44MjUgMTE2LjYwMUMxMTEuNDgxIDExNi4zNSAxMTEuMDIzIDExNi4yMjUgMTEwLjQ1IDExNi4yMjVDMTEwLjAyMSAxMTYuMjI1IDEwOS42MzcgMTE2LjMxOCAxMDkuMzAxIDExNi41MDRDMTA4Ljk3MSAxMTYuNjgzIDEwOC42OTYgMTE2Ljk0NCAxMDguNDc0IDExNy4yODhDMTA4LjI1MiAxMTcuNjMyIDEwOC4wOCAxMTguMDQ3IDEwNy45NTggMTE4LjUzNEMxMDcuODQzIDExOS4wMTQgMTA3Ljc4NiAxMTkuNTU1IDEwNy43ODYgMTIwLjE1NlYxMjAuNTg2QzEwNy43ODYgMTIxLjA5NCAxMDcuODU0IDEyMS41NjcgMTA3Ljk5IDEyMi4wMDRDMTA4LjEzMyAxMjIuNDM0IDEwOC4zNDEgMTIyLjgxIDEwOC42MTMgMTIzLjEzMkMxMDguODg1IDEyMy40NTQgMTA5LjIxNSAxMjMuNzA4IDEwOS42MDIgMTIzLjg5NUMxMDkuOTg4IDEyNC4wNzQgMTEwLjQyOSAxMjQuMTYzIDExMC45MjMgMTI0LjE2M0MxMTEuNTQ2IDEyNC4xNjMgMTEyLjEwMSAxMjQuMDM4IDExMi41ODggMTIzLjc4N0MxMTMuMDc1IDEyMy41MzYgMTEzLjQ5NyAxMjMuMTgyIDExMy44NTUgMTIyLjcyNEwxMTUuMjIgMTI0LjA0NUMxMTQuOTY5IDEyNC40MSAxMTQuNjQzIDEyNC43NjEgMTE0LjI0MiAxMjUuMDk4QzExMy44NDEgMTI1LjQyNyAxMTMuMzUxIDEyNS42OTYgMTEyLjc3MSAxMjUuOTAzQzExMi4xOTggMTI2LjExMSAxMTEuNTMyIDEyNi4yMTUgMTEwLjc3MiAxMjYuMjE1Wk0xMjAuMjYxIDExNi44NThWMTI2SDExNy42NzJWMTE0LjM3N0gxMjAuMTFMMTIwLjI2MSAxMTYuODU4Wk0xMTkuNzk5IDExOS43NTlMMTE4Ljk2MSAxMTkuNzQ4QzExOC45NjggMTE4LjkyNCAxMTkuMDgzIDExOC4xNjkgMTE5LjMwNSAxMTcuNDgxQzExOS41MzQgMTE2Ljc5NCAxMTkuODQ5IDExNi4yMDMgMTIwLjI1IDExNS43MDlDMTIwLjY1OCAxMTUuMjE1IDEyMS4xNDUgMTE0LjgzNSAxMjEuNzExIDExNC41N0MxMjIuMjc3IDExNC4yOTggMTIyLjkwNyAxMTQuMTYyIDEyMy42MDIgMTE0LjE2MkMxMjQuMTYgMTE0LjE2MiAxMjQuNjY1IDExNC4yNDEgMTI1LjExNiAxMTQuMzk4QzEyNS41NzUgMTE0LjU0OSAxMjUuOTY1IDExNC43OTYgMTI2LjI4NyAxMTUuMTRDMTI2LjYxNyAxMTUuNDgzIDEyNi44NjcgMTE1LjkzMSAxMjcuMDM5IDExNi40ODJDMTI3LjIxMSAxMTcuMDI3IDEyNy4yOTcgMTE3LjY5NiAxMjcuMjk3IDExOC40OTFWMTI2SDEyNC42OTdWMTE4LjQ4QzEyNC42OTcgMTE3LjkyMiAxMjQuNjE1IDExNy40ODEgMTI0LjQ1IDExNy4xNTlDMTI0LjI5MyAxMTYuODMgMTI0LjA2IDExNi41OTcgMTIzLjc1MiAxMTYuNDYxQzEyMy40NTEgMTE2LjMxOCAxMjMuMDc1IDExNi4yNDYgMTIyLjYyNCAxMTYuMjQ2QzEyMi4xOCAxMTYuMjQ2IDEyMS43ODMgMTE2LjMzOSAxMjEuNDMyIDExNi41MjVDMTIxLjA4MSAxMTYuNzEyIDEyMC43ODQgMTE2Ljk2NiAxMjAuNTQgMTE3LjI4OEMxMjAuMzA0IDExNy42MSAxMjAuMTIxIDExNy45ODMgMTE5Ljk5MiAxMTguNDA1QzExOS44NjMgMTE4LjgyOCAxMTkuNzk5IDExOS4yNzkgMTE5Ljc5OSAxMTkuNzU5Wk0xMzcuMjc5IDEyMy41OTRWMTA5LjVIMTM5Ljg3OVYxMjZIMTM3LjUyNkwxMzcuMjc5IDEyMy41OTRaTTEyOS43MTcgMTIwLjMxN1YxMjAuMDkyQzEyOS43MTcgMTE5LjIxMSAxMjkuODIxIDExOC40MDkgMTMwLjAyOCAxMTcuNjg2QzEzMC4yMzYgMTE2Ljk1NSAxMzAuNTM3IDExNi4zMjggMTMwLjkzMSAxMTUuODA2QzEzMS4zMjUgMTE1LjI3NiAxMzEuODA0IDExNC44NzEgMTMyLjM3IDExNC41OTJDMTMyLjkzNiAxMTQuMzA1IDEzMy41NzMgMTE0LjE2MiAxMzQuMjgyIDExNC4xNjJDMTM0Ljk4NCAxMTQuMTYyIDEzNS42IDExNC4yOTggMTM2LjEzIDExNC41N0MxMzYuNjYgMTE0Ljg0MiAxMzcuMTExIDExNS4yMzMgMTM3LjQ4MyAxMTUuNzQxQzEzNy44NTYgMTE2LjI0MyAxMzguMTUzIDExNi44NDQgMTM4LjM3NSAxMTcuNTQ2QzEzOC41OTcgMTE4LjI0MSAxMzguNzU1IDExOS4wMTQgMTM4Ljg0OCAxMTkuODY2VjEyMC41ODZDMTM4Ljc1NSAxMjEuNDE3IDEzOC41OTcgMTIyLjE3NiAxMzguMzc1IDEyMi44NjNDMTM4LjE1MyAxMjMuNTUxIDEzNy44NTYgMTI0LjE0NSAxMzcuNDgzIDEyNC42NDZDMTM3LjExMSAxMjUuMTQ4IDEzNi42NTYgMTI1LjUzNSAxMzYuMTE5IDEyNS44MDdDMTM1LjU4OSAxMjYuMDc5IDEzNC45NyAxMjYuMjE1IDEzNC4yNjEgMTI2LjIxNUMxMzMuNTU5IDEyNi4yMTUgMTMyLjkyNSAxMjYuMDY4IDEzMi4zNTkgMTI1Ljc3NEMxMzEuODAxIDEyNS40ODEgMTMxLjMyNSAxMjUuMDY5IDEzMC45MzEgMTI0LjUzOUMxMzAuNTM3IDEyNC4wMDkgMTMwLjIzNiAxMjMuMzg2IDEzMC4wMjggMTIyLjY3QzEyOS44MjEgMTIxLjk0NyAxMjkuNzE3IDEyMS4xNjIgMTI5LjcxNyAxMjAuMzE3Wk0xMzIuMzA2IDEyMC4wOTJWMTIwLjMxN0MxMzIuMzA2IDEyMC44NDcgMTMyLjM1MiAxMjEuMzQxIDEzMi40NDUgMTIxLjhDMTMyLjU0NiAxMjIuMjU4IDEzMi43IDEyMi42NjMgMTMyLjkwNyAxMjMuMDE0QzEzMy4xMTUgMTIzLjM1NyAxMzMuMzgzIDEyMy42MyAxMzMuNzEzIDEyMy44M0MxMzQuMDQ5IDEyNC4wMjMgMTM0LjQ1MSAxMjQuMTIgMTM0LjkxNiAxMjQuMTJDMTM1LjUwMyAxMjQuMTIgMTM1Ljk4NyAxMjMuOTkxIDEzNi4zNjYgMTIzLjczM0MxMzYuNzQ2IDEyMy40NzYgMTM3LjA0MyAxMjMuMTI4IDEzNy4yNTggMTIyLjY5MUMxMzcuNDggMTIyLjI0NyAxMzcuNjMgMTIxLjc1MyAxMzcuNzA5IDEyMS4yMDlWMTE5LjI2NUMxMzcuNjY2IDExOC44NDIgMTM3LjU3NiAxMTguNDQ4IDEzNy40NCAxMTguMDgzQzEzNy4zMTIgMTE3LjcxOCAxMzcuMTM2IDExNy4zOTkgMTM2LjkxNCAxMTcuMTI3QzEzNi42OTIgMTE2Ljg0OCAxMzYuNDE2IDExNi42MzMgMTM2LjA4NyAxMTYuNDgyQzEzNS43NjUgMTE2LjMyNSAxMzUuMzgyIDExNi4yNDYgMTM0LjkzOCAxMTYuMjQ2QzEzNC40NjUgMTE2LjI0NiAxMzQuMDY0IDExNi4zNDYgMTMzLjczNCAxMTYuNTQ3QzEzMy40MDUgMTE2Ljc0NyAxMzMuMTMzIDExNy4wMjMgMTMyLjkxOCAxMTcuMzc0QzEzMi43MSAxMTcuNzI1IDEzMi41NTYgMTE4LjEzMyAxMzIuNDU2IDExOC41OTlDMTMyLjM1NiAxMTkuMDY0IDEzMi4zMDYgMTE5LjU2MiAxMzIuMzA2IDEyMC4wOTJaIiBmaWxsPSIjM0Y1MkREIi8+Cjwvc3ZnPgo=", "description": null, "order": 7500, "name": "Buttons" diff --git a/application/src/main/data/json/system/widget_types/single_switch.json b/application/src/main/data/json/system/widget_types/single_switch.json index 4a62cc0598..b14cf633a2 100644 --- a/application/src/main/data/json/system/widget_types/single_switch.json +++ b/application/src/main/data/json/system/widget_types/single_switch.json @@ -2,7 +2,7 @@ "fqn": "single_switch", "name": "Single Switch", "deprecated": false, - "image": "tb-image:c2luZ2xlLXN3aXRjaC5zdmc=:IlNpbmdsZSBTd2l0Y2giIHN5c3RlbSB3aWRnZXQgaW1hZ2U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAxIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMSAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8zNjM5XzE1NTY3NCkiPgo8cmVjdCB4PSIwLjUiIHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiByeD0iNCIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iMSIgeT0iMC41IiB3aWR0aD0iMTk5IiBoZWlnaHQ9IjE1OSIgcng9IjMuNSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPHJlY3QgeD0iNTUiIHk9IjcwIiB3aWR0aD0iMzguMDk1MiIgaGVpZ2h0PSIxOS45NTQ3IiByeD0iOS45NzczMyIgZmlsbD0iIzU0NjlGRiIvPgo8Y2lyY2xlIGN4PSI4My4xMTY0IiBjeT0iNzkuOTc3MiIgcj0iOC4xNjMyNyIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTEwNi42OTQgODEuNzUzNEMxMDYuNjk0IDgxLjUzMzcgMTA2LjY2IDgxLjMzODQgMTA2LjU5MiA4MS4xNjc1QzEwNi41MjggODAuOTk2NiAxMDYuNDE0IDgwLjg0MDMgMTA2LjI0OCA4MC42OTg3QzEwNi4wODIgODAuNTU3MSAxMDUuODQ3IDgwLjQyMDQgMTA1LjU0NCA4MC4yODg2QzEwNS4yNDcgODAuMTUxOSAxMDQuODY2IDgwLjAxMjcgMTA0LjQwMiA3OS44NzExQzEwMy44OTQgNzkuNzE0OCAxMDMuNDI1IDc5LjU0MTUgMTAyLjk5NiA3OS4zNTExQzEwMi41NzEgNzkuMTU1OCAxMDIuMiA3OC45MzEyIDEwMS44ODIgNzguNjc3MkMxMDEuNTY1IDc4LjQxODUgMTAxLjMxOCA3OC4xMjMgMTAxLjE0MyA3Ny43OTFDMTAwLjk2NyA3Ny40NTQxIDEwMC44NzkgNzcuMDY1OSAxMDAuODc5IDc2LjYyNjVDMTAwLjg3OSA3Ni4xOTE5IDEwMC45NjkgNzUuNzk2NCAxMDEuMTUgNzUuNDM5OUMxMDEuMzM1IDc1LjA4MzUgMTAxLjU5NyA3NC43NzU5IDEwMS45MzQgNzQuNTE3MUMxMDIuMjc1IDc0LjI1MzQgMTAyLjY3OCA3NC4wNTA4IDEwMy4xNDIgNzMuOTA5MkMxMDMuNjA2IDczLjc2MjcgMTA0LjExOSA3My42ODk1IDEwNC42OCA3My42ODk1QzEwNS40NzEgNzMuNjg5NSAxMDYuMTUyIDczLjgzNTkgMTA2LjcyNCA3NC4xMjg5QzEwNy4zIDc0LjQyMTkgMTA3Ljc0MiA3NC44MTQ5IDEwOC4wNDkgNzUuMzA4MUMxMDguMzYyIDc1LjgwMTMgMTA4LjUxOCA3Ni4zNDU3IDEwOC41MTggNzYuOTQxNEgxMDYuNjk0QzEwNi42OTQgNzYuNTg5OCAxMDYuNjE5IDc2LjI3OTggMTA2LjQ2NyA3Ni4wMTEyQzEwNi4zMjEgNzUuNzM3OCAxMDYuMDk2IDc1LjUyMjkgMTA1Ljc5MyA3NS4zNjY3QzEwNS40OTYgNzUuMjEwNCAxMDUuMTE3IDc1LjEzMjMgMTA0LjY1OCA3NS4xMzIzQzEwNC4yMjQgNzUuMTMyMyAxMDMuODYyIDc1LjE5ODIgMTAzLjU3NCA3NS4zMzAxQzEwMy4yODYgNzUuNDYxOSAxMDMuMDcxIDc1LjY0MDEgMTAyLjkzIDc1Ljg2NDdDMTAyLjc4OCA3Ni4wODk0IDEwMi43MTcgNzYuMzQzMyAxMDIuNzE3IDc2LjYyNjVDMTAyLjcxNyA3Ni44MjY3IDEwMi43NjQgNzcuMDA5OCAxMDIuODU2IDc3LjE3NThDMTAyLjk0OSA3Ny4zMzY5IDEwMy4wOTEgNzcuNDg4MyAxMDMuMjgxIDc3LjYyOTlDMTAzLjQ3MiA3Ny43NjY2IDEwMy43MTEgNzcuODk2IDEwMy45OTkgNzguMDE4MUMxMDQuMjg3IDc4LjE0MDEgMTA0LjYyNiA3OC4yNTczIDEwNS4wMTcgNzguMzY5NkMxMDUuNjA4IDc4LjU0NTQgMTA2LjEyMyA3OC43NDA3IDEwNi41NjIgNzguOTU1NkMxMDcuMDAyIDc5LjE2NTUgMTA3LjM2OCA3OS40MDQ4IDEwNy42NjEgNzkuNjczM0MxMDcuOTU0IDc5Ljk0MTkgMTA4LjE3NCA4MC4yNDcxIDEwOC4zMiA4MC41ODg5QzEwOC40NjcgODAuOTI1OCAxMDguNTQgODEuMzA5MSAxMDguNTQgODEuNzM4OEMxMDguNTQgODIuMTg4IDEwOC40NSA4Mi41OTMzIDEwOC4yNjkgODIuOTU0NkMxMDguMDg4IDgzLjMxMSAxMDcuODMgODMuNjE2MiAxMDcuNDkzIDgzLjg3MDFDMTA3LjE2MSA4NC4xMTkxIDEwNi43NiA4NC4zMTIgMTA2LjI5MiA4NC40NDg3QzEwNS44MjggODQuNTgwNiAxMDUuMzEgODQuNjQ2NSAxMDQuNzM5IDg0LjY0NjVDMTA0LjIyNiA4NC42NDY1IDEwMy43MjEgODQuNTc4MSAxMDMuMjIzIDg0LjQ0MTRDMTAyLjcyOSA4NC4zMDQ3IDEwMi4yOCA4NC4wOTcyIDEwMS44NzUgODMuODE4OEMxMDEuNDcgODMuNTM1NiAxMDEuMTQ3IDgzLjE4NDEgMTAwLjkwOCA4Mi43NjQyQzEwMC42NjkgODIuMzM5NCAxMDAuNTQ5IDgxLjg0MzggMTAwLjU0OSA4MS4yNzczSDEwMi4zODhDMTAyLjM4OCA4MS42MjQgMTAyLjQ0NiA4MS45MTk0IDEwMi41NjMgODIuMTYzNkMxMDIuNjg2IDgyLjQwNzcgMTAyLjg1NCA4Mi42MDc5IDEwMy4wNjkgODIuNzY0MkMxMDMuMjg0IDgyLjkxNTUgMTAzLjUzMyA4My4wMjc4IDEwMy44MTYgODMuMTAxMUMxMDQuMTA0IDgzLjE3NDMgMTA0LjQxMiA4My4yMTA5IDEwNC43MzkgODMuMjEwOUMxMDUuMTY4IDgzLjIxMDkgMTA1LjUyNyA4My4xNDk5IDEwNS44MTUgODMuMDI3OEMxMDYuMTA4IDgyLjkwNTggMTA2LjMyOCA4Mi43MzQ5IDEwNi40NzUgODIuNTE1MUMxMDYuNjIxIDgyLjI5NTQgMTA2LjY5NCA4Mi4wNDE1IDEwNi42OTQgODEuNzUzNFpNMTEyLjE0NCA4Mi43NDIyTDExMy45NzUgNzYuNTc1MkgxMTUuMTAzTDExNC43OTUgNzguNDIwOUwxMTIuOTQ5IDg0LjVIMTExLjkzOEwxMTIuMTQ0IDgyLjc0MjJaTTExMS4wNjcgNzYuNTc1MkwxMTIuNDk1IDgyLjc3MTVMMTEyLjYxMiA4NC41SDExMS40ODRMMTA5LjMzOCA3Ni41NzUySDExMS4wNjdaTTExNi44MTYgODIuNjk4MkwxMTguMjAxIDc2LjU3NTJIMTE5LjkyMkwxMTcuNzgzIDg0LjVIMTE2LjY1NUwxMTYuODE2IDgyLjY5ODJaTTExNS4yOTMgNzYuNTc1MkwxMTcuMTAyIDgyLjY2ODlMMTE3LjMyOSA4NC41SDExNi4zMThMMTE0LjQ1MSA3OC40MTM2TDExNC4xNDMgNzYuNTc1MkgxMTUuMjkzWk0xMjMuMDEzIDc2LjU3NTJWODQuNUgxMjEuMjRWNzYuNTc1MkgxMjMuMDEzWk0xMjEuMTIzIDc0LjQ5NTFDMTIxLjEyMyA3NC4yMjY2IDEyMS4yMTEgNzQuMDA0NCAxMjEuMzg3IDczLjgyODZDMTIxLjU2NyA3My42NDc5IDEyMS44MTYgNzMuNTU3NiAxMjIuMTM0IDczLjU1NzZDMTIyLjQ0NiA3My41NTc2IDEyMi42OTMgNzMuNjQ3OSAxMjIuODc0IDczLjgyODZDMTIzLjA1NCA3NC4wMDQ0IDEyMy4xNDUgNzQuMjI2NiAxMjMuMTQ1IDc0LjQ5NTFDMTIzLjE0NSA3NC43NTg4IDEyMy4wNTQgNzQuOTc4NSAxMjIuODc0IDc1LjE1NDNDMTIyLjY5MyA3NS4zMzAxIDEyMi40NDYgNzUuNDE4IDEyMi4xMzQgNzUuNDE4QzEyMS44MTYgNzUuNDE4IDEyMS41NjcgNzUuMzMwMSAxMjEuMzg3IDc1LjE1NDNDMTIxLjIxMSA3NC45Nzg1IDEyMS4xMjMgNzQuNzU4OCAxMjEuMTIzIDc0LjQ5NTFaTTEyOC41NzkgNzYuNTc1MlY3Ny44NjQzSDEyNC4xMTFWNzYuNTc1MkgxMjguNTc5Wk0xMjUuNCA3NC42MzQzSDEyNy4xNjZWODIuMzEwMUMxMjcuMTY2IDgyLjU1NDIgMTI3LjIgODIuNzQyMiAxMjcuMjY4IDgyLjg3NEMxMjcuMzQxIDgzLjAwMSAxMjcuNDQxIDgzLjA4NjQgMTI3LjU2OCA4My4xMzA0QzEyNy42OTUgODMuMTc0MyAxMjcuODQ0IDgzLjE5NjMgMTI4LjAxNSA4My4xOTYzQzEyOC4xMzcgODMuMTk2MyAxMjguMjU0IDgzLjE4OSAxMjguMzY3IDgzLjE3NDNDMTI4LjQ3OSA4My4xNTk3IDEyOC41NjkgODMuMTQ1IDEyOC42MzggODMuMTMwNEwxMjguNjQ1IDg0LjQ3OEMxMjguNDk5IDg0LjUyMiAxMjguMzI4IDg0LjU2MSAxMjguMTMyIDg0LjU5NTJDMTI3Ljk0MiA4NC42Mjk0IDEyNy43MjIgODQuNjQ2NSAxMjcuNDczIDg0LjY0NjVDMTI3LjA2OCA4NC42NDY1IDEyNi43MDkgODQuNTc1NyAxMjYuMzk2IDg0LjQzNDFDMTI2LjA4NCA4NC4yODc2IDEyNS44NCA4NC4wNTA4IDEyNS42NjQgODMuNzIzNkMxMjUuNDg4IDgzLjM5NjUgMTI1LjQgODIuOTYxOSAxMjUuNCA4Mi40MTk5Vjc0LjYzNDNaTTEzMy4xNzkgODMuMjQwMkMxMzMuNDY3IDgzLjI0MDIgMTMzLjcyNiA4My4xODQxIDEzMy45NTUgODMuMDcxOEMxMzQuMTg5IDgyLjk1NDYgMTM0LjM3NyA4Mi43OTM1IDEzNC41MTkgODIuNTg4NEMxMzQuNjY2IDgyLjM4MzMgMTM0Ljc0NiA4Mi4xNDY1IDEzNC43NjEgODEuODc3OUgxMzYuNDIzQzEzNi40MTQgODIuMzkwNiAxMzYuMjYyIDgyLjg1NjkgMTM1Ljk2OSA4My4yNzY5QzEzNS42NzYgODMuNjk2OCAxMzUuMjg4IDg0LjAzMTIgMTM0LjgwNSA4NC4yODAzQzEzNC4zMjEgODQuNTI0NCAxMzMuNzg3IDg0LjY0NjUgMTMzLjIwMSA4NC42NDY1QzEzMi41OTUgODQuNjQ2NSAxMzIuMDY4IDg0LjU0MzkgMTMxLjYxOSA4NC4zMzg5QzEzMS4xNjkgODQuMTI4OSAxMzAuNzk2IDgzLjg0MDggMTMwLjQ5OCA4My40NzQ2QzEzMC4yIDgzLjEwODQgMTI5Ljk3NiA4Mi42ODYgMTI5LjgyNCA4Mi4yMDc1QzEyOS42NzggODEuNzI5IDEyOS42MDQgODEuMjE2MyAxMjkuNjA0IDgwLjY2OTRWODAuNDEzMUMxMjkuNjA0IDc5Ljg2NjIgMTI5LjY3OCA3OS4zNTM1IDEyOS44MjQgNzguODc1QzEyOS45NzYgNzguMzkxNiAxMzAuMiA3Ny45NjY4IDEzMC40OTggNzcuNjAwNkMxMzAuNzk2IDc3LjIzNDQgMTMxLjE2OSA3Ni45NDg3IDEzMS42MTkgNzYuNzQzN0MxMzIuMDY4IDc2LjUzMzcgMTMyLjU5MyA3Ni40Mjg3IDEzMy4xOTMgNzYuNDI4N0MxMzMuODI4IDc2LjQyODcgMTM0LjM4NSA3Ni41NTU3IDEzNC44NjMgNzYuODA5NkMxMzUuMzQyIDc3LjA1ODYgMTM1LjcxOCA3Ny40MDc3IDEzNS45OTEgNzcuODU2OUMxMzYuMjcgNzguMzAxMyAxMzYuNDE0IDc4LjgxODggMTM2LjQyMyA3OS40MDk3SDEzNC43NjFDMTM0Ljc0NiA3OS4xMTY3IDEzNC42NzMgNzguODUzIDEzNC41NDEgNzguNjE4N0MxMzQuNDE0IDc4LjM3OTQgMTM0LjIzMyA3OC4xODkgMTMzLjk5OSA3OC4wNDc0QzEzMy43NyA3Ny45MDU4IDEzMy40OTQgNzcuODM1IDEzMy4xNzEgNzcuODM1QzEzMi44MTUgNzcuODM1IDEzMi41MiA3Ny45MDgyIDEzMi4yODUgNzguMDU0N0MxMzIuMDUxIDc4LjE5NjMgMTMxLjg2OCA3OC4zOTE2IDEzMS43MzYgNzguNjQwNkMxMzEuNjA0IDc4Ljg4NDggMTMxLjUwOSA3OS4xNjA2IDEzMS40NSA3OS40NjgzQzEzMS4zOTYgNzkuNzcxIDEzMS4zNyA4MC4wODU5IDEzMS4zNyA4MC40MTMxVjgwLjY2OTRDMTMxLjM3IDgwLjk5NjYgMTMxLjM5NiA4MS4zMTQgMTMxLjQ1IDgxLjYyMTZDMTMxLjUwNCA4MS45MjkyIDEzMS41OTcgODIuMjA1MSAxMzEuNzI5IDgyLjQ0OTJDMTMxLjg2NSA4Mi42ODg1IDEzMi4wNTEgODIuODgxMyAxMzIuMjg1IDgzLjAyNzhDMTMyLjUyIDgzLjE2OTQgMTMyLjgxNyA4My4yNDAyIDEzMy4xNzkgODMuMjQwMlpNMTM5LjUyMSA3My4yNVY4NC41SDEzNy43NjRWNzMuMjVIMTM5LjUyMVpNMTM5LjIxNCA4MC4yNDQ2TDEzOC42NDMgODAuMjM3M0MxMzguNjQ3IDc5LjY5MDQgMTM4LjcyMyA3OS4xODUxIDEzOC44NyA3OC43MjEyQzEzOS4wMjEgNzguMjU3MyAxMzkuMjMxIDc3Ljg1NDUgMTM5LjUgNzcuNTEyN0MxMzkuNzczIDc3LjE2NiAxNDAuMSA3Ni44OTk5IDE0MC40ODEgNzYuNzE0NEMxNDAuODYyIDc2LjUyMzkgMTQxLjI4NCA3Ni40Mjg3IDE0MS43NDggNzYuNDI4N0MxNDIuMTM5IDc2LjQyODcgMTQyLjQ5IDc2LjQ4MjQgMTQyLjgwMyA3Ni41ODk4QzE0My4xMiA3Ni42OTczIDE0My4zOTQgNzYuODcwNiAxNDMuNjIzIDc3LjEwOTlDMTQzLjg1MyA3Ny4zNDQyIDE0NC4wMjYgNzcuNjUxOSAxNDQuMTQzIDc4LjAzMjdDMTQ0LjI2NSA3OC40MDg3IDE0NC4zMjYgNzguODY3NyAxNDQuMzI2IDc5LjQwOTdWODQuNUgxNDIuNTU0Vjc5LjM5NUMxNDIuNTU0IDc5LjAxNDIgMTQyLjQ5OCA3OC43MTE0IDE0Mi4zODUgNzguNDg2OEMxNDIuMjc4IDc4LjI2MjIgMTQyLjExOSA3OC4xMDExIDE0MS45MDkgNzguMDAzNEMxNDEuNjk5IDc3LjkwMDkgMTQxLjQ0MyA3Ny44NDk2IDE0MS4xNCA3Ny44NDk2QzE0MC44MjMgNzcuODQ5NiAxNDAuNTQyIDc3LjkxMzEgMTQwLjI5OCA3OC4wNEMxNDAuMDU5IDc4LjE2NyAxMzkuODU4IDc4LjM0MDMgMTM5LjY5NyA3OC41NjAxQzEzOS41MzYgNzguNzc5OCAxMzkuNDE0IDc5LjAzMzcgMTM5LjMzMSA3OS4zMjE4QzEzOS4yNTMgNzkuNjA5OSAxMzkuMjE0IDc5LjkxNzUgMTM5LjIxNCA4MC4yNDQ2WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC44NyIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzM2MzlfMTU1Njc0Ij4KPHJlY3QgeD0iMC41IiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgcng9IjQiIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==", + "image": "tb-image:c2luZ2xlLXN3aXRjaC5zdmc=:IlNpbmdsZSBTd2l0Y2giIHN5c3RlbSB3aWRnZXQgaW1hZ2U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiByeD0iNCIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iMzEuNTc3MSIgeT0iNjUuMDc0MiIgd2lkdGg9IjU2Ljk5MDEiIGhlaWdodD0iMjkuODUyIiByeD0iMTQuOTI2IiBmaWxsPSIjNTQ2OUZGIi8+CjxjaXJjbGUgY3g9IjczLjY0MDkiIGN5PSI4MC4wMDAzIiByPSIxMi4yMTIyIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTA5LjI0MSA4My40NzE3QzEwOS4yNDEgODMuMTQ5NCAxMDkuMTkxIDgyLjg2MyAxMDkuMDkxIDgyLjYxMjNDMTA4Ljk5OCA4Mi4zNjE3IDEwOC44MjkgODIuMTMyNSAxMDguNTg2IDgxLjkyNDhDMTA4LjM0MiA4MS43MTcxIDEwNy45OTkgODEuNTE2NiAxMDcuNTU1IDgxLjMyMzJDMTA3LjExOCA4MS4xMjI3IDEwNi41NTkgODAuOTE4NiAxMDUuODc5IDgwLjcxMDlDMTA1LjEzNCA4MC40ODE4IDEwNC40NDcgODAuMjI3NSAxMDMuODE2IDc5Ljk0ODJDMTAzLjE5MyA3OS42NjE4IDEwMi42NDkgNzkuMzMyNCAxMDIuMTg0IDc4Ljk2QzEwMS43MTggNzguNTgwNCAxMDEuMzU2IDc4LjE0NzEgMTAxLjA5OSA3Ny42NjAyQzEwMC44NDEgNzcuMTY2IDEwMC43MTIgNzYuNTk2NyAxMDAuNzEyIDc1Ljk1MjFDMTAwLjcxMiA3NS4zMTQ4IDEwMC44NDQgNzQuNzM0NyAxMDEuMTA5IDc0LjIxMTlDMTAxLjM4MiA3My42ODkxIDEwMS43NjUgNzMuMjM4IDEwMi4yNTkgNzIuODU4NEMxMDIuNzYgNzIuNDcxNyAxMDMuMzUxIDcyLjE3NDUgMTA0LjAzMSA3MS45NjY4QzEwNC43MTIgNzEuNzUyIDEwNS40NjQgNzEuNjQ0NSAxMDYuMjg3IDcxLjY0NDVDMTA3LjQ0NyA3MS42NDQ1IDEwOC40NDYgNzEuODU5NCAxMDkuMjg0IDcyLjI4OTFDMTEwLjEyOSA3Mi43MTg4IDExMC43NzcgNzMuMjk1MiAxMTEuMjI5IDc0LjAxODZDMTExLjY4NyA3NC43NDE5IDExMS45MTYgNzUuNTQwNCAxMTEuOTE2IDc2LjQxNDFIMTA5LjI0MUMxMDkuMjQxIDc1Ljg5ODQgMTA5LjEzIDc1LjQ0MzcgMTA4LjkwOCA3NS4wNDk4QzEwOC42OTMgNzQuNjQ4OCAxMDguMzY0IDc0LjMzMzcgMTA3LjkyIDc0LjEwNDVDMTA3LjQ4MyA3My44NzUzIDEwNi45MjggNzMuNzYwNyAxMDYuMjU1IDczLjc2MDdDMTA1LjYxOCA3My43NjA3IDEwNS4wODggNzMuODU3NCAxMDQuNjY1IDc0LjA1MDhDMTA0LjI0MyA3NC4yNDQxIDEwMy45MjcgNzQuNTA1NSAxMDMuNzIgNzQuODM1QzEwMy41MTIgNzUuMTY0NCAxMDMuNDA4IDc1LjUzNjggMTAzLjQwOCA3NS45NTIxQzEwMy40MDggNzYuMjQ1OCAxMDMuNDc2IDc2LjUxNDMgMTAzLjYxMiA3Ni43NTc4QzEwMy43NDggNzYuOTk0MSAxMDMuOTU2IDc3LjIxNjEgMTA0LjIzNSA3Ny40MjM4QzEwNC41MTUgNzcuNjI0MyAxMDQuODY2IDc3LjgxNDEgMTA1LjI4OCA3Ny45OTMyQzEwNS43MTEgNzguMTcyMiAxMDYuMjA4IDc4LjM0NDEgMTA2Ljc4MSA3OC41MDg4QzEwNy42NDggNzguNzY2NiAxMDguNDAzIDc5LjA1MzEgMTA5LjA0OCA3OS4zNjgyQzEwOS42OTIgNzkuNjc2MSAxMTAuMjI5IDgwLjAyNyAxMTAuNjU5IDgwLjQyMDlDMTExLjA4OSA4MC44MTQ4IDExMS40MTEgODEuMjYyNCAxMTEuNjI2IDgxLjc2MzdDMTExLjg0MSA4Mi4yNTc4IDExMS45NDggODIuODIgMTExLjk0OCA4My40NTAyQzExMS45NDggODQuMTA5IDExMS44MTYgODQuNzAzNSAxMTEuNTUxIDg1LjIzMzRDMTExLjI4NiA4NS43NTYyIDExMC45MDYgODYuMjAzOCAxMTAuNDEyIDg2LjU3NjJDMTA5LjkyNSA4Ni45NDE0IDEwOS4zMzggODcuMjI0MyAxMDguNjUgODcuNDI0OEMxMDcuOTcgODcuNjE4MiAxMDcuMjExIDg3LjcxNDggMTA2LjM3MyA4Ny43MTQ4QzEwNS42MjEgODcuNzE0OCAxMDQuODggODcuNjE0NiAxMDQuMTQ5IDg3LjQxNDFDMTAzLjQyNiA4Ny4yMTM1IDEwMi43NjcgODYuOTA5MiAxMDIuMTczIDg2LjUwMUMxMDEuNTc4IDg2LjA4NTYgMTAxLjEwNiA4NS41NyAxMDAuNzU1IDg0Ljk1NDFDMTAwLjQwNCA4NC4zMzExIDEwMC4yMjkgODMuNjA0MiAxMDAuMjI5IDgyLjc3MzRIMTAyLjkyNUMxMDIuOTI1IDgzLjI4MTkgMTAzLjAxMSA4My43MTUyIDEwMy4xODMgODQuMDczMkMxMDMuMzYyIDg0LjQzMTMgMTAzLjYwOSA4NC43MjQ5IDEwMy45MjQgODQuOTU0MUMxMDQuMjM5IDg1LjE3NjEgMTA0LjYwNCA4NS4zNDA4IDEwNS4wMiA4NS40NDgyQzEwNS40NDIgODUuNTU1NyAxMDUuODkzIDg1LjYwOTQgMTA2LjM3MyA4NS42MDk0QzEwNy4wMDMgODUuNjA5NCAxMDcuNTMgODUuNTE5OSAxMDcuOTUyIDg1LjM0MDhDMTA4LjM4MiA4NS4xNjE4IDEwOC43MDQgODQuOTExMSAxMDguOTE5IDg0LjU4ODlDMTA5LjEzNCA4NC4yNjY2IDEwOS4yNDEgODMuODk0MiAxMDkuMjQxIDgzLjQ3MTdaTTExNy41NzcgODQuOTIxOUwxMjAuMjYzIDc1Ljg3N0gxMjEuOTE3TDEyMS40NjYgNzguNTg0TDExOC43NTkgODcuNUgxMTcuMjc2TDExNy41NzcgODQuOTIxOVpNMTE1Ljk5OCA3NS44NzdMMTE4LjA5MyA4NC45NjQ4TDExOC4yNjUgODcuNUgxMTYuNjFMMTEzLjQ2MyA3NS44NzdIMTE1Ljk5OFpNMTI0LjQzMSA4NC44NTc0TDEyNi40NjEgNzUuODc3SDEyOC45ODVMMTI1Ljg0OSA4Ny41SDEyNC4xOTRMMTI0LjQzMSA4NC44NTc0Wk0xMjIuMTk2IDc1Ljg3N0wxMjQuODUgODQuODE0NUwxMjUuMTgzIDg3LjVIMTIzLjdMMTIwLjk2MSA3OC41NzMyTDEyMC41MSA3NS44NzdIMTIyLjE5NlpNMTMzLjg2MiA3NS44NzdWODcuNUgxMzEuMjYzVjc1Ljg3N0gxMzMuODYyWk0xMzEuMDkxIDcyLjgyNjJDMTMxLjA5MSA3Mi40MzIzIDEzMS4yMiA3Mi4xMDY0IDEzMS40NzggNzEuODQ4NkMxMzEuNzQzIDcxLjU4MzcgMTMyLjEwOCA3MS40NTEyIDEzMi41NzMgNzEuNDUxMkMxMzMuMDMyIDcxLjQ1MTIgMTMzLjM5MyA3MS41ODM3IDEzMy42NTggNzEuODQ4NkMxMzMuOTIzIDcyLjEwNjQgMTM0LjA1NiA3Mi40MzIzIDEzNC4wNTYgNzIuODI2MkMxMzQuMDU2IDczLjIxMjkgMTMzLjkyMyA3My41MzUyIDEzMy42NTggNzMuNzkzQzEzMy4zOTMgNzQuMDUwOCAxMzMuMDMyIDc0LjE3OTcgMTMyLjU3MyA3NC4xNzk3QzEzMi4xMDggNzQuMTc5NyAxMzEuNzQzIDc0LjA1MDggMTMxLjQ3OCA3My43OTNDMTMxLjIyIDczLjUzNTIgMTMxLjA5MSA3My4yMTI5IDEzMS4wOTEgNzIuODI2MlpNMTQyLjM3IDc1Ljg3N1Y3Ny43Njc2SDEzNS44MTdWNzUuODc3SDE0Mi4zN1pNMTM3LjcwOCA3My4wMzAzSDE0MC4yOTdWODQuMjg4MUMxNDAuMjk3IDg0LjY0NjIgMTQwLjM0NyA4NC45MjE5IDE0MC40NDcgODUuMTE1MkMxNDAuNTU1IDg1LjMwMTQgMTQwLjcwMSA4NS40MjY4IDE0MC44ODggODUuNDkxMkMxNDEuMDc0IDg1LjU1NTcgMTQxLjI5MiA4NS41ODc5IDE0MS41NDMgODUuNTg3OUMxNDEuNzIyIDg1LjU4NzkgMTQxLjg5NCA4NS41NzcxIDE0Mi4wNTkgODUuNTU1N0MxNDIuMjIzIDg1LjUzNDIgMTQyLjM1NiA4NS41MTI3IDE0Mi40NTYgODUuNDkxMkwxNDIuNDY3IDg3LjQ2NzhDMTQyLjI1MiA4Ny41MzIyIDE0Mi4wMDEgODcuNTg5NSAxNDEuNzE1IDg3LjYzOTZDMTQxLjQzNiA4Ny42ODk4IDE0MS4xMTMgODcuNzE0OCAxNDAuNzQ4IDg3LjcxNDhDMTQwLjE1NCA4Ny43MTQ4IDEzOS42MjcgODcuNjExIDEzOS4xNjkgODcuNDAzM0MxMzguNzExIDg3LjE4ODUgMTM4LjM1MyA4Ni44NDExIDEzOC4wOTUgODYuMzYxM0MxMzcuODM3IDg1Ljg4MTUgMTM3LjcwOCA4NS4yNDQxIDEzNy43MDggODQuNDQ5MlY3My4wMzAzWk0xNDkuNDYgODUuNjUyM0MxNDkuODgyIDg1LjY1MjMgMTUwLjI2MiA4NS41NyAxNTAuNTk5IDg1LjQwNTNDMTUwLjk0MiA4NS4yMzM0IDE1MS4yMTggODQuOTk3MSAxNTEuNDI2IDg0LjY5NjNDMTUxLjY0MSA4NC4zOTU1IDE1MS43NTkgODQuMDQ4MiAxNTEuNzggODMuNjU0M0gxNTQuMjE5QzE1NC4yMDQgODQuNDA2MiAxNTMuOTgyIDg1LjA5MDIgMTUzLjU1MyA4NS43MDYxQzE1My4xMjMgODYuMzIxOSAxNTIuNTU0IDg2LjgxMjUgMTUxLjg0NSA4Ny4xNzc3QzE1MS4xMzYgODcuNTM1OCAxNTAuMzUyIDg3LjcxNDggMTQ5LjQ5MiA4Ny43MTQ4QzE0OC42MDQgODcuNzE0OCAxNDcuODMxIDg3LjU2NDUgMTQ3LjE3MiA4Ny4yNjM3QzE0Ni41MTMgODYuOTU1NyAxNDUuOTY1IDg2LjUzMzIgMTQ1LjUyOCA4NS45OTYxQzE0NS4wOTEgODUuNDU5IDE0NC43NjIgODQuODM5NSAxNDQuNTQgODQuMTM3N0MxNDQuMzI1IDgzLjQzNTkgMTQ0LjIxOCA4Mi42ODM5IDE0NC4yMTggODEuODgxOFY4MS41MDU5QzE0NC4yMTggODAuNzAzOCAxNDQuMzI1IDc5Ljk1MTggMTQ0LjU0IDc5LjI1QzE0NC43NjIgNzguNTQxIDE0NS4wOTEgNzcuOTE4IDE0NS41MjggNzcuMzgwOUMxNDUuOTY1IDc2Ljg0MzggMTQ2LjUxMyA3Ni40MjQ4IDE0Ny4xNzIgNzYuMTI0QzE0Ny44MzEgNzUuODE2MSAxNDguNjAxIDc1LjY2MjEgMTQ5LjQ4MSA3NS42NjIxQzE1MC40MTIgNzUuNjYyMSAxNTEuMjI5IDc1Ljg0ODMgMTUxLjkzMSA3Ni4yMjA3QzE1Mi42MzIgNzYuNTg1OSAxNTMuMTg0IDc3LjA5OCAxNTMuNTg1IDc3Ljc1NjhDMTUzLjk5MyA3OC40MDg1IDE1NC4yMDQgNzkuMTY3NiAxNTQuMjE5IDgwLjAzNDJIMTUxLjc4QzE1MS43NTkgNzkuNjA0NSAxNTEuNjUxIDc5LjIxNzggMTUxLjQ1OCA3OC44NzRDMTUxLjI3MiA3OC41MjMxIDE1MS4wMDcgNzguMjQzOCAxNTAuNjYzIDc4LjAzNjFDMTUwLjMyNiA3Ny44Mjg1IDE0OS45MjIgNzcuNzI0NiAxNDkuNDQ5IDc3LjcyNDZDMTQ4LjkyNiA3Ny43MjQ2IDE0OC40OTMgNzcuODMyIDE0OC4xNDkgNzguMDQ2OUMxNDcuODA2IDc4LjI1NDYgMTQ3LjUzNyA3OC41NDEgMTQ3LjM0NCA3OC45MDYyQzE0Ny4xNSA3OS4yNjQzIDE0Ny4wMTEgNzkuNjY4OSAxNDYuOTI1IDgwLjEyMDFDMTQ2Ljg0NiA4MC41NjQxIDE0Ni44MDcgODEuMDI2IDE0Ni44MDcgODEuNTA1OVY4MS44ODE4QzE0Ni44MDcgODIuMzYxNyAxNDYuODQ2IDgyLjgyNzEgMTQ2LjkyNSA4My4yNzgzQzE0Ny4wMDQgODMuNzI5NSAxNDcuMTQgODQuMTM0MSAxNDcuMzMzIDg0LjQ5MjJDMTQ3LjUzNCA4NC44NDMxIDE0Ny44MDYgODUuMTI2IDE0OC4xNDkgODUuMzQwOEMxNDguNDkzIDg1LjU0ODUgMTQ4LjkzIDg1LjY1MjMgMTQ5LjQ2IDg1LjY1MjNaTTE1OS4xMDYgNzFWODcuNUgxNTYuNTI4VjcxSDE1OS4xMDZaTTE1OC42NTUgODEuMjU4OEwxNTcuODE3IDgxLjI0OEMxNTcuODI1IDgwLjQ0NiAxNTcuOTM2IDc5LjcwNDggMTU4LjE1IDc5LjAyNDRDMTU4LjM3MiA3OC4zNDQxIDE1OC42OCA3Ny43NTMzIDE1OS4wNzQgNzcuMjUyQzE1OS40NzUgNzYuNzQzNSAxNTkuOTU1IDc2LjM1MzIgMTYwLjUxNCA3Ni4wODExQzE2MS4wNzIgNzUuODAxOCAxNjEuNjkyIDc1LjY2MjEgMTYyLjM3MiA3NS42NjIxQzE2Mi45NDUgNzUuNjYyMSAxNjMuNDYxIDc1Ljc0MDkgMTYzLjkxOSA3NS44OTg0QzE2NC4zODQgNzYuMDU2IDE2NC43ODUgNzYuMzEwMiAxNjUuMTIyIDc2LjY2MTFDMTY1LjQ1OSA3Ny4wMDQ5IDE2NS43MTMgNzcuNDU2MSAxNjUuODg1IDc4LjAxNDZDMTY2LjA2NCA3OC41NjYxIDE2Ni4xNTMgNzkuMjM5MyAxNjYuMTUzIDgwLjAzNDJWODcuNUgxNjMuNTU0VjgwLjAxMjdDMTYzLjU1NCA3OS40NTQxIDE2My40NzEgNzkuMDEwMSAxNjMuMzA3IDc4LjY4MDdDMTYzLjE0OSA3OC4zNTEyIDE2Mi45MTYgNzguMTE0OSAxNjIuNjA4IDc3Ljk3MTdDMTYyLjMgNzcuODIxMyAxNjEuOTI0IDc3Ljc0NjEgMTYxLjQ4IDc3Ljc0NjFDMTYxLjAxNSA3Ny43NDYxIDE2MC42MDMgNzcuODM5MiAxNjAuMjQ1IDc4LjAyNTRDMTU5Ljg5NCA3OC4yMTE2IDE1OS42MDEgNzguNDY1OCAxNTkuMzY0IDc4Ljc4ODFDMTU5LjEyOCA3OS4xMTA0IDE1OC45NDkgNzkuNDgyNyAxNTguODI3IDc5LjkwNTNDMTU4LjcxMyA4MC4zMjc4IDE1OC42NTUgODAuNzc5IDE1OC42NTUgODEuMjU4OFoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNzYiLz4KPC9zdmc+Cg==", "description": "Sends the command to the device or updates attribute/time-series when the user toggles the slider. Widget settings will enable you to configure behavior how to fetch the initial state and what to trigger when turn on/off states.", "descriptor": { "type": "rpc",