Commit 7724495cb12530a45e64761c5cb66b908686237f

Authored by YevhenBondarenko
Committed by Andrew Shvayka
1 parent 461da688

added default queue for device profile

@@ -96,6 +96,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( @@ -96,6 +96,7 @@ CREATE TABLE IF NOT EXISTS device_profile (
96 is_default boolean, 96 is_default boolean,
97 tenant_id uuid, 97 tenant_id uuid,
98 default_rule_chain_id uuid, 98 default_rule_chain_id uuid,
  99 + default_queue_name varchar(255),
99 provision_device_key varchar, 100 provision_device_key varchar,
100 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), 101 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
101 CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), 102 CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
@@ -159,6 +159,10 @@ public class DefaultTbClusterService implements TbClusterService { @@ -159,6 +159,10 @@ public class DefaultTbClusterService implements TbClusterService {
159 if (targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId())) { 159 if (targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId())) {
160 tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId); 160 tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId);
161 } 161 }
  162 + String targetQueueName = deviceProfile.getDefaultQueueName();
  163 + if (targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName())) {
  164 + tbMsg = TbMsg.transformMsg(tbMsg, targetQueueName);
  165 + }
162 } 166 }
163 return tbMsg; 167 return tbMsg;
164 } 168 }
@@ -43,6 +43,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H @@ -43,6 +43,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
43 private DeviceTransportType transportType; 43 private DeviceTransportType transportType;
44 private DeviceProfileProvisionType provisionType; 44 private DeviceProfileProvisionType provisionType;
45 private RuleChainId defaultRuleChainId; 45 private RuleChainId defaultRuleChainId;
  46 + private String defaultQueueName;
46 private transient DeviceProfileData profileData; 47 private transient DeviceProfileData profileData;
47 @JsonIgnore 48 @JsonIgnore
48 private byte[] profileDataBytes; 49 private byte[] profileDataBytes;
@@ -63,6 +64,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H @@ -63,6 +64,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
63 this.description = deviceProfile.getDescription(); 64 this.description = deviceProfile.getDescription();
64 this.isDefault = deviceProfile.isDefault(); 65 this.isDefault = deviceProfile.isDefault();
65 this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId(); 66 this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId();
  67 + this.defaultQueueName = deviceProfile.getDefaultQueueName();
66 this.setProfileData(deviceProfile.getProfileData()); 68 this.setProfileData(deviceProfile.getProfileData());
67 this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); 69 this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey();
68 } 70 }
@@ -90,6 +90,11 @@ public final class TbMsg implements Serializable { @@ -90,6 +90,11 @@ public final class TbMsg implements Serializable {
90 origMsg.data, ruleChainId, null, origMsg.getCallback()); 90 origMsg.data, ruleChainId, null, origMsg.getCallback());
91 } 91 }
92 92
  93 + public static TbMsg transformMsg(TbMsg origMsg, String queueName) {
  94 + return new TbMsg(queueName, origMsg.id, origMsg.ts, origMsg.type, origMsg.originator, origMsg.metaData, origMsg.dataType,
  95 + origMsg.data, origMsg.getRuleChainId(), null, origMsg.getCallback());
  96 + }
  97 +
93 public static TbMsg newMsg(TbMsg tbMsg, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { 98 public static TbMsg newMsg(TbMsg tbMsg, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
94 return new TbMsg(tbMsg.getQueueName(), UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(), 99 return new TbMsg(tbMsg.getQueueName(), UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(),
95 tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, TbMsgCallback.EMPTY); 100 tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, TbMsgCallback.EMPTY);
@@ -174,6 +174,7 @@ public class ModelConstants { @@ -174,6 +174,7 @@ public class ModelConstants {
174 public static final String DEVICE_PROFILE_DESCRIPTION_PROPERTY = "description"; 174 public static final String DEVICE_PROFILE_DESCRIPTION_PROPERTY = "description";
175 public static final String DEVICE_PROFILE_IS_DEFAULT_PROPERTY = "is_default"; 175 public static final String DEVICE_PROFILE_IS_DEFAULT_PROPERTY = "is_default";
176 public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id"; 176 public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id";
  177 + public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name";
177 public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key"; 178 public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key";
178 179
179 /** 180 /**
@@ -79,6 +79,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl @@ -79,6 +79,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
79 @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY, columnDefinition = "uuid") 79 @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY, columnDefinition = "uuid")
80 private UUID defaultRuleChainId; 80 private UUID defaultRuleChainId;
81 81
  82 + @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY)
  83 + private String defaultQueueName;
  84 +
82 @Type(type = "jsonb") 85 @Type(type = "jsonb")
83 @Column(name = ModelConstants.DEVICE_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb") 86 @Column(name = ModelConstants.DEVICE_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb")
84 private JsonNode profileData; 87 private JsonNode profileData;
@@ -108,6 +111,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl @@ -108,6 +111,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
108 if (deviceProfile.getDefaultRuleChainId() != null) { 111 if (deviceProfile.getDefaultRuleChainId() != null) {
109 this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId().getId(); 112 this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId().getId();
110 } 113 }
  114 + this.defaultQueueName = deviceProfile.getDefaultQueueName();
111 this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); 115 this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey();
112 } 116 }
113 117
@@ -142,6 +146,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl @@ -142,6 +146,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
142 if (defaultRuleChainId != null) { 146 if (defaultRuleChainId != null) {
143 deviceProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId)); 147 deviceProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId));
144 } 148 }
  149 + deviceProfile.setDefaultQueueName(defaultQueueName);
145 deviceProfile.setProvisionDeviceKey(provisionDeviceKey); 150 deviceProfile.setProvisionDeviceKey(provisionDeviceKey);
146 return deviceProfile; 151 return deviceProfile;
147 } 152 }
@@ -170,6 +170,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( @@ -170,6 +170,7 @@ CREATE TABLE IF NOT EXISTS device_profile (
170 is_default boolean, 170 is_default boolean,
171 tenant_id uuid, 171 tenant_id uuid,
172 default_rule_chain_id uuid, 172 default_rule_chain_id uuid,
  173 + default_queue_name varchar(255),
173 provision_device_key varchar, 174 provision_device_key varchar,
174 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), 175 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
175 CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), 176 CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
@@ -188,6 +188,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( @@ -188,6 +188,7 @@ CREATE TABLE IF NOT EXISTS device_profile (
188 is_default boolean, 188 is_default boolean,
189 tenant_id uuid, 189 tenant_id uuid,
190 default_rule_chain_id uuid, 190 default_rule_chain_id uuid,
  191 + default_queue_name varchar(255),
191 provision_device_key varchar, 192 provision_device_key varchar,
192 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), 193 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
193 CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), 194 CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
@@ -45,6 +45,11 @@ @@ -45,6 +45,11 @@
45 labelText="device-profile.default-rule-chain" 45 labelText="device-profile.default-rule-chain"
46 formControlName="defaultRuleChainId"> 46 formControlName="defaultRuleChainId">
47 </tb-rule-chain-autocomplete> 47 </tb-rule-chain-autocomplete>
  48 + <tb-queue-type-list
  49 + [queueType]="serviceType"
  50 + formControlName="defaultQueueName">
  51 + </tb-queue-type-list>
  52 + <div class="tb-hint" translate>device-profile.select-queue-hint</div>
48 <mat-form-field fxHide class="mat-block"> 53 <mat-form-field fxHide class="mat-block">
49 <mat-label translate>device-profile.type</mat-label> 54 <mat-label translate>device-profile.type</mat-label>
50 <mat-select formControlName="type" required> 55 <mat-select formControlName="type" required>
@@ -48,6 +48,7 @@ import { MatHorizontalStepper } from '@angular/material/stepper'; @@ -48,6 +48,7 @@ import { MatHorizontalStepper } from '@angular/material/stepper';
48 import { RuleChainId } from '@shared/models/id/rule-chain-id'; 48 import { RuleChainId } from '@shared/models/id/rule-chain-id';
49 import { StepperSelectionEvent } from '@angular/cdk/stepper'; 49 import { StepperSelectionEvent } from '@angular/cdk/stepper';
50 import { deepTrim } from '@core/utils'; 50 import { deepTrim } from '@core/utils';
  51 +import {ServiceType} from "@shared/models/queue.models";
51 52
52 export interface AddDeviceProfileDialogData { 53 export interface AddDeviceProfileDialogData {
53 deviceProfileName: string; 54 deviceProfileName: string;
@@ -89,6 +90,8 @@ export class AddDeviceProfileDialogComponent extends @@ -89,6 +90,8 @@ export class AddDeviceProfileDialogComponent extends
89 90
90 provisionConfigFormGroup: FormGroup; 91 provisionConfigFormGroup: FormGroup;
91 92
  93 + serviceType = ServiceType.TB_RULE_ENGINE;
  94 +
92 constructor(protected store: Store<AppState>, 95 constructor(protected store: Store<AppState>,
93 protected router: Router, 96 protected router: Router,
94 @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData, 97 @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData,
@@ -104,6 +107,7 @@ export class AddDeviceProfileDialogComponent extends @@ -104,6 +107,7 @@ export class AddDeviceProfileDialogComponent extends
104 name: [data.deviceProfileName, [Validators.required]], 107 name: [data.deviceProfileName, [Validators.required]],
105 type: [DeviceProfileType.DEFAULT, [Validators.required]], 108 type: [DeviceProfileType.DEFAULT, [Validators.required]],
106 defaultRuleChainId: [null, []], 109 defaultRuleChainId: [null, []],
  110 + defaultQueueName: ['', []],
107 description: ['', []] 111 description: ['', []]
108 } 112 }
109 ); 113 );
@@ -53,6 +53,11 @@ @@ -53,6 +53,11 @@
53 labelText="device-profile.default-rule-chain" 53 labelText="device-profile.default-rule-chain"
54 formControlName="defaultRuleChainId"> 54 formControlName="defaultRuleChainId">
55 </tb-rule-chain-autocomplete> 55 </tb-rule-chain-autocomplete>
  56 + <tb-queue-type-list
  57 + [queueType]="serviceType"
  58 + formControlName="defaultQueueName">
  59 + </tb-queue-type-list>
  60 + <div class="tb-hint" translate>device-profile.select-queue-hint</div>
56 <mat-form-field fxHide class="mat-block"> 61 <mat-form-field fxHide class="mat-block">
57 <mat-label translate>device-profile.type</mat-label> 62 <mat-label translate>device-profile.type</mat-label>
58 <mat-select formControlName="type" required> 63 <mat-select formControlName="type" required>
@@ -38,6 +38,7 @@ import { @@ -38,6 +38,7 @@ import {
38 } from '@shared/models/device.models'; 38 } from '@shared/models/device.models';
39 import { EntityType } from '@shared/models/entity-type.models'; 39 import { EntityType } from '@shared/models/entity-type.models';
40 import { RuleChainId } from '@shared/models/id/rule-chain-id'; 40 import { RuleChainId } from '@shared/models/id/rule-chain-id';
  41 +import {ServiceType} from "@shared/models/queue.models";
41 42
42 @Component({ 43 @Component({
43 selector: 'tb-device-profile', 44 selector: 'tb-device-profile',
@@ -63,6 +64,8 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { @@ -63,6 +64,8 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
63 64
64 displayTransportConfiguration: boolean; 65 displayTransportConfiguration: boolean;
65 66
  67 + serviceType = ServiceType.TB_RULE_ENGINE;
  68 +
66 constructor(protected store: Store<AppState>, 69 constructor(protected store: Store<AppState>,
67 protected translate: TranslateService, 70 protected translate: TranslateService,
68 @Optional() @Inject('entity') protected entityValue: DeviceProfile, 71 @Optional() @Inject('entity') protected entityValue: DeviceProfile,
@@ -101,6 +104,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { @@ -101,6 +104,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
101 provisionConfiguration: [deviceProvisionConfiguration, Validators.required] 104 provisionConfiguration: [deviceProvisionConfiguration, Validators.required]
102 }), 105 }),
103 defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []], 106 defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []],
  107 + defaultQueueName: [entity ? entity.defaultQueueName : '', []],
104 description: [entity ? entity.description : '', []], 108 description: [entity ? entity.description : '', []],
105 } 109 }
106 ); 110 );
@@ -174,6 +178,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { @@ -174,6 +178,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
174 provisionConfiguration: deviceProvisionConfiguration 178 provisionConfiguration: deviceProvisionConfiguration
175 }}); 179 }});
176 this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}); 180 this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null});
  181 + this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName});
177 this.entityForm.patchValue({description: entity.description}); 182 this.entityForm.patchValue({description: entity.description});
178 } 183 }
179 184
@@ -97,6 +97,13 @@ @@ -97,6 +97,13 @@
97 formControlName="defaultRuleChainId"> 97 formControlName="defaultRuleChainId">
98 </tb-rule-chain-autocomplete> 98 </tb-rule-chain-autocomplete>
99 </div> 99 </div>
  100 + <div fxLayout="column" fxLayoutAlign="flex-end start">
  101 + <tb-queue-type-list
  102 + [queueType]="serviceType"
  103 + formControlName="defaultQueueName">
  104 + </tb-queue-type-list>
  105 + <div class="tb-hint" translate>device-profile.select-queue-hint</div>
  106 + </div>
100 </div> 107 </div>
101 <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;"> 108 <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;">
102 {{ 'device.is-gateway' | translate }} 109 {{ 'device.is-gateway' | translate }}
@@ -43,6 +43,7 @@ import { StepperSelectionEvent } from '@angular/cdk/stepper'; @@ -43,6 +43,7 @@ import { StepperSelectionEvent } from '@angular/cdk/stepper';
43 import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; 43 import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
44 import { MediaBreakpoints } from '@shared/models/constants'; 44 import { MediaBreakpoints } from '@shared/models/constants';
45 import { RuleChainId } from '@shared/models/id/rule-chain-id'; 45 import { RuleChainId } from '@shared/models/id/rule-chain-id';
  46 +import {ServiceType} from "@shared/models/queue.models";
46 47
47 @Component({ 48 @Component({
48 selector: 'tb-device-wizard', 49 selector: 'tb-device-wizard',
@@ -84,6 +85,8 @@ export class DeviceWizardDialogComponent extends @@ -84,6 +85,8 @@ export class DeviceWizardDialogComponent extends
84 85
85 labelPosition = 'end'; 86 labelPosition = 'end';
86 87
  88 + serviceType = ServiceType.TB_RULE_ENGINE;
  89 +
87 private subscriptions: Subscription[] = []; 90 private subscriptions: Subscription[] = [];
88 91
89 constructor(protected store: Store<AppState>, 92 constructor(protected store: Store<AppState>,
@@ -105,6 +108,7 @@ export class DeviceWizardDialogComponent extends @@ -105,6 +108,7 @@ export class DeviceWizardDialogComponent extends
105 deviceProfileId: [null, Validators.required], 108 deviceProfileId: [null, Validators.required],
106 newDeviceProfileTitle: [{value: null, disabled: true}], 109 newDeviceProfileTitle: [{value: null, disabled: true}],
107 defaultRuleChainId: [{value: null, disabled: true}], 110 defaultRuleChainId: [{value: null, disabled: true}],
  111 + defaultQueueName: [''],
108 description: [''] 112 description: ['']
109 } 113 }
110 ); 114 );
@@ -117,6 +121,7 @@ export class DeviceWizardDialogComponent extends @@ -117,6 +121,7 @@ export class DeviceWizardDialogComponent extends
117 this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null); 121 this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null);
118 this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable(); 122 this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable();
119 this.deviceWizardFormGroup.get('defaultRuleChainId').disable(); 123 this.deviceWizardFormGroup.get('defaultRuleChainId').disable();
  124 + this.deviceWizardFormGroup.get('defaultQueueName').disable();
120 this.deviceWizardFormGroup.updateValueAndValidity(); 125 this.deviceWizardFormGroup.updateValueAndValidity();
121 this.createProfile = false; 126 this.createProfile = false;
122 this.createTransportConfiguration = false; 127 this.createTransportConfiguration = false;
@@ -126,6 +131,8 @@ export class DeviceWizardDialogComponent extends @@ -126,6 +131,8 @@ export class DeviceWizardDialogComponent extends
126 this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); 131 this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]);
127 this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable(); 132 this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable();
128 this.deviceWizardFormGroup.get('defaultRuleChainId').enable(); 133 this.deviceWizardFormGroup.get('defaultRuleChainId').enable();
  134 + this.deviceWizardFormGroup.get('defaultQueueName').enable();
  135 +
129 this.deviceWizardFormGroup.updateValueAndValidity(); 136 this.deviceWizardFormGroup.updateValueAndValidity();
130 this.createProfile = true; 137 this.createProfile = true;
131 this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && 138 this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value &&
@@ -369,6 +369,7 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> { @@ -369,6 +369,7 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> {
369 provisionType: DeviceProvisionType; 369 provisionType: DeviceProvisionType;
370 provisionDeviceKey?: string; 370 provisionDeviceKey?: string;
371 defaultRuleChainId?: RuleChainId; 371 defaultRuleChainId?: RuleChainId;
  372 + defaultQueueName?: string;
372 profileData: DeviceProfileData; 373 profileData: DeviceProfileData;
373 } 374 }
374 375
@@ -872,6 +872,7 @@ @@ -872,6 +872,7 @@
872 "profile-configuration": "Profile configuration", 872 "profile-configuration": "Profile configuration",
873 "transport-configuration": "Transport configuration", 873 "transport-configuration": "Transport configuration",
874 "default-rule-chain": "Default rule chain", 874 "default-rule-chain": "Default rule chain",
  875 + "select-queue-hint": "The queue name can be selected from a drop-down list or add a custom name.",
875 "delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?", 876 "delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?",
876 "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.", 877 "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.",
877 "delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?", 878 "delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?",