Merge remote-tracking branch 'upstream/develop/3.5' into feature/notification-system
This commit is contained in:
commit
eea6e5a5e6
@ -422,3 +422,190 @@ END
|
|||||||
$$;
|
$$;
|
||||||
|
|
||||||
-- ALARM FUNCTIONS END
|
-- ALARM FUNCTIONS END
|
||||||
|
|
||||||
|
-- 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
|
||||||
@ -139,6 +139,9 @@ public class AlarmController extends BaseController {
|
|||||||
checkNotNull(alarm.getOriginator());
|
checkNotNull(alarm.getOriginator());
|
||||||
checkEntity(alarm.getId(), alarm, Resource.ALARM);
|
checkEntity(alarm.getId(), alarm, Resource.ALARM);
|
||||||
checkEntityId(alarm.getOriginator(), Operation.READ);
|
checkEntityId(alarm.getOriginator(), Operation.READ);
|
||||||
|
if (alarm.getAssigneeId() != null) {
|
||||||
|
checkUserId(alarm.getAssigneeId(), Operation.READ);
|
||||||
|
}
|
||||||
return tbAlarmService.save(alarm, getCurrentUser());
|
return tbAlarmService.save(alarm, getCurrentUser());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -76,7 +76,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
|
|||||||
UserId newAssignee = alarm.getAssigneeId();
|
UserId newAssignee = alarm.getAssigneeId();
|
||||||
UserId curAssignee = resultAlarm.getAssigneeId();
|
UserId curAssignee = resultAlarm.getAssigneeId();
|
||||||
if (newAssignee != null && !newAssignee.equals(curAssignee)) {
|
if (newAssignee != null && !newAssignee.equals(curAssignee)) {
|
||||||
resultAlarm = assign(alarm, newAssignee, alarm.getAssignTs(), user);
|
resultAlarm = assign(resultAlarm, newAssignee, alarm.getAssignTs(), user);
|
||||||
} else if (newAssignee == null && curAssignee != null) {
|
} else if (newAssignee == null && curAssignee != null) {
|
||||||
resultAlarm = unassign(alarm, alarm.getAssignTs(), user);
|
resultAlarm = unassign(alarm, alarm.getAssignTs(), user);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -357,6 +357,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
|
|||||||
protected Tenant savedDifferentTenant;
|
protected Tenant savedDifferentTenant;
|
||||||
protected User savedDifferentTenantUser;
|
protected User savedDifferentTenantUser;
|
||||||
private Customer savedDifferentCustomer;
|
private Customer savedDifferentCustomer;
|
||||||
|
protected User differentCustomerUser;
|
||||||
|
|
||||||
protected void loginDifferentTenant() throws Exception {
|
protected void loginDifferentTenant() throws Exception {
|
||||||
if (savedDifferentTenant != null) {
|
if (savedDifferentTenant != null) {
|
||||||
@ -387,7 +388,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
|
|||||||
createDifferentCustomer();
|
createDifferentCustomer();
|
||||||
|
|
||||||
loginTenantAdmin();
|
loginTenantAdmin();
|
||||||
User differentCustomerUser = new User();
|
differentCustomerUser = new User();
|
||||||
differentCustomerUser.setAuthority(Authority.CUSTOMER_USER);
|
differentCustomerUser.setAuthority(Authority.CUSTOMER_USER);
|
||||||
differentCustomerUser.setTenantId(tenantId);
|
differentCustomerUser.setTenantId(tenantId);
|
||||||
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
|
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
|
||||||
|
|||||||
@ -656,4 +656,38 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
|
|||||||
|
|
||||||
return foundAlarm;
|
return foundAlarm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateAlarmWithOtherTenantsAssignee() throws Exception {
|
||||||
|
loginDifferentTenant();
|
||||||
|
loginTenantAdmin();
|
||||||
|
|
||||||
|
Alarm alarm = Alarm.builder()
|
||||||
|
.tenantId(tenantId)
|
||||||
|
.customerId(customerId)
|
||||||
|
.originator(customerDevice.getId())
|
||||||
|
.severity(AlarmSeverity.CRITICAL)
|
||||||
|
.assigneeId(savedDifferentTenantUser.getId())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
doPost("/api/alarm", alarm).andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateAlarmWithOtherCustomerAsAssignee() throws Exception {
|
||||||
|
loginDifferentCustomer();
|
||||||
|
loginCustomerUser();
|
||||||
|
|
||||||
|
Alarm alarm = Alarm.builder()
|
||||||
|
.tenantId(tenantId)
|
||||||
|
.customerId(customerId)
|
||||||
|
.originator(customerDevice.getId())
|
||||||
|
.severity(AlarmSeverity.CRITICAL)
|
||||||
|
.assigneeId(differentCustomerUser.getId())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
doPost("/api/alarm", alarm).andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,14 +99,16 @@ public class JpaSqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cleanup(long systemTtl) {
|
public void cleanup(long systemTtl) {
|
||||||
|
if (systemTtl > 0) {
|
||||||
cleanupPartitions(systemTtl);
|
cleanupPartitions(systemTtl);
|
||||||
|
}
|
||||||
super.cleanup(systemTtl);
|
super.cleanup(systemTtl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanupPartitions(long systemTtl) {
|
private void cleanupPartitions(long systemTtl) {
|
||||||
log.info("Going to cleanup old timeseries data partitions using partition type: {} and ttl: {}s", partitioning, systemTtl);
|
log.info("Going to cleanup old timeseries data partitions using partition type: {} and ttl: {}s", partitioning, systemTtl);
|
||||||
try (Connection connection = dataSource.getConnection();
|
try (Connection connection = dataSource.getConnection();
|
||||||
PreparedStatement stmt = connection.prepareStatement("call drop_partitions_by_max_ttl(?,?,?)")) {
|
PreparedStatement stmt = connection.prepareStatement("call drop_partitions_by_system_ttl(?,?,?)")) {
|
||||||
stmt.setString(1, partitioning);
|
stmt.setString(1, partitioning);
|
||||||
stmt.setLong(2, systemTtl);
|
stmt.setLong(2, systemTtl);
|
||||||
stmt.setLong(3, 0);
|
stmt.setLong(3, 0);
|
||||||
|
|||||||
@ -34,13 +34,10 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary
|
|||||||
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
|
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint)
|
CREATE OR REPLACE PROCEDURE drop_partitions_by_system_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint)
|
||||||
LANGUAGE plpgsql AS
|
LANGUAGE plpgsql AS
|
||||||
$$
|
$$
|
||||||
DECLARE
|
DECLARE
|
||||||
max_tenant_ttl bigint;
|
|
||||||
max_customer_ttl bigint;
|
|
||||||
max_ttl bigint;
|
|
||||||
date timestamp;
|
date timestamp;
|
||||||
partition_by_max_ttl_date varchar;
|
partition_by_max_ttl_date varchar;
|
||||||
partition_by_max_ttl_month varchar;
|
partition_by_max_ttl_month varchar;
|
||||||
@ -52,20 +49,9 @@ DECLARE
|
|||||||
partition_day integer;
|
partition_day integer;
|
||||||
|
|
||||||
BEGIN
|
BEGIN
|
||||||
SELECT max(attribute_kv.long_v)
|
if system_ttl IS NOT NULL AND system_ttl > 0 THEN
|
||||||
FROM tenant
|
date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - system_ttl);
|
||||||
INNER JOIN attribute_kv ON tenant.id = attribute_kv.entity_id
|
partition_by_max_ttl_date := get_partition_by_system_ttl_date(partition_type, date);
|
||||||
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 'Date by max ttl: %', date;
|
||||||
RAISE NOTICE 'Partition by max ttl: %', partition_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
|
IF partition_by_max_ttl_date IS NOT NULL THEN
|
||||||
@ -203,7 +189,7 @@ BEGIN
|
|||||||
END
|
END
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION get_partition_by_max_ttl_date(IN partition_type varchar, IN date timestamp, OUT partition varchar) AS
|
CREATE OR REPLACE FUNCTION get_partition_by_system_ttl_date(IN partition_type varchar, IN date timestamp, OUT partition varchar) AS
|
||||||
$$
|
$$
|
||||||
BEGIN
|
BEGIN
|
||||||
CASE
|
CASE
|
||||||
|
|||||||
@ -421,6 +421,13 @@ export class MenuService {
|
|||||||
type: 'link',
|
type: 'link',
|
||||||
path: '/customers',
|
path: '/customers',
|
||||||
icon: 'supervisor_account'
|
icon: 'supervisor_account'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: guid(),
|
||||||
|
name: 'rulechain.rulechains',
|
||||||
|
type: 'link',
|
||||||
|
path: '/ruleChains',
|
||||||
|
icon: 'settings_ethernet'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (authState.edgesSupportEnabled) {
|
if (authState.edgesSupportEnabled) {
|
||||||
@ -458,13 +465,6 @@ export class MenuService {
|
|||||||
path: '/features',
|
path: '/features',
|
||||||
icon: 'construction',
|
icon: 'construction',
|
||||||
pages: [
|
pages: [
|
||||||
{
|
|
||||||
id: guid(),
|
|
||||||
name: 'rulechain.rulechains',
|
|
||||||
type: 'link',
|
|
||||||
path: '/features/ruleChains',
|
|
||||||
icon: 'settings_ethernet'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: guid(),
|
id: guid(),
|
||||||
name: 'ota-update.ota-updates',
|
name: 'ota-update.ota-updates',
|
||||||
|
|||||||
@ -17,7 +17,6 @@
|
|||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { Authority } from '@shared/models/authority.enum';
|
import { Authority } from '@shared/models/authority.enum';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { ruleChainsRoutes } from '@home/pages/rulechain/rulechain-routing.module';
|
|
||||||
import { otaUpdatesRoutes } from '@home/pages/ota-update/ota-update-routing.module';
|
import { otaUpdatesRoutes } from '@home/pages/ota-update/ota-update-routing.module';
|
||||||
import { vcRoutes } from '@home/pages/vc/vc-routing.module';
|
import { vcRoutes } from '@home/pages/vc/vc-routing.module';
|
||||||
|
|
||||||
@ -37,10 +36,9 @@ const routes: Routes = [
|
|||||||
children: [],
|
children: [],
|
||||||
data: {
|
data: {
|
||||||
auth: [Authority.TENANT_ADMIN],
|
auth: [Authority.TENANT_ADMIN],
|
||||||
redirectTo: '/features/ruleChains'
|
redirectTo: '/features/otaUpdates'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...ruleChainsRoutes,
|
|
||||||
...otaUpdatesRoutes,
|
...otaUpdatesRoutes,
|
||||||
...vcRoutes
|
...vcRoutes
|
||||||
]
|
]
|
||||||
|
|||||||
@ -122,7 +122,7 @@ export const importRuleChainBreadcumbLabelFunction: BreadCrumbLabelFunction<Rule
|
|||||||
return `${translate.instant('rulechain.import')}: ${component.ruleChain.name}`;
|
return `${translate.instant('rulechain.import')}: ${component.ruleChain.name}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ruleChainsRoutes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'ruleChains',
|
path: 'ruleChains',
|
||||||
data: {
|
data: {
|
||||||
@ -189,24 +189,6 @@ export const ruleChainsRoutes: Routes = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: 'ruleChains',
|
|
||||||
pathMatch: 'full',
|
|
||||||
redirectTo: '/features/ruleChains'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'ruleChains/:ruleChainId',
|
|
||||||
pathMatch: 'full',
|
|
||||||
redirectTo: '/features/ruleChains/:ruleChainId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'ruleChains/ruleChain/import',
|
|
||||||
pathMatch: 'full',
|
|
||||||
redirectTo: '/features/ruleChains/ruleChain/import'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// @dynamic
|
// @dynamic
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
|||||||
@ -15,21 +15,21 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
-->
|
-->
|
||||||
<form [formGroup]="timewindowForm" (ngSubmit)="update()">
|
<form [formGroup]="timewindowForm" class="mat-content">
|
||||||
<fieldset [disabled]="(isLoading$ | async)">
|
<mat-tab-group [ngClass]="{'tb-headless': historyOnly}"
|
||||||
<div class="mat-content" fxLayout="column">
|
|
||||||
<mat-tab-group dynamicHeight [ngClass]="{'tb-headless': historyOnly}"
|
|
||||||
(selectedIndexChange)="timewindowForm.markAsDirty()" [(selectedIndex)]="timewindow.selectedTab">
|
(selectedIndexChange)="timewindowForm.markAsDirty()" [(selectedIndex)]="timewindow.selectedTab">
|
||||||
<mat-tab label="{{ 'timewindow.realtime' | translate }}">
|
<mat-tab label="{{ 'timewindow.realtime' | translate }}">
|
||||||
<section fxLayout="row">
|
<section fxLayout="row">
|
||||||
<section *ngIf="isEdit" fxLayout="column" fxLayoutAlign="start center" style="padding-top: 8px; padding-left: 16px;">
|
<section *ngIf="isEdit" fxLayout="column" fxLayoutAlign="start center"
|
||||||
|
style="padding-top: 8px; padding-left: 16px;">
|
||||||
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
||||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval"
|
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval"
|
||||||
(ngModelChange)="onHideIntervalChanged()"></mat-checkbox>
|
(ngModelChange)="onHideIntervalChanged()"></mat-checkbox>
|
||||||
</section>
|
</section>
|
||||||
<section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideInterval">
|
<section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideInterval">
|
||||||
<div formGroupName="realtime" class="mat-content mat-padding" style="padding-top: 8px;">
|
<div formGroupName="realtime" class="mat-content mat-padding" style="padding-top: 8px;">
|
||||||
<mat-radio-group *ngIf="!quickIntervalOnly" [fxShow]="isEdit || (!timewindow.hideLastInterval && !timewindow.hideQuickInterval)"
|
<mat-radio-group *ngIf="!quickIntervalOnly"
|
||||||
|
[fxShow]="isEdit || (!timewindow.hideLastInterval && !timewindow.hideQuickInterval)"
|
||||||
formControlName="realtimeType">
|
formControlName="realtimeType">
|
||||||
<mat-radio-button [value]="realtimeTypes.LAST_INTERVAL" color="primary">
|
<mat-radio-button [value]="realtimeTypes.LAST_INTERVAL" color="primary">
|
||||||
<section fxLayout="row">
|
<section fxLayout="row">
|
||||||
@ -75,7 +75,8 @@
|
|||||||
predefinedName="timewindow.last"
|
predefinedName="timewindow.last"
|
||||||
required
|
required
|
||||||
style="padding-top: 8px;"></tb-timeinterval>
|
style="padding-top: 8px;"></tb-timeinterval>
|
||||||
<tb-quick-time-interval *ngIf="quickIntervalOnly || !isEdit && timewindow.hideLastInterval && !timewindow.hideQuickInterval"
|
<tb-quick-time-interval
|
||||||
|
*ngIf="quickIntervalOnly || !isEdit && timewindow.hideLastInterval && !timewindow.hideQuickInterval"
|
||||||
formControlName="quickInterval"
|
formControlName="quickInterval"
|
||||||
onlyCurrentInterval="true"
|
onlyCurrentInterval="true"
|
||||||
required
|
required
|
||||||
@ -83,10 +84,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
<ng-container *ngTemplateOutlet="additionalData">
|
||||||
|
</ng-container>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab label="{{ 'timewindow.history' | translate }}">
|
<mat-tab label="{{ 'timewindow.history' | translate }}">
|
||||||
<section fxLayout="row">
|
<section fxLayout="row">
|
||||||
<section *ngIf="isEdit" fxLayout="column" fxLayoutAlign="start center" style="padding-top: 8px; padding-left: 16px;">
|
<section *ngIf="isEdit" fxLayout="column" fxLayoutAlign="start center"
|
||||||
|
style="padding-top: 8px; padding-left: 16px;">
|
||||||
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
||||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval"
|
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval"
|
||||||
(ngModelChange)="onHideIntervalChanged()"></mat-checkbox>
|
(ngModelChange)="onHideIntervalChanged()"></mat-checkbox>
|
||||||
@ -135,8 +139,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
<ng-container *ngTemplateOutlet="additionalData">
|
||||||
|
</ng-container>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
|
<ng-template #additionalData>
|
||||||
<div *ngIf="aggregation" formGroupName="aggregation" class="mat-content mat-padding" fxLayout="column">
|
<div *ngIf="aggregation" formGroupName="aggregation" class="mat-content mat-padding" fxLayout="column">
|
||||||
<section fxLayout="row">
|
<section fxLayout="row">
|
||||||
<section fxLayout="column" fxLayoutAlign="start center" [fxShow]="isEdit">
|
<section fxLayout="column" fxLayoutAlign="start center" [fxShow]="isEdit">
|
||||||
@ -169,7 +176,7 @@
|
|||||||
<mat-slider fxFlex
|
<mat-slider fxFlex
|
||||||
discrete
|
discrete
|
||||||
min="{{minDatapointsLimit()}}"
|
min="{{minDatapointsLimit()}}"
|
||||||
max="{{maxDatapointsLimit()}}"><input matSliderThumb formControlName="limit" />
|
max="{{maxDatapointsLimit()}}"><input matSliderThumb formControlName="limit"/>
|
||||||
</mat-slider>
|
</mat-slider>
|
||||||
<mat-form-field class="limit-slider-value">
|
<mat-form-field class="limit-slider-value">
|
||||||
<input matInput formControlName="limit" type="number" step="1"
|
<input matInput formControlName="limit" type="number" step="1"
|
||||||
@ -217,20 +224,20 @@
|
|||||||
formControlName="timezone">
|
formControlName="timezone">
|
||||||
</tb-timezone-select>
|
</tb-timezone-select>
|
||||||
</div>
|
</div>
|
||||||
<div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
|
</ng-template>
|
||||||
|
</form>
|
||||||
|
<div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
mat-button
|
mat-button
|
||||||
[disabled]="(isLoading$ | async)"
|
[disabled]="(isLoading$ | async)"
|
||||||
(click)="cancel()">
|
(click)="cancel()">
|
||||||
{{ 'action.cancel' | translate }}
|
{{ 'action.cancel' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button type="submit"
|
<button type="button"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
color="primary"
|
color="primary"
|
||||||
|
(click)="update()"
|
||||||
[disabled]="(isLoading$ | async) || timewindowForm.invalid || !timewindowForm.dirty">
|
[disabled]="(isLoading$ | async) || timewindowForm.invalid || !timewindowForm.dirty">
|
||||||
{{ 'action.update' | translate }}
|
{{ 'action.update' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
|
|||||||
@ -16,16 +16,14 @@
|
|||||||
@import "../../../../scss/constants";
|
@import "../../../../scss/constants";
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
width: 100%;
|
display: flex;
|
||||||
height: 100%;
|
flex-direction: column;
|
||||||
form,
|
max-height: 100%;
|
||||||
fieldset {
|
max-width: 100%;
|
||||||
height: 100%;
|
background-color: #fff;
|
||||||
}
|
|
||||||
|
|
||||||
.mat-content {
|
.mat-content {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-padding {
|
.mat-padding {
|
||||||
@ -70,7 +68,6 @@
|
|||||||
:host ::ng-deep {
|
:host ::ng-deep {
|
||||||
.mat-mdc-radio-button {
|
.mat-mdc-radio-button {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 16px;
|
|
||||||
.mdc-form-field {
|
.mdc-form-field {
|
||||||
align-items: start;
|
align-items: start;
|
||||||
> label {
|
> label {
|
||||||
@ -78,4 +75,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.mat-mdc-tab-group:not(.tb-headless) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,14 +25,13 @@ import {
|
|||||||
Timewindow,
|
Timewindow,
|
||||||
TimewindowType
|
TimewindowType
|
||||||
} from '@shared/models/time/time.models';
|
} from '@shared/models/time/time.models';
|
||||||
import { OverlayRef } from '@angular/cdk/overlay';
|
|
||||||
import { PageComponent } from '@shared/components/page.component';
|
import { PageComponent } from '@shared/components/page.component';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppState } from '@core/core.state';
|
import { AppState } from '@core/core.state';
|
||||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||||
import { TimeService } from '@core/services/time.service';
|
import { TimeService } from '@core/services/time.service';
|
||||||
|
import { isDefined } from '@core/utils';
|
||||||
export const TIMEWINDOW_PANEL_DATA = new InjectionToken<any>('TimewindowPanelData');
|
import { OverlayRef } from '@angular/cdk/overlay';
|
||||||
|
|
||||||
export interface TimewindowPanelData {
|
export interface TimewindowPanelData {
|
||||||
historyOnly: boolean;
|
historyOnly: boolean;
|
||||||
@ -43,6 +42,8 @@ export interface TimewindowPanelData {
|
|||||||
isEdit: boolean;
|
isEdit: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TIMEWINDOW_PANEL_DATA = new InjectionToken<any>('TimewindowPanelData');
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tb-timewindow-panel',
|
selector: 'tb-timewindow-panel',
|
||||||
templateUrl: './timewindow-panel.component.html',
|
templateUrl: './timewindow-panel.component.html',
|
||||||
@ -62,8 +63,6 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
|
|||||||
|
|
||||||
timewindow: Timewindow;
|
timewindow: Timewindow;
|
||||||
|
|
||||||
result: Timewindow;
|
|
||||||
|
|
||||||
timewindowForm: UntypedFormGroup;
|
timewindowForm: UntypedFormGroup;
|
||||||
|
|
||||||
historyTypes = HistoryWindowType;
|
historyTypes = HistoryWindowType;
|
||||||
@ -78,6 +77,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
|
|||||||
|
|
||||||
aggregationTypesTranslations = aggregationTranslations;
|
aggregationTypesTranslations = aggregationTranslations;
|
||||||
|
|
||||||
|
result: Timewindow;
|
||||||
|
|
||||||
constructor(@Inject(TIMEWINDOW_PANEL_DATA) public data: TimewindowPanelData,
|
constructor(@Inject(TIMEWINDOW_PANEL_DATA) public data: TimewindowPanelData,
|
||||||
public overlayRef: OverlayRef,
|
public overlayRef: OverlayRef,
|
||||||
protected store: Store<AppState>,
|
protected store: Store<AppState>,
|
||||||
@ -101,77 +102,60 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
|
|||||||
const hideAggInterval = this.timewindow.hideAggInterval || false;
|
const hideAggInterval = this.timewindow.hideAggInterval || false;
|
||||||
const hideTimezone = this.timewindow.hideTimezone || false;
|
const hideTimezone = this.timewindow.hideTimezone || false;
|
||||||
|
|
||||||
|
const realtime = this.timewindow.realtime;
|
||||||
|
const history = this.timewindow.history;
|
||||||
|
const aggregation = this.timewindow.aggregation;
|
||||||
|
|
||||||
this.timewindowForm = this.fb.group({
|
this.timewindowForm = this.fb.group({
|
||||||
realtime: this.fb.group(
|
realtime: this.fb.group({
|
||||||
{
|
realtimeType: [{
|
||||||
realtimeType: this.fb.control({
|
value: isDefined(realtime?.realtimeType) ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL,
|
||||||
value: this.timewindow.realtime && typeof this.timewindow.realtime.realtimeType !== 'undefined'
|
|
||||||
? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL,
|
|
||||||
disabled: hideInterval
|
disabled: hideInterval
|
||||||
}),
|
}],
|
||||||
timewindowMs: this.fb.control({
|
timewindowMs: [{
|
||||||
value: this.timewindow.realtime && typeof this.timewindow.realtime.timewindowMs !== 'undefined'
|
value: isDefined(realtime?.timewindowMs) ? this.timewindow.realtime.timewindowMs : null,
|
||||||
? this.timewindow.realtime.timewindowMs : null,
|
|
||||||
disabled: hideInterval || hideLastInterval
|
disabled: hideInterval || hideLastInterval
|
||||||
}),
|
}],
|
||||||
interval: [
|
interval: [isDefined(realtime?.interval) ? this.timewindow.realtime.interval : null],
|
||||||
this.timewindow.realtime && typeof this.timewindow.realtime.interval !== 'undefined'
|
quickInterval: [{
|
||||||
? this.timewindow.realtime.interval : null
|
value: isDefined(realtime?.quickInterval) ? this.timewindow.realtime.quickInterval : null,
|
||||||
],
|
|
||||||
quickInterval: this.fb.control({
|
|
||||||
value: this.timewindow.realtime && typeof this.timewindow.realtime.quickInterval !== 'undefined'
|
|
||||||
? this.timewindow.realtime.quickInterval : null,
|
|
||||||
disabled: hideInterval || hideQuickInterval
|
disabled: hideInterval || hideQuickInterval
|
||||||
})
|
}]
|
||||||
}
|
|
||||||
),
|
|
||||||
history: this.fb.group(
|
|
||||||
{
|
|
||||||
historyType: this.fb.control({
|
|
||||||
value: this.timewindow.history && typeof this.timewindow.history.historyType !== 'undefined'
|
|
||||||
? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL,
|
|
||||||
disabled: hideInterval
|
|
||||||
}),
|
}),
|
||||||
timewindowMs: this.fb.control({
|
history: this.fb.group({
|
||||||
value: this.timewindow.history && typeof this.timewindow.history.timewindowMs !== 'undefined'
|
historyType: [{
|
||||||
? this.timewindow.history.timewindowMs : null,
|
value: isDefined(history?.historyType) ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL,
|
||||||
disabled: hideInterval
|
disabled: hideInterval
|
||||||
}),
|
}],
|
||||||
interval: [
|
timewindowMs: [{
|
||||||
this.timewindow.history && typeof this.timewindow.history.interval !== 'undefined'
|
value: isDefined(history?.timewindowMs) ? this.timewindow.history.timewindowMs : null,
|
||||||
? this.timewindow.history.interval : null
|
disabled: hideInterval
|
||||||
|
}],
|
||||||
|
interval: [ isDefined(history?.interval) ? this.timewindow.history.interval : null
|
||||||
],
|
],
|
||||||
fixedTimewindow: this.fb.control({
|
fixedTimewindow: [{
|
||||||
value: this.timewindow.history && typeof this.timewindow.history.fixedTimewindow !== 'undefined'
|
value: isDefined(history?.fixedTimewindow) ? this.timewindow.history.fixedTimewindow : null,
|
||||||
? this.timewindow.history.fixedTimewindow : null,
|
|
||||||
disabled: hideInterval
|
disabled: hideInterval
|
||||||
|
}],
|
||||||
|
quickInterval: [{
|
||||||
|
value: isDefined(history?.quickInterval) ? this.timewindow.history.quickInterval : null,
|
||||||
|
disabled: hideInterval
|
||||||
|
}]
|
||||||
}),
|
}),
|
||||||
quickInterval: this.fb.control({
|
aggregation: this.fb.group({
|
||||||
value: this.timewindow.history && typeof this.timewindow.history.quickInterval !== 'undefined'
|
type: [{
|
||||||
? this.timewindow.history.quickInterval : null,
|
value: isDefined(aggregation?.type) ? this.timewindow.aggregation.type : null,
|
||||||
disabled: hideInterval
|
|
||||||
})
|
|
||||||
}
|
|
||||||
),
|
|
||||||
aggregation: this.fb.group(
|
|
||||||
{
|
|
||||||
type: this.fb.control({
|
|
||||||
value: this.timewindow.aggregation && typeof this.timewindow.aggregation.type !== 'undefined'
|
|
||||||
? this.timewindow.aggregation.type : null,
|
|
||||||
disabled: hideAggregation
|
disabled: hideAggregation
|
||||||
}),
|
}],
|
||||||
limit: this.fb.control({
|
limit: [{
|
||||||
value: this.timewindow.aggregation && typeof this.timewindow.aggregation.limit !== 'undefined'
|
value: isDefined(aggregation?.limit) ? this.checkLimit(this.timewindow.aggregation.limit) : null,
|
||||||
? this.checkLimit(this.timewindow.aggregation.limit) : null,
|
|
||||||
disabled: hideAggInterval
|
disabled: hideAggInterval
|
||||||
}, [])
|
}, []]
|
||||||
}
|
}),
|
||||||
),
|
timezone: [{
|
||||||
timezone: this.fb.control({
|
value: isDefined(this.timewindow.timezone) ? this.timewindow.timezone : null,
|
||||||
value: this.timewindow.timezone !== 'undefined'
|
|
||||||
? this.timewindow.timezone : null,
|
|
||||||
disabled: hideTimezone
|
disabled: hideTimezone
|
||||||
})
|
}]
|
||||||
});
|
});
|
||||||
this.updateValidators();
|
this.updateValidators();
|
||||||
this.timewindowForm.get('aggregation.type').valueChanges.subscribe(() => {
|
this.timewindowForm.get('aggregation.type').valueChanges.subscribe(() => {
|
||||||
|
|||||||
@ -15,30 +15,34 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
-->
|
-->
|
||||||
<button *ngIf="asButton" cdkOverlayOrigin #timewindowPanelOrigin="cdkOverlayOrigin" [disabled]="timewindowDisabled"
|
<button *ngIf="asButton"
|
||||||
|
[disabled]="timewindowDisabled"
|
||||||
type="button"
|
type="button"
|
||||||
mat-raised-button color="primary" (click)="openEditMode()">
|
mat-raised-button color="primary"
|
||||||
|
(click)="toggleTimewindow($event)">
|
||||||
<mat-icon class="material-icons">query_builder</mat-icon>
|
<mat-icon class="material-icons">query_builder</mat-icon>
|
||||||
<span>{{innerValue?.displayValue}}</span>
|
<span>{{innerValue?.displayValue}}</span>
|
||||||
</button>
|
</button>
|
||||||
<section *ngIf="!asButton" cdkOverlayOrigin #timewindowPanelOrigin="cdkOverlayOrigin"
|
<section *ngIf="!asButton"
|
||||||
class="tb-timewindow" fxLayout="row" fxLayoutAlign="start center">
|
class="tb-timewindow"
|
||||||
|
fxLayout="row"
|
||||||
|
fxLayoutAlign="start center">
|
||||||
<button *ngIf="direction === 'left'" [disabled]="timewindowDisabled" mat-icon-button class="tb-mat-32"
|
<button *ngIf="direction === 'left'" [disabled]="timewindowDisabled" mat-icon-button class="tb-mat-32"
|
||||||
type="button"
|
type="button"
|
||||||
(click)="openEditMode()"
|
(click)="toggleTimewindow($event)"
|
||||||
matTooltip="{{ 'timewindow.edit' | translate }}"
|
matTooltip="{{ 'timewindow.edit' | translate }}"
|
||||||
[matTooltipPosition]="tooltipPosition">
|
[matTooltipPosition]="tooltipPosition">
|
||||||
<mat-icon class="material-icons">query_builder</mat-icon>
|
<mat-icon class="material-icons">query_builder</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<span [fxHide]="hideLabel()"
|
<span [fxHide]="hideLabel()"
|
||||||
(click)="openEditMode()"
|
(click)="toggleTimewindow($event)"
|
||||||
matTooltip="{{ 'timewindow.edit' | translate }}"
|
matTooltip="{{ 'timewindow.edit' | translate }}"
|
||||||
[matTooltipPosition]="tooltipPosition">
|
[matTooltipPosition]="tooltipPosition">
|
||||||
{{innerValue?.displayValue}} <span [fxShow]="innerValue?.displayTimezoneAbbr !== ''">| <span class="timezone-abbr">{{innerValue.displayTimezoneAbbr}}</span></span>
|
{{innerValue?.displayValue}} <span [fxShow]="innerValue?.displayTimezoneAbbr !== ''">| <span class="timezone-abbr">{{innerValue.displayTimezoneAbbr}}</span></span>
|
||||||
</span>
|
</span>
|
||||||
<button *ngIf="direction === 'right'" [disabled]="timewindowDisabled" mat-icon-button class="tb-mat-32"
|
<button *ngIf="direction === 'right'" [disabled]="timewindowDisabled" mat-icon-button class="tb-mat-32"
|
||||||
type="button"
|
type="button"
|
||||||
(click)="openEditMode()"
|
(click)="toggleTimewindow($event)"
|
||||||
matTooltip="{{ 'timewindow.edit' | translate }}"
|
matTooltip="{{ 'timewindow.edit' | translate }}"
|
||||||
[matTooltipPosition]="tooltipPosition">
|
[matTooltipPosition]="tooltipPosition">
|
||||||
<mat-icon class="material-icons">query_builder</mat-icon>
|
<mat-icon class="material-icons">query_builder</mat-icon>
|
||||||
|
|||||||
@ -17,14 +17,13 @@
|
|||||||
import {
|
import {
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
|
ElementRef,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
Inject,
|
|
||||||
Injector,
|
Injector,
|
||||||
Input,
|
Input,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
StaticProvider,
|
StaticProvider,
|
||||||
ViewChild,
|
|
||||||
ViewContainerRef
|
ViewContainerRef
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
@ -40,21 +39,16 @@ import {
|
|||||||
Timewindow,
|
Timewindow,
|
||||||
TimewindowType
|
TimewindowType
|
||||||
} from '@shared/models/time/time.models';
|
} from '@shared/models/time/time.models';
|
||||||
import { DatePipe, DOCUMENT } from '@angular/common';
|
import { DatePipe } from '@angular/common';
|
||||||
import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
|
import { TIMEWINDOW_PANEL_DATA, TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component';
|
||||||
import {
|
|
||||||
TIMEWINDOW_PANEL_DATA,
|
|
||||||
TimewindowPanelComponent,
|
|
||||||
TimewindowPanelData
|
|
||||||
} from '@shared/components/time/timewindow-panel.component';
|
|
||||||
import { ComponentPortal } from '@angular/cdk/portal';
|
|
||||||
import { MediaBreakpoints } from '@shared/models/constants';
|
import { MediaBreakpoints } from '@shared/models/constants';
|
||||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||||
import { WINDOW } from '@core/services/window.service';
|
|
||||||
import { TimeService } from '@core/services/time.service';
|
import { TimeService } from '@core/services/time.service';
|
||||||
import { TooltipPosition } from '@angular/material/tooltip';
|
import { TooltipPosition } from '@angular/material/tooltip';
|
||||||
import { deepClone, isDefinedAndNotNull } from '@core/utils';
|
import { deepClone, isDefinedAndNotNull } from '@core/utils';
|
||||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||||
|
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
|
||||||
|
import { ComponentPortal } from '@angular/cdk/portal';
|
||||||
|
|
||||||
// @dynamic
|
// @dynamic
|
||||||
@Component({
|
@Component({
|
||||||
@ -174,24 +168,21 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
|
|||||||
|
|
||||||
@Input() disabled: boolean;
|
@Input() disabled: boolean;
|
||||||
|
|
||||||
@ViewChild('timewindowPanelOrigin') timewindowPanelOrigin: CdkOverlayOrigin;
|
|
||||||
|
|
||||||
innerValue: Timewindow;
|
innerValue: Timewindow;
|
||||||
|
|
||||||
timewindowDisabled: boolean;
|
timewindowDisabled: boolean;
|
||||||
|
|
||||||
private propagateChange = (_: any) => {};
|
private propagateChange = (_: any) => {};
|
||||||
|
|
||||||
constructor(private translate: TranslateService,
|
constructor(private overlay: Overlay,
|
||||||
|
private translate: TranslateService,
|
||||||
private timeService: TimeService,
|
private timeService: TimeService,
|
||||||
private millisecondsToTimeStringPipe: MillisecondsToTimeStringPipe,
|
private millisecondsToTimeStringPipe: MillisecondsToTimeStringPipe,
|
||||||
private datePipe: DatePipe,
|
private datePipe: DatePipe,
|
||||||
private overlay: Overlay,
|
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
|
private nativeElement: ElementRef,
|
||||||
public viewContainerRef: ViewContainerRef,
|
public viewContainerRef: ViewContainerRef,
|
||||||
public breakpointObserver: BreakpointObserver,
|
public breakpointObserver: BreakpointObserver) {
|
||||||
@Inject(DOCUMENT) private document: Document,
|
|
||||||
@Inject(WINDOW) private window: Window) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -200,72 +191,35 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
|
|||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
openEditMode() {
|
toggleTimewindow($event: Event) {
|
||||||
if (this.timewindowDisabled) {
|
if ($event) {
|
||||||
return;
|
$event.stopPropagation();
|
||||||
}
|
}
|
||||||
const isGtXs = this.breakpointObserver.isMatched(MediaBreakpoints['gt-xs']);
|
|
||||||
const position = this.overlay.position();
|
|
||||||
const config = new OverlayConfig({
|
const config = new OverlayConfig({
|
||||||
panelClass: 'tb-timewindow-panel',
|
panelClass: 'tb-timewindow-panel',
|
||||||
backdropClass: 'cdk-overlay-transparent-backdrop',
|
backdropClass: 'cdk-overlay-transparent-backdrop',
|
||||||
hasBackdrop: isGtXs,
|
hasBackdrop: true,
|
||||||
|
maxHeight: '80vh',
|
||||||
|
height: 'min-content'
|
||||||
});
|
});
|
||||||
if (isGtXs) {
|
config.hasBackdrop = true;
|
||||||
config.minWidth = '417px';
|
|
||||||
config.maxHeight = '550px';
|
|
||||||
const panelHeight = 375;
|
|
||||||
const panelWidth = 417;
|
|
||||||
const el = this.timewindowPanelOrigin.elementRef.nativeElement;
|
|
||||||
const offset = el.getBoundingClientRect();
|
|
||||||
const scrollTop = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0;
|
|
||||||
const scrollLeft = this.window.pageXOffset || this.document.documentElement.scrollLeft || this.document.body.scrollLeft || 0;
|
|
||||||
const bottomY = offset.bottom - scrollTop;
|
|
||||||
const leftX = offset.left - scrollLeft;
|
|
||||||
let originX;
|
|
||||||
let originY;
|
|
||||||
let overlayX;
|
|
||||||
let overlayY;
|
|
||||||
const wHeight = this.document.documentElement.clientHeight;
|
|
||||||
const wWidth = this.document.documentElement.clientWidth;
|
|
||||||
if (bottomY + panelHeight > wHeight) {
|
|
||||||
originY = 'top';
|
|
||||||
overlayY = 'bottom';
|
|
||||||
} else {
|
|
||||||
originY = 'bottom';
|
|
||||||
overlayY = 'top';
|
|
||||||
}
|
|
||||||
if (leftX + panelWidth > wWidth) {
|
|
||||||
originX = 'end';
|
|
||||||
overlayX = 'end';
|
|
||||||
} else {
|
|
||||||
originX = 'start';
|
|
||||||
overlayX = 'start';
|
|
||||||
}
|
|
||||||
const connectedPosition: ConnectedPosition = {
|
const connectedPosition: ConnectedPosition = {
|
||||||
originX,
|
originX: 'start',
|
||||||
originY,
|
originY: 'bottom',
|
||||||
overlayX,
|
overlayX: 'start',
|
||||||
overlayY
|
overlayY: 'top'
|
||||||
};
|
};
|
||||||
config.positionStrategy = position.flexibleConnectedTo(this.timewindowPanelOrigin.elementRef)
|
config.positionStrategy = this.overlay.position().flexibleConnectedTo(this.nativeElement)
|
||||||
.withPositions([connectedPosition]);
|
.withPositions([connectedPosition]);
|
||||||
} else {
|
|
||||||
config.minWidth = '100%';
|
|
||||||
config.minHeight = '100%';
|
|
||||||
config.positionStrategy = position.global().top('0%').left('0%')
|
|
||||||
.right('0%').bottom('0%');
|
|
||||||
}
|
|
||||||
|
|
||||||
const overlayRef = this.overlay.create(config);
|
const overlayRef = this.overlay.create(config);
|
||||||
|
|
||||||
overlayRef.backdropClick().subscribe(() => {
|
overlayRef.backdropClick().subscribe(() => {
|
||||||
overlayRef.dispose();
|
overlayRef.dispose();
|
||||||
});
|
});
|
||||||
|
const providers: StaticProvider[] = [
|
||||||
const injector = this._createTimewindowPanelInjector(
|
|
||||||
overlayRef,
|
|
||||||
{
|
{
|
||||||
|
provide: TIMEWINDOW_PANEL_DATA,
|
||||||
|
useValue: {
|
||||||
timewindow: deepClone(this.innerValue),
|
timewindow: deepClone(this.innerValue),
|
||||||
historyOnly: this.historyOnly,
|
historyOnly: this.historyOnly,
|
||||||
quickIntervalOnly: this.quickIntervalOnly,
|
quickIntervalOnly: this.quickIntervalOnly,
|
||||||
@ -273,9 +227,15 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
|
|||||||
timezone: this.timezone,
|
timezone: this.timezone,
|
||||||
isEdit: this.isEdit
|
isEdit: this.isEdit
|
||||||
}
|
}
|
||||||
);
|
},
|
||||||
|
{
|
||||||
const componentRef = overlayRef.attach(new ComponentPortal(TimewindowPanelComponent, this.viewContainerRef, injector));
|
provide: OverlayRef,
|
||||||
|
useValue: overlayRef
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const injector = Injector.create({parent: this.viewContainerRef.injector, providers});
|
||||||
|
const componentRef = overlayRef.attach(new ComponentPortal(TimewindowPanelComponent,
|
||||||
|
this.viewContainerRef, injector));
|
||||||
componentRef.onDestroy(() => {
|
componentRef.onDestroy(() => {
|
||||||
if (componentRef.instance.result) {
|
if (componentRef.instance.result) {
|
||||||
this.innerValue = componentRef.instance.result;
|
this.innerValue = componentRef.instance.result;
|
||||||
@ -284,14 +244,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
|
|||||||
this.notifyChanged();
|
this.notifyChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
this.cd.detectChanges();
|
||||||
|
|
||||||
private _createTimewindowPanelInjector(overlayRef: OverlayRef, data: TimewindowPanelData): Injector {
|
|
||||||
const providers: StaticProvider[] = [
|
|
||||||
{provide: TIMEWINDOW_PANEL_DATA, useValue: data},
|
|
||||||
{provide: OverlayRef, useValue: overlayRef}
|
|
||||||
];
|
|
||||||
return Injector.create({parent: this.viewContainerRef.injector, providers});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onHistoryOnlyChanged(): boolean {
|
private onHistoryOnlyChanged(): boolean {
|
||||||
|
|||||||
@ -505,7 +505,7 @@ export const baseDetailsPageByEntityType = new Map<EntityType, string>([
|
|||||||
[EntityType.DEVICE, '/entities/devices'],
|
[EntityType.DEVICE, '/entities/devices'],
|
||||||
[EntityType.DEVICE_PROFILE, '/profiles/deviceProfiles'],
|
[EntityType.DEVICE_PROFILE, '/profiles/deviceProfiles'],
|
||||||
[EntityType.ASSET_PROFILE, '/profiles/assetProfiles'],
|
[EntityType.ASSET_PROFILE, '/profiles/assetProfiles'],
|
||||||
[EntityType.RULE_CHAIN, '/features/ruleChains'],
|
[EntityType.RULE_CHAIN, '/ruleChains'],
|
||||||
[EntityType.EDGE, '/edgeManagement/instances'],
|
[EntityType.EDGE, '/edgeManagement/instances'],
|
||||||
[EntityType.ENTITY_VIEW, '/entities/entityViews'],
|
[EntityType.ENTITY_VIEW, '/entities/entityViews'],
|
||||||
[EntityType.TB_RESOURCE, '/resources/resources-library'],
|
[EntityType.TB_RESOURCE, '/resources/resources-library'],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user