From 868234f13df958000e7d72a294f6ab6c613c9b5c Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 26 Aug 2020 15:00:11 +0300 Subject: [PATCH] Tenant, device profiles UI --- ui-ngx/src/app/core/http/tenant.service.ts | 10 ++- .../home/pages/device/device.component.ts | 4 ++ .../device/devices-table-config.resolver.ts | 9 +-- .../pages/tenant/tenant-tabs.component.ts | 4 +- .../home/pages/tenant/tenant.component.ts | 24 +++---- .../tenant/tenants-table-config.resolver.ts | 32 +++++----- ui-ngx/src/app/shared/models/constants.ts | 2 + ui-ngx/src/app/shared/models/device.models.ts | 63 ++++++++++++++++++- .../app/shared/models/entity-type.models.ts | 42 +++++++++++++ ui-ngx/src/app/shared/models/entity.models.ts | 6 ++ .../app/shared/models/id/device-profile-id.ts | 26 ++++++++ ui-ngx/src/app/shared/models/id/public-api.ts | 2 + .../app/shared/models/id/tenant-profile-id.ts | 26 ++++++++ .../app/shared/models/query/query.models.ts | 17 +++-- ui-ngx/src/app/shared/models/tenant.model.ts | 22 ++++++- .../assets/locale/locale.constant-en_US.json | 28 ++++++++- 16 files changed, 271 insertions(+), 46 deletions(-) create mode 100644 ui-ngx/src/app/shared/models/id/device-profile-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/tenant-profile-id.ts diff --git a/ui-ngx/src/app/core/http/tenant.service.ts b/ui-ngx/src/app/core/http/tenant.service.ts index b98955f16b..2306c4852f 100644 --- a/ui-ngx/src/app/core/http/tenant.service.ts +++ b/ui-ngx/src/app/core/http/tenant.service.ts @@ -20,7 +20,7 @@ import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; -import { Tenant } from '@shared/models/tenant.model'; +import { Tenant, TenantInfo } from '@shared/models/tenant.model'; @Injectable({ providedIn: 'root' @@ -35,10 +35,18 @@ export class TenantService { return this.http.get>(`/api/tenants${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); } + public getTenantInfos(pageLink: PageLink, config?: RequestConfig): Observable> { + return this.http.get>(`/api/tenantInfos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); + } + public getTenant(tenantId: string, config?: RequestConfig): Observable { return this.http.get(`/api/tenant/${tenantId}`, defaultHttpOptionsFromConfig(config)); } + public getTenantInfo(tenantId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/tenant/info/${tenantId}`, defaultHttpOptionsFromConfig(config)); + } + public saveTenant(tenant: Tenant, config?: RequestConfig): Observable { return this.http.post('/api/tenant', tenant, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.ts b/ui-ngx/src/app/modules/home/pages/device/device.component.ts index d187c8bf0c..53a3eaa3db 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.ts @@ -70,8 +70,10 @@ export class DeviceComponent extends EntityComponent { return this.fb.group( { name: [entity ? entity.name : '', [Validators.required]], + deviceProfileId: [entity ? entity.deviceProfileId : null, [Validators.required]], type: [entity ? entity.type : null, [Validators.required]], label: [entity ? entity.label : ''], + deviceData: [entity ? entity.deviceData : null, [Validators.required]], additionalInfo: this.fb.group( { gateway: [entity && entity.additionalInfo ? entity.additionalInfo.gateway : false], @@ -84,8 +86,10 @@ export class DeviceComponent extends EntityComponent { updateForm(entity: DeviceInfo) { this.entityForm.patchValue({name: entity.name}); + this.entityForm.patchValue({deviceProfileId: entity.deviceProfileId}); this.entityForm.patchValue({type: entity.type}); this.entityForm.patchValue({label: entity.label}); + this.entityForm.patchValue({deviceData: entity.deviceData}); this.entityForm.patchValue({additionalInfo: {gateway: entity.additionalInfo ? entity.additionalInfo.gateway : false}}); this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 3216e776dd..d2bef0da4f 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -150,13 +150,14 @@ export class DevicesTableConfigResolver implements Resolve> { const columns: Array> = [ new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '150px'), - new EntityTableColumn('name', 'device.name', '25%'), - new EntityTableColumn('type', 'device.device-type', '25%'), - new EntityTableColumn('label', 'device.label', '25%') + new EntityTableColumn('name', 'device.name', '20%'), + new EntityTableColumn('deviceProfileName', 'device-profile.device-profile', '20%'), + new EntityTableColumn('type', 'device.device-type', '20%'), + new EntityTableColumn('label', 'device.label', '20%') ]; if (deviceScope === 'tenant') { columns.push( - new EntityTableColumn('customerTitle', 'customer.customer', '25%'), + new EntityTableColumn('customerTitle', 'customer.customer', '20%'), new EntityTableColumn('customerIsPublic', 'device.public', '60px', entity => { return checkBoxCell(entity.customerIsPublic); diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.ts index f6374330dc..388de81fda 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.ts @@ -18,14 +18,14 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; -import { Tenant } from '@shared/models/tenant.model'; +import { TenantInfo } from '@shared/models/tenant.model'; @Component({ selector: 'tb-tenant-tabs', templateUrl: './tenant-tabs.component.html', styleUrls: [] }) -export class TenantTabsComponent extends EntityTabsComponent { +export class TenantTabsComponent extends EntityTabsComponent { constructor(protected store: Store) { super(store); 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 8a76830093..19fe1f342d 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 @@ -18,7 +18,7 @@ import { Component, Inject } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Tenant } from '@app/shared/models/tenant.model'; +import { Tenant, TenantInfo } from '@app/shared/models/tenant.model'; import { ActionNotificationShow } from '@app/core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; import { ContactBasedComponent } from '../../components/entity/contact-based.component'; @@ -29,12 +29,12 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod templateUrl: './tenant.component.html', styleUrls: ['./tenant.component.scss'] }) -export class TenantComponent extends ContactBasedComponent { +export class TenantComponent extends ContactBasedComponent { constructor(protected store: Store, protected translate: TranslateService, - @Inject('entity') protected entityValue: Tenant, - @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, + @Inject('entity') protected entityValue: TenantInfo, + @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, protected fb: FormBuilder) { super(store, fb, entityValue, entitiesTableConfigValue); } @@ -47,12 +47,13 @@ export class TenantComponent extends ContactBasedComponent { } } - buildEntityForm(entity: Tenant): FormGroup { + buildEntityForm(entity: TenantInfo): FormGroup { return this.fb.group( { title: [entity ? entity.title : '', [Validators.required]], - isolatedTbCore: [entity ? entity.isolatedTbCore : false, []], - isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []], + tenantProfileId: [entity ? entity.tenantProfileId : null, [Validators.required]], + // isolatedTbCore: [entity ? entity.isolatedTbCore : false, []], + // isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []], additionalInfo: this.fb.group( { description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] @@ -64,8 +65,9 @@ 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({tenantProfileId: entity.tenantProfileId}); + // this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}); + // this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}); this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); } @@ -73,10 +75,10 @@ export class TenantComponent extends ContactBasedComponent { if (this.entityForm) { if (this.isEditValue) { this.entityForm.enable({emitEvent: false}); - if (!this.isAdd) { + /* if (!this.isAdd) { this.entityForm.get('isolatedTbCore').disable({emitEvent: false}); this.entityForm.get('isolatedTbRuleEngine').disable({emitEvent: false}); - } + } */ } else { this.entityForm.disable({emitEvent: false}); } diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts index 11e1d1e466..b1515ee62d 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts @@ -18,7 +18,7 @@ import { Injectable } from '@angular/core'; import { Resolve, Router } from '@angular/router'; -import { Tenant } from '@shared/models/tenant.model'; +import { TenantInfo } from '@shared/models/tenant.model'; import { DateEntityTableColumn, EntityTableColumn, @@ -31,11 +31,12 @@ import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared import { TenantComponent } from '@modules/home/pages/tenant/tenant.component'; import { EntityAction } from '@home/models/entity/entity-component.models'; import { TenantTabsComponent } from '@home/pages/tenant/tenant-tabs.component'; +import { mergeMap } from 'rxjs/operators'; @Injectable() -export class TenantsTableConfigResolver implements Resolve> { +export class TenantsTableConfigResolver implements Resolve> { - private readonly config: EntityTableConfig = new EntityTableConfig(); + private readonly config: EntityTableConfig = new EntityTableConfig(); constructor(private tenantService: TenantService, private translate: TranslateService, @@ -49,11 +50,12 @@ export class TenantsTableConfigResolver implements Resolve('createdTime', 'common.created-time', this.datePipe, '150px'), - new EntityTableColumn('title', 'tenant.title', '25%'), - new EntityTableColumn('email', 'contact.email', '25%'), - new EntityTableColumn('country', 'contact.country', '25%'), - new EntityTableColumn('city', 'contact.city', '25%') + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '150px'), + new EntityTableColumn('title', 'tenant-profile.tenant-profile', '20%'), + new EntityTableColumn('tenantProfileName', 'tenant.title', '20%'), + new EntityTableColumn('email', 'contact.email', '20%'), + new EntityTableColumn('country', 'contact.country', '20%'), + new EntityTableColumn('city', 'contact.city', '20%') ); this.config.cellActionDescriptors.push( @@ -70,27 +72,29 @@ export class TenantsTableConfigResolver implements Resolve this.translate.instant('tenant.delete-tenants-title', {count}); this.config.deleteEntitiesContent = () => this.translate.instant('tenant.delete-tenants-text'); - this.config.entitiesFetchFunction = pageLink => this.tenantService.getTenants(pageLink); - this.config.loadEntity = id => this.tenantService.getTenant(id.id); - this.config.saveEntity = tenant => this.tenantService.saveTenant(tenant); + this.config.entitiesFetchFunction = pageLink => this.tenantService.getTenantInfos(pageLink); + this.config.loadEntity = id => this.tenantService.getTenantInfo(id.id); + this.config.saveEntity = tenant => this.tenantService.saveTenant(tenant).pipe( + mergeMap((savedTenant) => this.tenantService.getTenantInfo(savedTenant.id.id)) + ); this.config.deleteEntity = id => this.tenantService.deleteTenant(id.id); this.config.onEntityAction = action => this.onTenantAction(action); } - resolve(): EntityTableConfig { + resolve(): EntityTableConfig { this.config.tableTitle = this.translate.instant('tenant.tenants'); return this.config; } - manageTenantAdmins($event: Event, tenant: Tenant) { + manageTenantAdmins($event: Event, tenant: TenantInfo) { if ($event) { $event.stopPropagation(); } this.router.navigateByUrl(`tenants/${tenant.id.id}/users`); } - onTenantAction(action: EntityAction): boolean { + onTenantAction(action: EntityAction): boolean { switch (action.action) { case 'manageTenantAdmins': this.manageTenantAdmins(action.event, action.entity); diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 5e8665c655..cff9b26c2b 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -107,9 +107,11 @@ export const HelpLinks = { ruleNodeRestApiCall: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#rest-api-call-node', ruleNodeSendEmail: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#send-email-node', tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', + tenantProfiles: helpBaseUrl + '/docs/user-guide/ui/tenant-profiles', customers: helpBaseUrl + '/docs/user-guide/ui/customers', users: helpBaseUrl + '/docs/user-guide/ui/users', devices: helpBaseUrl + '/docs/user-guide/ui/devices', + deviceProfiles: helpBaseUrl + '/docs/user-guide/ui/device-profiles', assets: helpBaseUrl + '/docs/user-guide/ui/assets', entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views', entitiesImport: helpBaseUrl + '/docs/user-guide/bulk-provisioning', diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 780f60904e..74f48a09bd 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -20,6 +20,62 @@ import { TenantId } from '@shared/models/id/tenant-id'; import { CustomerId } from '@shared/models/id/customer-id'; import { DeviceCredentialsId } from '@shared/models/id/device-credentials-id'; import { EntitySearchQuery } from '@shared/models/relation.models'; +import { DeviceProfileId } from '@shared/models/id/device-profile-id'; +import { RuleChainId } from '@shared/models/id/rule-chain-id'; +import { EntityInfoData } from '@shared/models/entity.models'; + +export enum DeviceProfileType { + DEFAULT = 'DEFAULT', + LWM2M = 'LWM2M' +} + +export interface DefaultDeviceProfileConfiguration { + [key: string]: any; +} +export interface Lwm2mDeviceProfileConfiguration { + [key: string]: any; +} + +export type DeviceProfileConfigurations = DefaultDeviceProfileConfiguration & Lwm2mDeviceProfileConfiguration; + +export interface DeviceProfileConfiguration extends DeviceProfileConfigurations { + type: DeviceProfileType; +} + +export interface DeviceProfileData { + configuration: DeviceProfileConfiguration; +} + +export interface DeviceProfile extends BaseData { + tenantId?: TenantId; + name: string; + description?: string; + isDefault: boolean; + type: DeviceProfileType; + defaultRuleChainId?: RuleChainId; + profileData: DeviceProfileData; +} + +export interface DeviceProfileInfo extends EntityInfoData { + type: DeviceProfileType; +} + +export interface DefaultDeviceConfiguration { + [key: string]: any; +} +export interface Lwm2mDeviceConfiguration { + [key: string]: any; +} + +export type DeviceConfigurations = DefaultDeviceConfiguration & Lwm2mDeviceConfiguration; + +export interface DeviceConfiguration extends DeviceConfigurations { + type: DeviceProfileType; +} + +export interface DeviceData { + configuration: DeviceConfiguration; +} export interface Device extends BaseData { tenantId?: TenantId; @@ -27,12 +83,15 @@ export interface Device extends BaseData { name: string; type: string; label: string; + deviceProfileId: DeviceProfileId; + deviceData: DeviceData; additionalInfo?: any; } export interface DeviceInfo extends Device { customerTitle: string; customerIsPublic: boolean; + deviceProfileName: string; } export enum DeviceCredentialsType { @@ -69,6 +128,6 @@ export enum ClaimResponse { } export interface ClaimResult { - device: Device, - response: ClaimResponse + device: Device; + response: ClaimResponse; } diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 6841a911b7..c6a94ad8b5 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -35,11 +35,13 @@ import { BaseData, HasId } from '@shared/models/base-data'; export enum EntityType { TENANT = 'TENANT', + TENANT_PROFILE = 'TENANT_PROFILE', CUSTOMER = 'CUSTOMER', USER = 'USER', DASHBOARD = 'DASHBOARD', ASSET = 'ASSET', DEVICE = 'DEVICE', + DEVICE_PROFILE = 'DEVICE_PROFILE', ALARM = 'ALARM', RULE_CHAIN = 'RULE_CHAIN', RULE_NODE = 'RULE_NODE', @@ -88,6 +90,20 @@ export const entityTypeTranslations = new Map { } export interface StringFilterPredicate { - type: FilterPredicateType.STRING, + type: FilterPredicateType.STRING; operation: StringOperation; value: FilterPredicateValue; ignoreCase: boolean; } export interface NumericFilterPredicate { - type: FilterPredicateType.NUMERIC, + type: FilterPredicateType.NUMERIC; operation: NumericOperation; value: FilterPredicateValue; } export interface BooleanFilterPredicate { - type: FilterPredicateType.BOOLEAN, + type: FilterPredicateType.BOOLEAN; operation: BooleanOperation; value: FilterPredicateValue; } export interface BaseComplexFilterPredicate { - type: FilterPredicateType.COMPLEX, + type: FilterPredicateType.COMPLEX; operation: ComplexOperation; predicates: Array; } @@ -481,7 +480,7 @@ export interface Filter extends FilterInfo { } export interface Filters { - [id: string]: Filter + [id: string]: Filter; } export interface EntityFilter extends EntityFilters { @@ -535,7 +534,7 @@ export function createDefaultEntityDataPageLink(pageSize: number): EntityDataPag }, direction: Direction.DESC } - } + }; } export const singleEntityDataPageLink: EntityDataPageLink = createDefaultEntityDataPageLink(1); diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index 65a176de0d..7eaedd7c41 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -16,11 +16,29 @@ import { ContactBased } from '@shared/models/contact-based.model'; import { TenantId } from './id/tenant-id'; +import { TenantProfileId } from '@shared/models/id/tenant-profile-id'; +import { BaseData } from '@shared/models/base-data'; + +export interface TenantProfileData { + [key: string]: string; +} + +export interface TenantProfile extends BaseData { + name: string; + description: string; + isDefault: boolean; + isolatedTbCore: boolean; + isolatedTbRuleEngine: boolean; + profileData: TenantProfileData; +} export interface Tenant extends ContactBased { title: string; region: string; - isolatedTbCore: boolean; - isolatedTbRuleEngine: boolean; + tenantProfileId: TenantProfileId; additionalInfo?: any; } + +export interface TenantInfo extends Tenant { + tenantProfileName: string; +} 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 9c36e0ee68..5cab0a7284 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -750,6 +750,15 @@ "search": "Search devices", "selected-devices": "{ count, plural, 1 {1 device} other {# devices} } selected" }, + "device-profile": { + "device-profile": "Device profile", + "device-profiles": "Device profiles", + "add": "Add device profile", + "device-profile-details": "Device profile details", + "no-device-profiles-text": "No device profiles found", + "search": "Search device profiles", + "selected-device-profiles": "{ count, plural, 1 {1 device profile} other {# device profiles} } selected" + }, "dialog": { "close": "Close dialog" }, @@ -806,6 +815,10 @@ "type-devices": "Devices", "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }", "device-name-starts-with": "Devices whose names start with '{{prefix}}'", + "type-device-profile": "Device profile", + "type-device-profiles": "Device profiles", + "list-of-device-profiles": "{ count, plural, 1 {One device profile} other {List of # device profiles} }", + "device-profile-name-starts-with": "Device profiles whose names start with '{{prefix}}'", "type-asset": "Asset", "type-assets": "Assets", "list-of-assets": "{ count, plural, 1 {One asset} other {List of # assets} }", @@ -826,6 +839,10 @@ "type-tenants": "Tenants", "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }", "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'", + "type-tenant-profile": "Tenant profile", + "type-tenant-profiles": "Tenant profiles", + "list-of-tenant-profiles": "{ count, plural, 1 {One tenant profile} other {List of # tenant profiles} }", + "tenant-profile-name-starts-with": "Tenant profiles whose names start with '{{prefix}}'", "type-customer": "Customer", "type-customers": "Customers", "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }", @@ -1659,6 +1676,15 @@ "isolated-tb-core-details": "Requires separate microservice(s) per isolated Tenant", "isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant" }, + "tenant-profile": { + "tenant-profile": "Tenant profile", + "tenant-profiles": "Tenant profiles", + "add": "Add tenant profile", + "tenant-profile-details": "Tenant profile details", + "no-tenant-profiles-text": "No tenant profiles found", + "search": "Search tenant profiles", + "selected-tenant-profiles": "{ count, plural, 1 {1 tenant profile} other {# tenant profiles} } selected" + }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", "minutes-interval": "{ minutes, plural, 1 {1 minute} other {# minutes} }", @@ -2059,4 +2085,4 @@ "ka_GE": "ქართული" } } -} +},