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
|
||||
|
||||
-- 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());
|
||||
checkEntity(alarm.getId(), alarm, Resource.ALARM);
|
||||
checkEntityId(alarm.getOriginator(), Operation.READ);
|
||||
if (alarm.getAssigneeId() != null) {
|
||||
checkUserId(alarm.getAssigneeId(), Operation.READ);
|
||||
}
|
||||
return tbAlarmService.save(alarm, getCurrentUser());
|
||||
}
|
||||
|
||||
|
||||
@ -76,7 +76,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
|
||||
UserId newAssignee = alarm.getAssigneeId();
|
||||
UserId curAssignee = resultAlarm.getAssigneeId();
|
||||
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) {
|
||||
resultAlarm = unassign(alarm, alarm.getAssignTs(), user);
|
||||
}
|
||||
|
||||
@ -357,6 +357,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
|
||||
protected Tenant savedDifferentTenant;
|
||||
protected User savedDifferentTenantUser;
|
||||
private Customer savedDifferentCustomer;
|
||||
protected User differentCustomerUser;
|
||||
|
||||
protected void loginDifferentTenant() throws Exception {
|
||||
if (savedDifferentTenant != null) {
|
||||
@ -387,7 +388,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
|
||||
createDifferentCustomer();
|
||||
|
||||
loginTenantAdmin();
|
||||
User differentCustomerUser = new User();
|
||||
differentCustomerUser = new User();
|
||||
differentCustomerUser.setAuthority(Authority.CUSTOMER_USER);
|
||||
differentCustomerUser.setTenantId(tenantId);
|
||||
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
|
||||
|
||||
@ -656,4 +656,38 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
|
||||
|
||||
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
|
||||
public void cleanup(long systemTtl) {
|
||||
cleanupPartitions(systemTtl);
|
||||
if (systemTtl > 0) {
|
||||
cleanupPartitions(systemTtl);
|
||||
}
|
||||
super.cleanup(systemTtl);
|
||||
}
|
||||
|
||||
private void cleanupPartitions(long systemTtl) {
|
||||
log.info("Going to cleanup old timeseries data partitions using partition type: {} and ttl: {}s", partitioning, systemTtl);
|
||||
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.setLong(2, systemTtl);
|
||||
stmt.setLong(3, 0);
|
||||
|
||||
@ -34,13 +34,10 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary
|
||||
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
|
||||
$$
|
||||
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;
|
||||
@ -52,20 +49,9 @@ DECLARE
|
||||
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);
|
||||
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
|
||||
@ -203,7 +189,7 @@ BEGIN
|
||||
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
|
||||
CASE
|
||||
|
||||
@ -421,6 +421,13 @@ export class MenuService {
|
||||
type: 'link',
|
||||
path: '/customers',
|
||||
icon: 'supervisor_account'
|
||||
},
|
||||
{
|
||||
id: guid(),
|
||||
name: 'rulechain.rulechains',
|
||||
type: 'link',
|
||||
path: '/ruleChains',
|
||||
icon: 'settings_ethernet'
|
||||
}
|
||||
);
|
||||
if (authState.edgesSupportEnabled) {
|
||||
@ -458,13 +465,6 @@ export class MenuService {
|
||||
path: '/features',
|
||||
icon: 'construction',
|
||||
pages: [
|
||||
{
|
||||
id: guid(),
|
||||
name: 'rulechain.rulechains',
|
||||
type: 'link',
|
||||
path: '/features/ruleChains',
|
||||
icon: 'settings_ethernet'
|
||||
},
|
||||
{
|
||||
id: guid(),
|
||||
name: 'ota-update.ota-updates',
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { Authority } from '@shared/models/authority.enum';
|
||||
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 { vcRoutes } from '@home/pages/vc/vc-routing.module';
|
||||
|
||||
@ -37,10 +36,9 @@ const routes: Routes = [
|
||||
children: [],
|
||||
data: {
|
||||
auth: [Authority.TENANT_ADMIN],
|
||||
redirectTo: '/features/ruleChains'
|
||||
redirectTo: '/features/otaUpdates'
|
||||
}
|
||||
},
|
||||
...ruleChainsRoutes,
|
||||
...otaUpdatesRoutes,
|
||||
...vcRoutes
|
||||
]
|
||||
|
||||
@ -122,7 +122,7 @@ export const importRuleChainBreadcumbLabelFunction: BreadCrumbLabelFunction<Rule
|
||||
return `${translate.instant('rulechain.import')}: ${component.ruleChain.name}`;
|
||||
});
|
||||
|
||||
export const ruleChainsRoutes: Routes = [
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'ruleChains',
|
||||
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
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
|
||||
@ -15,222 +15,229 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<form [formGroup]="timewindowForm" (ngSubmit)="update()">
|
||||
<fieldset [disabled]="(isLoading$ | async)">
|
||||
<div class="mat-content" fxLayout="column">
|
||||
<mat-tab-group dynamicHeight [ngClass]="{'tb-headless': historyOnly}"
|
||||
(selectedIndexChange)="timewindowForm.markAsDirty()" [(selectedIndex)]="timewindow.selectedTab">
|
||||
<mat-tab label="{{ 'timewindow.realtime' | translate }}">
|
||||
<section fxLayout="row">
|
||||
<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>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval"
|
||||
(ngModelChange)="onHideIntervalChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideInterval">
|
||||
<div formGroupName="realtime" class="mat-content mat-padding" style="padding-top: 8px;">
|
||||
<mat-radio-group *ngIf="!quickIntervalOnly" [fxShow]="isEdit || (!timewindow.hideLastInterval && !timewindow.hideQuickInterval)"
|
||||
formControlName="realtimeType">
|
||||
<mat-radio-button [value]="realtimeTypes.LAST_INTERVAL" color="primary">
|
||||
<section fxLayout="row">
|
||||
<section *ngIf="isEdit" fxLayout="column" fxLayoutAlign="start center" style="padding-right: 8px;">
|
||||
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideLastInterval"
|
||||
(ngModelChange)="onHideLastIntervalChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<section fxLayout="column">
|
||||
<span translate>timewindow.last</span>
|
||||
<tb-timeinterval
|
||||
formControlName="timewindowMs"
|
||||
predefinedName="timewindow.last"
|
||||
[fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL"
|
||||
[required]="timewindow.selectedTab === timewindowTypes.REALTIME &&
|
||||
timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL"
|
||||
style="padding-top: 8px;"></tb-timeinterval>
|
||||
</section>
|
||||
</section>
|
||||
</mat-radio-button>
|
||||
<mat-radio-button [value]="realtimeTypes.INTERVAL" color="primary">
|
||||
<section fxLayout="row">
|
||||
<section *ngIf="isEdit" fxLayout="column" fxLayoutAlign="start center" style="padding-right: 8px;">
|
||||
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideQuickInterval"
|
||||
(ngModelChange)="onHideQuickIntervalChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<section fxLayout="column">
|
||||
<span translate>timewindow.interval</span>
|
||||
<tb-quick-time-interval
|
||||
formControlName="quickInterval"
|
||||
onlyCurrentInterval="true"
|
||||
[fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
|
||||
[required]="timewindow.selectedTab === timewindowTypes.REALTIME &&
|
||||
timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
|
||||
style="padding-top: 8px"></tb-quick-time-interval>
|
||||
</section>
|
||||
</section>
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
<tb-timeinterval *ngIf="!isEdit && !timewindow.hideLastInterval && timewindow.hideQuickInterval"
|
||||
formControlName="timewindowMs"
|
||||
predefinedName="timewindow.last"
|
||||
required
|
||||
style="padding-top: 8px;"></tb-timeinterval>
|
||||
<tb-quick-time-interval *ngIf="quickIntervalOnly || !isEdit && timewindow.hideLastInterval && !timewindow.hideQuickInterval"
|
||||
formControlName="quickInterval"
|
||||
onlyCurrentInterval="true"
|
||||
required
|
||||
style="padding-top: 8px"></tb-quick-time-interval>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</mat-tab>
|
||||
<mat-tab label="{{ 'timewindow.history' | translate }}">
|
||||
<section fxLayout="row">
|
||||
<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>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval"
|
||||
(ngModelChange)="onHideIntervalChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideInterval">
|
||||
<div formGroupName="history" class="mat-content mat-padding" style="padding-top: 8px;">
|
||||
<mat-radio-group formControlName="historyType">
|
||||
<mat-radio-button [value]="historyTypes.LAST_INTERVAL" color="primary">
|
||||
<section fxLayout="column">
|
||||
<span translate>timewindow.last</span>
|
||||
<tb-timeinterval
|
||||
formControlName="timewindowMs"
|
||||
predefinedName="timewindow.last"
|
||||
class="history-time-input"
|
||||
[fxShow]="timewindowForm.get('history.historyType').value === historyTypes.LAST_INTERVAL"
|
||||
[required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
|
||||
timewindowForm.get('history.historyType').value === historyTypes.LAST_INTERVAL"
|
||||
style="padding-top: 8px;"></tb-timeinterval>
|
||||
</section>
|
||||
</mat-radio-button>
|
||||
<mat-radio-button [value]="historyTypes.FIXED" color="primary">
|
||||
<section fxLayout="column">
|
||||
<span translate>timewindow.time-period</span>
|
||||
<tb-datetime-period
|
||||
formControlName="fixedTimewindow"
|
||||
class="history-time-input"
|
||||
[fxShow]="timewindowForm.get('history.historyType').value === historyTypes.FIXED"
|
||||
[required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
|
||||
timewindowForm.get('history.historyType').value === historyTypes.FIXED"
|
||||
style="padding-top: 8px;"></tb-datetime-period>
|
||||
</section>
|
||||
</mat-radio-button>
|
||||
<mat-radio-button [value]="historyTypes.INTERVAL" color="primary">
|
||||
<section fxLayout="column">
|
||||
<span translate>timewindow.interval</span>
|
||||
<tb-quick-time-interval
|
||||
formControlName="quickInterval"
|
||||
class="history-time-input"
|
||||
[fxShow]="timewindowForm.get('history.historyType').value === historyTypes.INTERVAL"
|
||||
[required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
|
||||
timewindowForm.get('history.historyType').value === historyTypes.INTERVAL"
|
||||
style="padding-top: 8px"></tb-quick-time-interval>
|
||||
</section>
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<div *ngIf="aggregation" formGroupName="aggregation" class="mat-content mat-padding" fxLayout="column">
|
||||
<section fxLayout="row">
|
||||
<section fxLayout="column" fxLayoutAlign="start center" [fxShow]="isEdit">
|
||||
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideAggregation"
|
||||
(ngModelChange)="onHideAggregationChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<section fxFlex fxLayout="column" [fxShow]="isEdit || !timewindow.hideAggregation">
|
||||
<mat-form-field>
|
||||
<mat-label translate>aggregation.function</mat-label>
|
||||
<mat-select formControlName="type" style="min-width: 150px;">
|
||||
<mat-option *ngFor="let aggregation of aggregations" [value]="aggregation">
|
||||
{{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</section>
|
||||
<form [formGroup]="timewindowForm" class="mat-content">
|
||||
<mat-tab-group [ngClass]="{'tb-headless': historyOnly}"
|
||||
(selectedIndexChange)="timewindowForm.markAsDirty()" [(selectedIndex)]="timewindow.selectedTab">
|
||||
<mat-tab label="{{ 'timewindow.realtime' | translate }}">
|
||||
<section fxLayout="row">
|
||||
<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>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval"
|
||||
(ngModelChange)="onHideIntervalChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<section fxLayout="row" *ngIf="timewindowForm.get('aggregation.type').value === aggregationTypes.NONE">
|
||||
<section fxLayout="column" fxLayoutAlign="start center" [fxShow]="isEdit">
|
||||
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideAggInterval"
|
||||
(ngModelChange)="onHideAggIntervalChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideAggInterval">
|
||||
<div class="limit-slider-container" fxLayout="row" fxLayoutAlign="start center"
|
||||
fxLayout.xs="column" fxLayoutAlign.xs="stretch">
|
||||
<label translate>aggregation.limit</label>
|
||||
<div fxLayout="row" fxLayoutAlign="start center" fxFlex>
|
||||
<mat-slider fxFlex
|
||||
discrete
|
||||
min="{{minDatapointsLimit()}}"
|
||||
max="{{maxDatapointsLimit()}}"><input matSliderThumb formControlName="limit" />
|
||||
</mat-slider>
|
||||
<mat-form-field class="limit-slider-value">
|
||||
<input matInput formControlName="limit" type="number" step="1"
|
||||
[value]="timewindowForm.get('aggregation.limit').value"
|
||||
min="{{minDatapointsLimit()}}"
|
||||
max="{{maxDatapointsLimit()}}"/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideInterval">
|
||||
<div formGroupName="realtime" class="mat-content mat-padding" style="padding-top: 8px;">
|
||||
<mat-radio-group *ngIf="!quickIntervalOnly"
|
||||
[fxShow]="isEdit || (!timewindow.hideLastInterval && !timewindow.hideQuickInterval)"
|
||||
formControlName="realtimeType">
|
||||
<mat-radio-button [value]="realtimeTypes.LAST_INTERVAL" color="primary">
|
||||
<section fxLayout="row">
|
||||
<section *ngIf="isEdit" fxLayout="column" fxLayoutAlign="start center" style="padding-right: 8px;">
|
||||
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideLastInterval"
|
||||
(ngModelChange)="onHideLastIntervalChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<section fxLayout="column">
|
||||
<span translate>timewindow.last</span>
|
||||
<tb-timeinterval
|
||||
formControlName="timewindowMs"
|
||||
predefinedName="timewindow.last"
|
||||
[fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL"
|
||||
[required]="timewindow.selectedTab === timewindowTypes.REALTIME &&
|
||||
timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL"
|
||||
style="padding-top: 8px;"></tb-timeinterval>
|
||||
</section>
|
||||
</section>
|
||||
</mat-radio-button>
|
||||
<mat-radio-button [value]="realtimeTypes.INTERVAL" color="primary">
|
||||
<section fxLayout="row">
|
||||
<section *ngIf="isEdit" fxLayout="column" fxLayoutAlign="start center" style="padding-right: 8px;">
|
||||
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideQuickInterval"
|
||||
(ngModelChange)="onHideQuickIntervalChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<section fxLayout="column">
|
||||
<span translate>timewindow.interval</span>
|
||||
<tb-quick-time-interval
|
||||
formControlName="quickInterval"
|
||||
onlyCurrentInterval="true"
|
||||
[fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
|
||||
[required]="timewindow.selectedTab === timewindowTypes.REALTIME &&
|
||||
timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
|
||||
style="padding-top: 8px"></tb-quick-time-interval>
|
||||
</section>
|
||||
</section>
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
<tb-timeinterval *ngIf="!isEdit && !timewindow.hideLastInterval && timewindow.hideQuickInterval"
|
||||
formControlName="timewindowMs"
|
||||
predefinedName="timewindow.last"
|
||||
required
|
||||
style="padding-top: 8px;"></tb-timeinterval>
|
||||
<tb-quick-time-interval
|
||||
*ngIf="quickIntervalOnly || !isEdit && timewindow.hideLastInterval && !timewindow.hideQuickInterval"
|
||||
formControlName="quickInterval"
|
||||
onlyCurrentInterval="true"
|
||||
required
|
||||
style="padding-top: 8px"></tb-quick-time-interval>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div formGroupName="realtime"
|
||||
*ngIf="aggregation && timewindowForm.get('aggregation.type').value !== aggregationTypes.NONE &&
|
||||
timewindow.selectedTab === timewindowTypes.REALTIME" class="mat-content mat-padding" fxLayout="column">
|
||||
<tb-timeinterval
|
||||
formControlName="interval"
|
||||
[isEdit]="isEdit"
|
||||
[(hideFlag)]="timewindow.hideAggInterval"
|
||||
(hideFlagChange)="onHideAggIntervalChanged()"
|
||||
[min]="minRealtimeAggInterval()" [max]="maxRealtimeAggInterval()"
|
||||
predefinedName="aggregation.group-interval">
|
||||
</tb-timeinterval>
|
||||
</div>
|
||||
<div formGroupName="history"
|
||||
*ngIf="aggregation && timewindowForm.get('aggregation.type').value !== aggregationTypes.NONE &&
|
||||
timewindow.selectedTab === timewindowTypes.HISTORY" class="mat-content mat-padding" fxLayout="column">
|
||||
<tb-timeinterval
|
||||
formControlName="interval"
|
||||
[isEdit]="isEdit"
|
||||
[(hideFlag)]="timewindow.hideAggInterval"
|
||||
(hideFlagChange)="onHideAggIntervalChanged()"
|
||||
[min]="minHistoryAggInterval()" [max]="maxHistoryAggInterval()"
|
||||
predefinedName="aggregation.group-interval">
|
||||
</tb-timeinterval>
|
||||
</div>
|
||||
<div *ngIf="timezone" class="mat-content mat-padding" fxLayout="row">
|
||||
</section>
|
||||
<ng-container *ngTemplateOutlet="additionalData">
|
||||
</ng-container>
|
||||
</mat-tab>
|
||||
<mat-tab label="{{ 'timewindow.history' | translate }}">
|
||||
<section fxLayout="row">
|
||||
<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>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval"
|
||||
(ngModelChange)="onHideIntervalChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideInterval">
|
||||
<div formGroupName="history" class="mat-content mat-padding" style="padding-top: 8px;">
|
||||
<mat-radio-group formControlName="historyType">
|
||||
<mat-radio-button [value]="historyTypes.LAST_INTERVAL" color="primary">
|
||||
<section fxLayout="column">
|
||||
<span translate>timewindow.last</span>
|
||||
<tb-timeinterval
|
||||
formControlName="timewindowMs"
|
||||
predefinedName="timewindow.last"
|
||||
class="history-time-input"
|
||||
[fxShow]="timewindowForm.get('history.historyType').value === historyTypes.LAST_INTERVAL"
|
||||
[required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
|
||||
timewindowForm.get('history.historyType').value === historyTypes.LAST_INTERVAL"
|
||||
style="padding-top: 8px;"></tb-timeinterval>
|
||||
</section>
|
||||
</mat-radio-button>
|
||||
<mat-radio-button [value]="historyTypes.FIXED" color="primary">
|
||||
<section fxLayout="column">
|
||||
<span translate>timewindow.time-period</span>
|
||||
<tb-datetime-period
|
||||
formControlName="fixedTimewindow"
|
||||
class="history-time-input"
|
||||
[fxShow]="timewindowForm.get('history.historyType').value === historyTypes.FIXED"
|
||||
[required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
|
||||
timewindowForm.get('history.historyType').value === historyTypes.FIXED"
|
||||
style="padding-top: 8px;"></tb-datetime-period>
|
||||
</section>
|
||||
</mat-radio-button>
|
||||
<mat-radio-button [value]="historyTypes.INTERVAL" color="primary">
|
||||
<section fxLayout="column">
|
||||
<span translate>timewindow.interval</span>
|
||||
<tb-quick-time-interval
|
||||
formControlName="quickInterval"
|
||||
class="history-time-input"
|
||||
[fxShow]="timewindowForm.get('history.historyType').value === historyTypes.INTERVAL"
|
||||
[required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
|
||||
timewindowForm.get('history.historyType').value === historyTypes.INTERVAL"
|
||||
style="padding-top: 8px"></tb-quick-time-interval>
|
||||
</section>
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<ng-container *ngTemplateOutlet="additionalData">
|
||||
</ng-container>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<ng-template #additionalData>
|
||||
<div *ngIf="aggregation" formGroupName="aggregation" class="mat-content mat-padding" fxLayout="column">
|
||||
<section fxLayout="row">
|
||||
<section fxLayout="column" fxLayoutAlign="start center" [fxShow]="isEdit">
|
||||
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideTimezone"
|
||||
(ngModelChange)="onHideTimezoneChanged()"></mat-checkbox>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideAggregation"
|
||||
(ngModelChange)="onHideAggregationChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<tb-timezone-select fxFlex [fxShow]="isEdit || !timewindow.hideTimezone"
|
||||
localBrowserTimezonePlaceholderOnEmpty="true"
|
||||
formControlName="timezone">
|
||||
</tb-timezone-select>
|
||||
</div>
|
||||
<div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
|
||||
<button type="button"
|
||||
mat-button
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()">
|
||||
{{ 'action.cancel' | translate }}
|
||||
</button>
|
||||
<button type="submit"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
[disabled]="(isLoading$ | async) || timewindowForm.invalid || !timewindowForm.dirty">
|
||||
{{ 'action.update' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<section fxFlex fxLayout="column" [fxShow]="isEdit || !timewindow.hideAggregation">
|
||||
<mat-form-field>
|
||||
<mat-label translate>aggregation.function</mat-label>
|
||||
<mat-select formControlName="type" style="min-width: 150px;">
|
||||
<mat-option *ngFor="let aggregation of aggregations" [value]="aggregation">
|
||||
{{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</section>
|
||||
</section>
|
||||
<section fxLayout="row" *ngIf="timewindowForm.get('aggregation.type').value === aggregationTypes.NONE">
|
||||
<section fxLayout="column" fxLayoutAlign="start center" [fxShow]="isEdit">
|
||||
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideAggInterval"
|
||||
(ngModelChange)="onHideAggIntervalChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideAggInterval">
|
||||
<div class="limit-slider-container" fxLayout="row" fxLayoutAlign="start center"
|
||||
fxLayout.xs="column" fxLayoutAlign.xs="stretch">
|
||||
<label translate>aggregation.limit</label>
|
||||
<div fxLayout="row" fxLayoutAlign="start center" fxFlex>
|
||||
<mat-slider fxFlex
|
||||
discrete
|
||||
min="{{minDatapointsLimit()}}"
|
||||
max="{{maxDatapointsLimit()}}"><input matSliderThumb formControlName="limit"/>
|
||||
</mat-slider>
|
||||
<mat-form-field class="limit-slider-value">
|
||||
<input matInput formControlName="limit" type="number" step="1"
|
||||
[value]="timewindowForm.get('aggregation.limit').value"
|
||||
min="{{minDatapointsLimit()}}"
|
||||
max="{{maxDatapointsLimit()}}"/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div formGroupName="realtime"
|
||||
*ngIf="aggregation && timewindowForm.get('aggregation.type').value !== aggregationTypes.NONE &&
|
||||
timewindow.selectedTab === timewindowTypes.REALTIME" class="mat-content mat-padding" fxLayout="column">
|
||||
<tb-timeinterval
|
||||
formControlName="interval"
|
||||
[isEdit]="isEdit"
|
||||
[(hideFlag)]="timewindow.hideAggInterval"
|
||||
(hideFlagChange)="onHideAggIntervalChanged()"
|
||||
[min]="minRealtimeAggInterval()" [max]="maxRealtimeAggInterval()"
|
||||
predefinedName="aggregation.group-interval">
|
||||
</tb-timeinterval>
|
||||
</div>
|
||||
<div formGroupName="history"
|
||||
*ngIf="aggregation && timewindowForm.get('aggregation.type').value !== aggregationTypes.NONE &&
|
||||
timewindow.selectedTab === timewindowTypes.HISTORY" class="mat-content mat-padding" fxLayout="column">
|
||||
<tb-timeinterval
|
||||
formControlName="interval"
|
||||
[isEdit]="isEdit"
|
||||
[(hideFlag)]="timewindow.hideAggInterval"
|
||||
(hideFlagChange)="onHideAggIntervalChanged()"
|
||||
[min]="minHistoryAggInterval()" [max]="maxHistoryAggInterval()"
|
||||
predefinedName="aggregation.group-interval">
|
||||
</tb-timeinterval>
|
||||
</div>
|
||||
<div *ngIf="timezone" class="mat-content mat-padding" fxLayout="row">
|
||||
<section fxLayout="column" fxLayoutAlign="start center" [fxShow]="isEdit">
|
||||
<label class="tb-small hide-label" translate>timewindow.hide</label>
|
||||
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideTimezone"
|
||||
(ngModelChange)="onHideTimezoneChanged()"></mat-checkbox>
|
||||
</section>
|
||||
<tb-timezone-select fxFlex [fxShow]="isEdit || !timewindow.hideTimezone"
|
||||
localBrowserTimezonePlaceholderOnEmpty="true"
|
||||
formControlName="timezone">
|
||||
</tb-timezone-select>
|
||||
</div>
|
||||
</ng-template>
|
||||
</form>
|
||||
<div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
|
||||
<button type="button"
|
||||
mat-button
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()">
|
||||
{{ 'action.cancel' | translate }}
|
||||
</button>
|
||||
<button type="button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="update()"
|
||||
[disabled]="(isLoading$ | async) || timewindowForm.invalid || !timewindowForm.dirty">
|
||||
{{ 'action.update' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -16,16 +16,14 @@
|
||||
@import "../../../../scss/constants";
|
||||
|
||||
:host {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
form,
|
||||
fieldset {
|
||||
height: 100%;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
background-color: #fff;
|
||||
|
||||
.mat-content {
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.mat-padding {
|
||||
@ -70,7 +68,6 @@
|
||||
:host ::ng-deep {
|
||||
.mat-mdc-radio-button {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
.mdc-form-field {
|
||||
align-items: start;
|
||||
> label {
|
||||
@ -78,4 +75,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.mat-mdc-tab-group:not(.tb-headless) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,14 +25,13 @@ import {
|
||||
Timewindow,
|
||||
TimewindowType
|
||||
} from '@shared/models/time/time.models';
|
||||
import { OverlayRef } from '@angular/cdk/overlay';
|
||||
import { PageComponent } from '@shared/components/page.component';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { TimeService } from '@core/services/time.service';
|
||||
|
||||
export const TIMEWINDOW_PANEL_DATA = new InjectionToken<any>('TimewindowPanelData');
|
||||
import { isDefined } from '@core/utils';
|
||||
import { OverlayRef } from '@angular/cdk/overlay';
|
||||
|
||||
export interface TimewindowPanelData {
|
||||
historyOnly: boolean;
|
||||
@ -43,6 +42,8 @@ export interface TimewindowPanelData {
|
||||
isEdit: boolean;
|
||||
}
|
||||
|
||||
export const TIMEWINDOW_PANEL_DATA = new InjectionToken<any>('TimewindowPanelData');
|
||||
|
||||
@Component({
|
||||
selector: 'tb-timewindow-panel',
|
||||
templateUrl: './timewindow-panel.component.html',
|
||||
@ -62,8 +63,6 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
|
||||
|
||||
timewindow: Timewindow;
|
||||
|
||||
result: Timewindow;
|
||||
|
||||
timewindowForm: UntypedFormGroup;
|
||||
|
||||
historyTypes = HistoryWindowType;
|
||||
@ -78,6 +77,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
|
||||
|
||||
aggregationTypesTranslations = aggregationTranslations;
|
||||
|
||||
result: Timewindow;
|
||||
|
||||
constructor(@Inject(TIMEWINDOW_PANEL_DATA) public data: TimewindowPanelData,
|
||||
public overlayRef: OverlayRef,
|
||||
protected store: Store<AppState>,
|
||||
@ -101,77 +102,60 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
|
||||
const hideAggInterval = this.timewindow.hideAggInterval || 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({
|
||||
realtime: this.fb.group(
|
||||
{
|
||||
realtimeType: this.fb.control({
|
||||
value: this.timewindow.realtime && typeof this.timewindow.realtime.realtimeType !== 'undefined'
|
||||
? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL,
|
||||
disabled: hideInterval
|
||||
}),
|
||||
timewindowMs: this.fb.control({
|
||||
value: this.timewindow.realtime && typeof this.timewindow.realtime.timewindowMs !== 'undefined'
|
||||
? this.timewindow.realtime.timewindowMs : null,
|
||||
disabled: hideInterval || hideLastInterval
|
||||
}),
|
||||
interval: [
|
||||
this.timewindow.realtime && typeof this.timewindow.realtime.interval !== 'undefined'
|
||||
? this.timewindow.realtime.interval : null
|
||||
],
|
||||
quickInterval: this.fb.control({
|
||||
value: this.timewindow.realtime && typeof this.timewindow.realtime.quickInterval !== 'undefined'
|
||||
? this.timewindow.realtime.quickInterval : null,
|
||||
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({
|
||||
value: this.timewindow.history && typeof this.timewindow.history.timewindowMs !== 'undefined'
|
||||
? this.timewindow.history.timewindowMs : null,
|
||||
disabled: hideInterval
|
||||
}),
|
||||
interval: [
|
||||
this.timewindow.history && typeof this.timewindow.history.interval !== 'undefined'
|
||||
? this.timewindow.history.interval : null
|
||||
],
|
||||
fixedTimewindow: this.fb.control({
|
||||
value: this.timewindow.history && typeof this.timewindow.history.fixedTimewindow !== 'undefined'
|
||||
? this.timewindow.history.fixedTimewindow : null,
|
||||
disabled: hideInterval
|
||||
}),
|
||||
quickInterval: this.fb.control({
|
||||
value: this.timewindow.history && typeof this.timewindow.history.quickInterval !== 'undefined'
|
||||
? this.timewindow.history.quickInterval : 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
|
||||
}),
|
||||
limit: this.fb.control({
|
||||
value: this.timewindow.aggregation && typeof this.timewindow.aggregation.limit !== 'undefined'
|
||||
? this.checkLimit(this.timewindow.aggregation.limit) : null,
|
||||
disabled: hideAggInterval
|
||||
}, [])
|
||||
}
|
||||
),
|
||||
timezone: this.fb.control({
|
||||
value: this.timewindow.timezone !== 'undefined'
|
||||
? this.timewindow.timezone : null,
|
||||
disabled: hideTimezone
|
||||
})
|
||||
realtime: this.fb.group({
|
||||
realtimeType: [{
|
||||
value: isDefined(realtime?.realtimeType) ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL,
|
||||
disabled: hideInterval
|
||||
}],
|
||||
timewindowMs: [{
|
||||
value: isDefined(realtime?.timewindowMs) ? this.timewindow.realtime.timewindowMs : null,
|
||||
disabled: hideInterval || hideLastInterval
|
||||
}],
|
||||
interval: [isDefined(realtime?.interval) ? this.timewindow.realtime.interval : null],
|
||||
quickInterval: [{
|
||||
value: isDefined(realtime?.quickInterval) ? this.timewindow.realtime.quickInterval : null,
|
||||
disabled: hideInterval || hideQuickInterval
|
||||
}]
|
||||
}),
|
||||
history: this.fb.group({
|
||||
historyType: [{
|
||||
value: isDefined(history?.historyType) ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL,
|
||||
disabled: hideInterval
|
||||
}],
|
||||
timewindowMs: [{
|
||||
value: isDefined(history?.timewindowMs) ? this.timewindow.history.timewindowMs : null,
|
||||
disabled: hideInterval
|
||||
}],
|
||||
interval: [ isDefined(history?.interval) ? this.timewindow.history.interval : null
|
||||
],
|
||||
fixedTimewindow: [{
|
||||
value: isDefined(history?.fixedTimewindow) ? this.timewindow.history.fixedTimewindow : null,
|
||||
disabled: hideInterval
|
||||
}],
|
||||
quickInterval: [{
|
||||
value: isDefined(history?.quickInterval) ? this.timewindow.history.quickInterval : null,
|
||||
disabled: hideInterval
|
||||
}]
|
||||
}),
|
||||
aggregation: this.fb.group({
|
||||
type: [{
|
||||
value: isDefined(aggregation?.type) ? this.timewindow.aggregation.type : null,
|
||||
disabled: hideAggregation
|
||||
}],
|
||||
limit: [{
|
||||
value: isDefined(aggregation?.limit) ? this.checkLimit(this.timewindow.aggregation.limit) : null,
|
||||
disabled: hideAggInterval
|
||||
}, []]
|
||||
}),
|
||||
timezone: [{
|
||||
value: isDefined(this.timewindow.timezone) ? this.timewindow.timezone : null,
|
||||
disabled: hideTimezone
|
||||
}]
|
||||
});
|
||||
this.updateValidators();
|
||||
this.timewindowForm.get('aggregation.type').valueChanges.subscribe(() => {
|
||||
|
||||
@ -15,30 +15,34 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<button *ngIf="asButton" cdkOverlayOrigin #timewindowPanelOrigin="cdkOverlayOrigin" [disabled]="timewindowDisabled"
|
||||
<button *ngIf="asButton"
|
||||
[disabled]="timewindowDisabled"
|
||||
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>
|
||||
<span>{{innerValue?.displayValue}}</span>
|
||||
</button>
|
||||
<section *ngIf="!asButton" cdkOverlayOrigin #timewindowPanelOrigin="cdkOverlayOrigin"
|
||||
class="tb-timewindow" fxLayout="row" fxLayoutAlign="start center">
|
||||
<section *ngIf="!asButton"
|
||||
class="tb-timewindow"
|
||||
fxLayout="row"
|
||||
fxLayoutAlign="start center">
|
||||
<button *ngIf="direction === 'left'" [disabled]="timewindowDisabled" mat-icon-button class="tb-mat-32"
|
||||
type="button"
|
||||
(click)="openEditMode()"
|
||||
(click)="toggleTimewindow($event)"
|
||||
matTooltip="{{ 'timewindow.edit' | translate }}"
|
||||
[matTooltipPosition]="tooltipPosition">
|
||||
<mat-icon class="material-icons">query_builder</mat-icon>
|
||||
</button>
|
||||
<span [fxHide]="hideLabel()"
|
||||
(click)="openEditMode()"
|
||||
(click)="toggleTimewindow($event)"
|
||||
matTooltip="{{ 'timewindow.edit' | translate }}"
|
||||
[matTooltipPosition]="tooltipPosition">
|
||||
{{innerValue?.displayValue}} <span [fxShow]="innerValue?.displayTimezoneAbbr !== ''">| <span class="timezone-abbr">{{innerValue.displayTimezoneAbbr}}</span></span>
|
||||
</span>
|
||||
<button *ngIf="direction === 'right'" [disabled]="timewindowDisabled" mat-icon-button class="tb-mat-32"
|
||||
type="button"
|
||||
(click)="openEditMode()"
|
||||
(click)="toggleTimewindow($event)"
|
||||
matTooltip="{{ 'timewindow.edit' | translate }}"
|
||||
[matTooltipPosition]="tooltipPosition">
|
||||
<mat-icon class="material-icons">query_builder</mat-icon>
|
||||
|
||||
@ -17,14 +17,13 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
Inject,
|
||||
Injector,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
StaticProvider,
|
||||
ViewChild,
|
||||
ViewContainerRef
|
||||
} from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
@ -40,21 +39,16 @@ import {
|
||||
Timewindow,
|
||||
TimewindowType
|
||||
} from '@shared/models/time/time.models';
|
||||
import { DatePipe, DOCUMENT } from '@angular/common';
|
||||
import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
|
||||
import {
|
||||
TIMEWINDOW_PANEL_DATA,
|
||||
TimewindowPanelComponent,
|
||||
TimewindowPanelData
|
||||
} from '@shared/components/time/timewindow-panel.component';
|
||||
import { ComponentPortal } from '@angular/cdk/portal';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { TIMEWINDOW_PANEL_DATA, TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component';
|
||||
import { MediaBreakpoints } from '@shared/models/constants';
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
import { WINDOW } from '@core/services/window.service';
|
||||
import { TimeService } from '@core/services/time.service';
|
||||
import { TooltipPosition } from '@angular/material/tooltip';
|
||||
import { deepClone, isDefinedAndNotNull } from '@core/utils';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
|
||||
import { ComponentPortal } from '@angular/cdk/portal';
|
||||
|
||||
// @dynamic
|
||||
@Component({
|
||||
@ -174,24 +168,21 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@ViewChild('timewindowPanelOrigin') timewindowPanelOrigin: CdkOverlayOrigin;
|
||||
|
||||
innerValue: Timewindow;
|
||||
|
||||
timewindowDisabled: boolean;
|
||||
|
||||
private propagateChange = (_: any) => {};
|
||||
|
||||
constructor(private translate: TranslateService,
|
||||
constructor(private overlay: Overlay,
|
||||
private translate: TranslateService,
|
||||
private timeService: TimeService,
|
||||
private millisecondsToTimeStringPipe: MillisecondsToTimeStringPipe,
|
||||
private datePipe: DatePipe,
|
||||
private overlay: Overlay,
|
||||
private cd: ChangeDetectorRef,
|
||||
private nativeElement: ElementRef,
|
||||
public viewContainerRef: ViewContainerRef,
|
||||
public breakpointObserver: BreakpointObserver,
|
||||
@Inject(DOCUMENT) private document: Document,
|
||||
@Inject(WINDOW) private window: Window) {
|
||||
public breakpointObserver: BreakpointObserver) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@ -200,82 +191,51 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
|
||||
ngOnDestroy(): void {
|
||||
}
|
||||
|
||||
openEditMode() {
|
||||
if (this.timewindowDisabled) {
|
||||
return;
|
||||
toggleTimewindow($event: Event) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
const isGtXs = this.breakpointObserver.isMatched(MediaBreakpoints['gt-xs']);
|
||||
const position = this.overlay.position();
|
||||
const config = new OverlayConfig({
|
||||
panelClass: 'tb-timewindow-panel',
|
||||
backdropClass: 'cdk-overlay-transparent-backdrop',
|
||||
hasBackdrop: isGtXs,
|
||||
hasBackdrop: true,
|
||||
maxHeight: '80vh',
|
||||
height: 'min-content'
|
||||
});
|
||||
if (isGtXs) {
|
||||
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 = {
|
||||
originX,
|
||||
originY,
|
||||
overlayX,
|
||||
overlayY
|
||||
};
|
||||
config.positionStrategy = position.flexibleConnectedTo(this.timewindowPanelOrigin.elementRef)
|
||||
.withPositions([connectedPosition]);
|
||||
} else {
|
||||
config.minWidth = '100%';
|
||||
config.minHeight = '100%';
|
||||
config.positionStrategy = position.global().top('0%').left('0%')
|
||||
.right('0%').bottom('0%');
|
||||
}
|
||||
config.hasBackdrop = true;
|
||||
const connectedPosition: ConnectedPosition = {
|
||||
originX: 'start',
|
||||
originY: 'bottom',
|
||||
overlayX: 'start',
|
||||
overlayY: 'top'
|
||||
};
|
||||
config.positionStrategy = this.overlay.position().flexibleConnectedTo(this.nativeElement)
|
||||
.withPositions([connectedPosition]);
|
||||
|
||||
const overlayRef = this.overlay.create(config);
|
||||
|
||||
overlayRef.backdropClick().subscribe(() => {
|
||||
overlayRef.dispose();
|
||||
});
|
||||
|
||||
const injector = this._createTimewindowPanelInjector(
|
||||
overlayRef,
|
||||
const providers: StaticProvider[] = [
|
||||
{
|
||||
timewindow: deepClone(this.innerValue),
|
||||
historyOnly: this.historyOnly,
|
||||
quickIntervalOnly: this.quickIntervalOnly,
|
||||
aggregation: this.aggregation,
|
||||
timezone: this.timezone,
|
||||
isEdit: this.isEdit
|
||||
provide: TIMEWINDOW_PANEL_DATA,
|
||||
useValue: {
|
||||
timewindow: deepClone(this.innerValue),
|
||||
historyOnly: this.historyOnly,
|
||||
quickIntervalOnly: this.quickIntervalOnly,
|
||||
aggregation: this.aggregation,
|
||||
timezone: this.timezone,
|
||||
isEdit: this.isEdit
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: OverlayRef,
|
||||
useValue: overlayRef
|
||||
}
|
||||
);
|
||||
|
||||
const componentRef = overlayRef.attach(new ComponentPortal(TimewindowPanelComponent, this.viewContainerRef, injector));
|
||||
];
|
||||
const injector = Injector.create({parent: this.viewContainerRef.injector, providers});
|
||||
const componentRef = overlayRef.attach(new ComponentPortal(TimewindowPanelComponent,
|
||||
this.viewContainerRef, injector));
|
||||
componentRef.onDestroy(() => {
|
||||
if (componentRef.instance.result) {
|
||||
this.innerValue = componentRef.instance.result;
|
||||
@ -284,14 +244,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
|
||||
this.notifyChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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});
|
||||
this.cd.detectChanges();
|
||||
}
|
||||
|
||||
private onHistoryOnlyChanged(): boolean {
|
||||
|
||||
@ -505,7 +505,7 @@ export const baseDetailsPageByEntityType = new Map<EntityType, string>([
|
||||
[EntityType.DEVICE, '/entities/devices'],
|
||||
[EntityType.DEVICE_PROFILE, '/profiles/deviceProfiles'],
|
||||
[EntityType.ASSET_PROFILE, '/profiles/assetProfiles'],
|
||||
[EntityType.RULE_CHAIN, '/features/ruleChains'],
|
||||
[EntityType.RULE_CHAIN, '/ruleChains'],
|
||||
[EntityType.EDGE, '/edgeManagement/instances'],
|
||||
[EntityType.ENTITY_VIEW, '/entities/entityViews'],
|
||||
[EntityType.TB_RESOURCE, '/resources/resources-library'],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user