diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java index 08a5fdd3e9..e7f21a18d0 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java @@ -203,7 +203,8 @@ public class CassandraEntitiesToSqlMigrateService implements EntitiesMigrateServ stringColumn("entity_type"), stringColumn("event_type"), stringColumn("event_uid"), - stringColumn("body")), + stringColumn("body"), + new CassandraToSqlEventTsColumn()), new CassandraToSqlTable("relation", idColumn("from_id"), stringColumn("from_type"), @@ -245,7 +246,9 @@ public class CassandraEntitiesToSqlMigrateService implements EntitiesMigrateServ stringColumn("zip"), stringColumn("phone"), stringColumn("email"), - stringColumn("additional_info")), + stringColumn("additional_info"), + booleanColumn("isolated_tb_core"), + booleanColumn("isolated_tb_rule_engine")), new CassandraToSqlTable("user_credentials", idColumn("id"), idColumn("user_id"), diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java new file mode 100644 index 0000000000..b488e812fe --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.install.migrate; + +import com.datastax.driver.core.Row; + +import java.util.UUID; + +import static org.thingsboard.server.dao.model.ModelConstants.EPOCH_DIFF; + +public class CassandraToSqlEventTsColumn extends CassandraToSqlColumn { + + CassandraToSqlEventTsColumn() { + super("id", "ts", CassandraToSqlColumnType.BIGINT, null); + } + + @Override + public String getColumnValue(Row row) { + UUID id = row.getUUID(getIndex()); + long ts = getTs(id); + return ts + ""; + } + + private long getTs(UUID uuid) { + return (uuid.timestamp() - EPOCH_DIFF) / 10000; + } +} \ No newline at end of file diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java index 408f81a845..76db7920a6 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java @@ -15,12 +15,9 @@ */ package org.thingsboard.server.dao.util; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) -@ConditionalOnProperty(prefix = "database.entities", value = "type", havingValue = "sql") public @interface SqlDao { } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index 2f3c72d01b..78230332b4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -38,7 +38,7 @@ public interface AlarmRepository extends CrudRepository { @Param("alarmType") String alarmType, Pageable pageable); - @Query("SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a, " + + @Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a, " + "RelationEntity re " + "WHERE a.tenantId = :tenantId " + "AND a.id = re.toId AND re.toType = 'ALARM' " + @@ -51,7 +51,21 @@ public interface AlarmRepository extends CrudRepository { "AND (:idOffset IS NULL OR a.id < :idOffset) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%'))" + "OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%'))" + - "OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))") + "OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))", + countQuery = "SELECT count(a) FROM AlarmEntity a, " + + "RelationEntity re " + + "WHERE a.tenantId = :tenantId " + + "AND a.id = re.toId AND re.toType = 'ALARM' " + + "AND re.relationTypeGroup = 'ALARM' " + + "AND re.relationType = :relationType " + + "AND re.fromId = :affectedEntityId " + + "AND re.fromType = :affectedEntityType " + + "AND (:startId IS NULL OR a.id >= :startId) " + + "AND (:endId IS NULL OR a.id <= :endId) " + + "AND (:idOffset IS NULL OR a.id < :idOffset) " + + "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%'))" + + "OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%'))" + + "OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))") Page findAlarms(@Param("tenantId") String tenantId, @Param("affectedEntityId") String affectedEntityId, @Param("affectedEntityType") String affectedEntityType, diff --git a/ui-ngx/src/app/core/http/public-api.ts b/ui-ngx/src/app/core/http/public-api.ts index 761f04989d..db477c3a8c 100644 --- a/ui-ngx/src/app/core/http/public-api.ts +++ b/ui-ngx/src/app/core/http/public-api.ts @@ -28,6 +28,7 @@ export * from './entity-relation.service'; export * from './entity-view.service'; export * from './event.service'; export * from './http-utils'; +export * from './queue.service'; export * from './rule-chain.service'; export * from './tenant.service'; export * from './user.service'; diff --git a/ui-ngx/src/app/core/http/queue.service.ts b/ui-ngx/src/app/core/http/queue.service.ts new file mode 100644 index 0000000000..42beadb81a --- /dev/null +++ b/ui-ngx/src/app/core/http/queue.service.ts @@ -0,0 +1,36 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; +import { Observable } from 'rxjs'; +import { ServiceType } from '@shared/models/queue.models'; + +@Injectable({ + providedIn: 'root' +}) +export class QueueService { + + constructor( + private http: HttpClient + ) { } + + public getTenantQueuesByServiceType(serviceType: ServiceType, config?: RequestConfig): Observable> { + return this.http.get>(`/api/tenant/queues?serviceType=${serviceType}`, + defaultHttpOptionsFromConfig(config)); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html index 48c57184ab..7d0ccd5056 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html @@ -56,6 +56,16 @@ +
+ +
{{ 'tenant.isolated-tb-core' | translate }}
+
{{'tenant.isolated-tb-core-details' | translate}}
+
+ +
{{ 'tenant.isolated-tb-rule-engine' | translate }}
+
{{'tenant.isolated-tb-rule-engine-details' | translate}}
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss new file mode 100644 index 0000000000..b05c6324fa --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host ::ng-deep { + .mat-checkbox.hinted-checkbox { + .mat-checkbox-inner-container { + margin-top: 4px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts index c8317a20c5..f624a5d5cf 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts @@ -26,7 +26,8 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod @Component({ selector: 'tb-tenant', - templateUrl: './tenant.component.html' + templateUrl: './tenant.component.html', + styleUrls: ['./tenant.component.scss'] }) export class TenantComponent extends ContactBasedComponent { @@ -50,6 +51,8 @@ export class TenantComponent extends ContactBasedComponent { return this.fb.group( { title: [entity ? entity.title : '', [Validators.required]], + isolatedTbCore: [entity ? entity.isolatedTbCore : false, []], + isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []], additionalInfo: this.fb.group( { description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] @@ -61,6 +64,8 @@ export class TenantComponent extends ContactBasedComponent { updateEntityForm(entity: Tenant) { this.entityForm.patchValue({title: entity.title}); + this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}); + this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}); this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); } diff --git a/ui-ngx/src/app/shared/components/queue/queue-type-list.component.html b/ui-ngx/src/app/shared/components/queue/queue-type-list.component.html new file mode 100644 index 0000000000..ccdf4a76ec --- /dev/null +++ b/ui-ngx/src/app/shared/components/queue/queue-type-list.component.html @@ -0,0 +1,43 @@ + + + {{ 'queue.name' | translate }} + + + + + + + + + {{ 'queue.name_required' | translate }} + + diff --git a/ui-ngx/src/app/shared/components/queue/queue-type-list.component.ts b/ui-ngx/src/app/shared/components/queue/queue-type-list.component.ts new file mode 100644 index 0000000000..7e65bfbd91 --- /dev/null +++ b/ui-ngx/src/app/shared/components/queue/queue-type-list.component.ts @@ -0,0 +1,177 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Observable } from 'rxjs'; +import { map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { QueueService } from '@core/http/queue.service'; +import { ServiceType } from '@shared/models/queue.models'; + +@Component({ + selector: 'tb-queue-type-list', + templateUrl: './queue-type-list.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => QueueTypeListComponent), + multi: true + }] +}) +export class QueueTypeListComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy { + + queueFormGroup: FormGroup; + + modelValue: string | null; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + @Input() + queueType: ServiceType; + + @ViewChild('queueInput', {static: true}) queueInput: ElementRef; + + filteredQueues: Observable>; + + queues: Observable>; + + searchText = ''; + + private dirty = false; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + public translate: TranslateService, + private queueService: QueueService, + private fb: FormBuilder) { + this.queueFormGroup = this.fb.group({ + queue: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.filteredQueues = this.queueFormGroup.get('queue').valueChanges + .pipe( + tap(value => { + this.updateView(value); + }), + map(value => value ? value : ''), + mergeMap(queue => this.fetchQueues(queue) ) + ); + } + + ngAfterViewInit(): void { + } + + ngOnDestroy(): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.queueFormGroup.disable({emitEvent: false}); + } else { + this.queueFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: string | null): void { + this.searchText = ''; + this.modelValue = value; + this.queueFormGroup.get('queue').patchValue(value, {emitEvent: false}); + this.dirty = true; + } + + onFocus() { + if (this.dirty) { + this.queueFormGroup.get('queue').updateValueAndValidity({onlySelf: true, emitEvent: true}); + this.dirty = false; + } + } + + updateView(value: string | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + displayQueueFn(queue?: string): string | undefined { + return queue ? queue : undefined; + } + + fetchQueues(searchText?: string): Observable> { + this.searchText = searchText; + return this.getQueues().pipe( + map(queues => { + const result = queues.filter( queue => { + return searchText ? queue.toUpperCase().startsWith(searchText.toUpperCase()) : true; + }); + if (result.length) { + if (searchText && searchText.length && result.indexOf(searchText) === -1) { + result.push(searchText); + } + result.sort(); + } else if (searchText && searchText.length) { + result.push(searchText); + } + return result; + }) + ); + } + + getQueues(): Observable> { + if (!this.queues) { + this.queues = this.queueService. + getTenantQueuesByServiceType(this.queueType, {ignoreLoading: true}).pipe( + publishReplay(1), + refCount() + ); + } + return this.queues; + } + + clear() { + this.queueFormGroup.get('queue').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.queueInput.nativeElement.blur(); + this.queueInput.nativeElement.focus(); + }, 0); + } + +} diff --git a/ui-ngx/src/app/shared/models/public-api.ts b/ui-ngx/src/app/shared/models/public-api.ts index 856c994943..cbc030a4fa 100644 --- a/ui-ngx/src/app/shared/models/public-api.ts +++ b/ui-ngx/src/app/shared/models/public-api.ts @@ -36,6 +36,7 @@ export * from './error.models'; export * from './event.models'; export * from './login.models'; export * from './material.models'; +export * from './queue.models'; export * from './relation.models'; export * from './rule-chain.models'; export * from './rule-node.models'; diff --git a/ui-ngx/src/app/shared/models/queue.models.ts b/ui-ngx/src/app/shared/models/queue.models.ts new file mode 100644 index 0000000000..ff8f386dbe --- /dev/null +++ b/ui-ngx/src/app/shared/models/queue.models.ts @@ -0,0 +1,22 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +export enum ServiceType { + TB_CORE = 'TB_CORE', + TB_RULE_ENGINE = 'TB_RULE_ENGINE', + TB_TRANSPORT = 'TB_TRANSPORT', + JS_EXECUTOR = 'JS_EXECUTOR' +} diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index 266981db1f..4f2e01ea81 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -21,5 +21,7 @@ import {TenantId} from './id/tenant-id'; export interface Tenant extends ContactBased { title: string; region: string; + isolatedTbCore: boolean; + isolatedTbRuleEngine: boolean; additionalInfo?: any; } diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 5008f10459..e94c8a6641 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -129,6 +129,7 @@ import { JsonObjectEditDialogComponent } from '@shared/components/dialog/json-ob import { HistorySelectorComponent } from './components/time/history-selector/history-selector.component'; import { TbTemplatePipe } from '@shared/pipe/template.pipe'; import { EntityGatewaySelectComponent } from '@shared/components/entity/entity-gateway-select.component'; +import { QueueTypeListComponent } from '@shared/components/queue/queue-type-list.component'; @NgModule({ providers: [ @@ -179,6 +180,7 @@ import { EntityGatewaySelectComponent } from '@shared/components/entity/entity-g EntityKeysListComponent, EntityListSelectComponent, EntityTypeListComponent, + QueueTypeListComponent, RelationTypeAutocompleteComponent, SocialSharePanelComponent, JsonObjectEditComponent, @@ -297,6 +299,7 @@ import { EntityGatewaySelectComponent } from '@shared/components/entity/entity-g EntityKeysListComponent, EntityListSelectComponent, EntityTypeListComponent, + QueueTypeListComponent, RelationTypeAutocompleteComponent, SocialSharePanelComponent, JsonObjectEditComponent, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 3112cba1af..4ef7b845c7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1514,6 +1514,11 @@ "help": "Help", "reset-debug-mode": "Reset debug mode in all nodes" }, + "queue": { + "select_name": "Select queue name", + "name": "Queue Name", + "name_required": "Queue name is required!" + }, "tenant": { "tenant": "Tenant", "tenants": "Tenants", @@ -1541,7 +1546,11 @@ "no-tenants-matching": "No tenants matching '{{entity}}' were found.", "tenant-required": "Tenant is required", "search": "Search tenants", - "selected-tenants": "{ count, plural, 1 {1 tenant} other {# tenants} } selected" + "selected-tenants": "{ count, plural, 1 {1 tenant} other {# tenants} } selected", + "isolated-tb-core": "Processing in isolated ThingsBoard Core container", + "isolated-tb-rule-engine": "Processing in isolated ThingsBoard Rule Engine container", + "isolated-tb-core-details": "Requires separate microservice(s) per isolated Tenant", + "isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant" }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", diff --git a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json index 514cd9a8e3..0d6c9abdc9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json +++ b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json @@ -1404,6 +1404,11 @@ "help": "Помощь", "reset-debug-mode": "Сбросить режим отладки во всех правилах" }, + "queue": { + "select_name": "Выберите имя для Queue", + "name": "Имя для Queue", + "name_required": "Поле 'Имя для Queue' обязательно к заполнению!" + }, "tenant": { "tenant": "Владелец", "tenants": "Владельцы", diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index 92e8dc4a99..fa0da05e0a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -1970,6 +1970,11 @@ "no-timezones-matching": "Не знайдено жодних часових поясів, які відповідають '{{timezone}}'.", "timezone-required": "Необхідно вказати часовий пояс." }, + "queue": { + "select_name": "Виберіть ім'я для Queue", + "name": "Iм'я для Queue", + "name_required": "Поле 'Имя для Queue' обязательно к заполнению!" + }, "tenant": { "tenant": "Власник", "tenants": "Власники",