Browse Source

Moved Asset and Device profile to new style

Moved Asset, Device and Entity view to new style

Moved rest forms to new style
pull/15265/head
Maksym Tsymbarov 6 months ago
parent
commit
e3e3649d49
  1. 26
      ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.html
  2. 128
      ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html
  3. 3
      ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.scss
  4. 141
      ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.html
  5. 104
      ui-ngx/src/app/modules/home/components/device/device-credentials.component.html
  6. 109
      ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html
  7. 1
      ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss
  8. 2
      ui-ngx/src/app/modules/home/components/profile/asset-profile-autocomplete.component.html
  9. 5
      ui-ngx/src/app/modules/home/components/profile/asset-profile-autocomplete.component.ts
  10. 90
      ui-ngx/src/app/modules/home/components/profile/asset-profile.component.html
  11. 126
      ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html
  12. 4
      ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts
  13. 248
      ui-ngx/src/app/modules/home/components/profile/device-profile.component.html
  14. 138
      ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html
  15. 67
      ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.html
  16. 4
      ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.html
  17. 4
      ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.ts
  18. 38
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.html
  19. 9
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.ts
  20. 234
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html
  21. 82
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html
  22. 2
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html
  23. 4
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts
  24. 105
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-instances.component.html
  25. 71
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.html
  26. 54
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html
  27. 288
      ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html
  28. 4
      ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts
  29. 109
      ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.html
  30. 6
      ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.ts
  31. 32
      ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-transport-configuration.component.html
  32. 94
      ui-ngx/src/app/modules/home/components/resources/resources-library.component.html
  33. 2
      ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.html
  34. 4
      ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts
  35. 84
      ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html
  36. 65
      ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html
  37. 1
      ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss
  38. 125
      ui-ngx/src/app/modules/home/pages/admin/resource/js-resource.component.html
  39. 2
      ui-ngx/src/app/modules/home/pages/admin/resource/js-resource.component.ts
  40. 45
      ui-ngx/src/app/modules/home/pages/asset/asset.component.html
  41. 64
      ui-ngx/src/app/modules/home/pages/customer/customer.component.html
  42. 38
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.html
  43. 40
      ui-ngx/src/app/modules/home/pages/device/device.component.html
  44. 90
      ui-ngx/src/app/modules/home/pages/edge/edge.component.html
  45. 43
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html
  46. 150
      ui-ngx/src/app/modules/home/pages/mobile/bundes/layout/mobile-layout.component.html
  47. 109
      ui-ngx/src/app/modules/home/pages/mobile/bundes/layout/mobile-page-item-row.component.html
  48. 39
      ui-ngx/src/app/modules/home/pages/mobile/bundes/mobile-bundle-dialog.component.html
  49. 257
      ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html
  50. 17
      ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.html
  51. 62
      ui-ngx/src/app/modules/home/pages/notification/rule/escalations.component.html
  52. 1167
      ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html
  53. 1
      ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.scss
  54. 278
      ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.html
  55. 1
      ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.scss
  56. 79
      ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-action-button-configuration.component.html
  57. 486
      ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html
  58. 55
      ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.html
  59. 1
      ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.scss
  60. 195
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html
  61. 60
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html
  62. 18
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.html
  63. 21
      ui-ngx/src/app/modules/home/pages/widget/widget-type.component.html
  64. 38
      ui-ngx/src/app/shared/components/contact.component.html
  65. 2
      ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.html
  66. 5
      ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.ts
  67. 2
      ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html
  68. 4
      ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts
  69. 2
      ui-ngx/src/app/shared/components/notification/template-autocomplete.component.html
  70. 5
      ui-ngx/src/app/shared/components/notification/template-autocomplete.component.ts
  71. 2
      ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html
  72. 4
      ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts
  73. 2
      ui-ngx/src/app/shared/components/queue/queue-autocomplete.component.html
  74. 5
      ui-ngx/src/app/shared/components/queue/queue-autocomplete.component.ts
  75. 2
      ui-ngx/src/app/shared/components/slack-conversation-autocomplete.component.html
  76. 4
      ui-ngx/src/app/shared/components/slack-conversation-autocomplete.component.ts

26
ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m-server.component.html

@ -16,16 +16,18 @@
-->
<section [formGroup]="serverFormGroup" class="flex flex-col">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.lwm2m-security-config.mode</mat-label>
<mat-select formControlName="securityMode">
<mat-option *ngFor="let securityType of securityConfigLwM2MTypes" [value]="securityType">
{{ lwm2mSecurityTypeTranslationMap.get(securityType) }}
</mat-option>
@for (securityType of securityConfigLwM2MTypes; track securityType) {
<mat-option [value]="securityType">
{{ lwm2mSecurityTypeTranslationMap.get(securityType) }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div [class.!hidden]="serverFormGroup.get('securityMode').value === securityConfigLwM2MType.NO_SEC">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label>{{ 'device.lwm2m-security-config.client-publicKey-or-id' | translate }}</mat-label>
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ publicKeyOrIdTooltipNamesMap.get(serverFormGroup.get('securityMode').value) | translate }}">help</mat-icon>
@ -36,11 +38,11 @@
formControlName="clientPublicKeyOrId"
required>
</textarea>
<mat-error *ngIf="serverFormGroup.get('clientPublicKeyOrId').hasError('required')">
{{ 'device.lwm2m-security-config.client-publicKey-or-id-required' | translate }}
</mat-error>
@if (serverFormGroup.get('clientPublicKeyOrId').hasError('required')) {
<mat-error>{{ 'device.lwm2m-security-config.client-publicKey-or-id-required' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label>{{ 'device.lwm2m-security-config.client-secret-key' | translate }}</mat-label>
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ clientSecretKeyTooltipNamesMap.get(serverFormGroup.get('securityMode').value) | translate }}">help</mat-icon>
@ -51,9 +53,9 @@
formControlName="clientSecretKey"
required>
</textarea>
<mat-error *ngIf="serverFormGroup.get('clientSecretKey').hasError('required')">
{{ 'device.lwm2m-security-config.client-secret-key-required' | translate }}
</mat-error>
@if (serverFormGroup.get('clientSecretKey').hasError('required')) {
<mat-error>{{ 'device.lwm2m-security-config.client-secret-key-required' | translate }}</mat-error>
}
</mat-form-field>
</div>
</section>

128
ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html

@ -18,66 +18,75 @@
<mat-tab-group [formGroup]="lwm2mConfigFormGroup" dynamicHeight>
<mat-tab label="{{ 'device.lwm2m-security-config.client-tab' | translate }}">
<ng-container formGroupName="client">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.lwm2m-security-config.endpoint</mat-label>
<input matInput type="text" formControlName="endpoint" required>
<mat-error *ngIf="lwm2mConfigFormGroup.get('client.endpoint').hasError('required')">
{{ 'device.lwm2m-security-config.endpoint-required' | translate }}
</mat-error>
@if (lwm2mConfigFormGroup.get('client.endpoint').hasError('required')) {
<mat-error>{{ 'device.lwm2m-security-config.endpoint-required' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.lwm2m-security-config.mode</mat-label>
<mat-select formControlName="securityConfigClientMode">
<mat-option *ngFor="let securityConfigClientMode of securityConfigLwM2MTypes"
[value]="securityConfigClientMode">
{{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }}
</mat-option>
@for (securityConfigClientMode of securityConfigLwM2MTypes; track securityConfigClientMode) {
<mat-option [value]="securityConfigClientMode">
{{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block"
*ngIf="lwm2mConfigFormGroup.get('client.securityConfigClientMode').value === securityConfigLwM2MType.PSK">
<mat-label>{{ 'device.lwm2m-security-config.identity' | translate }}</mat-label>
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ 'device.lwm2m-security-config.identity-tooltip' | translate }}">help</mat-icon>
<input matInput type="text" formControlName="identity" required>
<mat-error *ngIf="lwm2mConfigFormGroup.get('client.identity').hasError('required')">
{{ 'device.lwm2m-security-config.identity-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block" *ngIf="lwm2mConfigFormGroup.get('client.securityConfigClientMode').value === securityConfigLwM2MType.RPK ||
lwm2mConfigFormGroup.get('client.securityConfigClientMode').value === securityConfigLwM2MType.PSK">
<mat-label>{{ 'device.lwm2m-security-config.client-key' | translate }}</mat-label>
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ clientKeyTooltipNamesMap.get(lwm2mConfigFormGroup.get('client.securityConfigClientMode').value) | translate }}">help</mat-icon>
<textarea matInput
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cols="1"
formControlName="key"
required>
</textarea>
<mat-error *ngIf="lwm2mConfigFormGroup.get('client.key').hasError('required')">
{{ 'device.lwm2m-security-config.client-key-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block" *ngIf="lwm2mConfigFormGroup.get('client.securityConfigClientMode').value === securityConfigLwM2MType.X509">
<mat-label translate>device.lwm2m-security-config.client-public-key</mat-label>
<mat-icon *ngIf="lwm2mConfigFormGroup.get('client.cert').value" class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ 'device.lwm2m-security-config.client-public-key-tooltip' | translate }}">help</mat-icon>
<textarea matInput
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cols="1"
formControlName="cert">
</textarea>
<mat-hint translate>device.lwm2m-security-config.client-public-key-hint</mat-hint>
</mat-form-field>
<button *ngIf="deviceId"
mat-raised-button color="primary" type="button"
(click)="rebootDevice(false)"
[disabled]="lwm2mConfigFormGroup.get('client').invalid">
{{ 'device.lwm2m-security-config.client-reboot' | translate }}
</button>
@if (lwm2mConfigFormGroup.get('client.securityConfigClientMode').value === securityConfigLwM2MType.PSK) {
<mat-form-field class="mat-block" appearance="outline">
<mat-label>{{ 'device.lwm2m-security-config.identity' | translate }}</mat-label>
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ 'device.lwm2m-security-config.identity-tooltip' | translate }}">help</mat-icon>
<input matInput type="text" formControlName="identity" required>
@if (lwm2mConfigFormGroup.get('client.identity').hasError('required')) {
<mat-error>{{ 'device.lwm2m-security-config.identity-required' | translate }}</mat-error>
}
</mat-form-field>
}
@if (lwm2mConfigFormGroup.get('client.securityConfigClientMode').value === securityConfigLwM2MType.RPK ||
lwm2mConfigFormGroup.get('client.securityConfigClientMode').value === securityConfigLwM2MType.PSK) {
<mat-form-field class="mat-block" appearance="outline">
<mat-label>{{ 'device.lwm2m-security-config.client-key' | translate }}</mat-label>
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ clientKeyTooltipNamesMap.get(lwm2mConfigFormGroup.get('client.securityConfigClientMode').value) | translate }}">help</mat-icon>
<textarea matInput
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cols="1"
formControlName="key"
required>
</textarea>
@if (lwm2mConfigFormGroup.get('client.key').hasError('required')) {
<mat-error>{{ 'device.lwm2m-security-config.client-key-required' | translate }}</mat-error>
}
</mat-form-field>
}
@if (lwm2mConfigFormGroup.get('client.securityConfigClientMode').value === securityConfigLwM2MType.X509) {
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.lwm2m-security-config.client-public-key</mat-label>
@if (lwm2mConfigFormGroup.get('client.cert').value) {
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ 'device.lwm2m-security-config.client-public-key-tooltip' | translate }}">help</mat-icon>
}
<textarea matInput
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cols="1"
formControlName="cert">
</textarea>
<mat-hint translate>device.lwm2m-security-config.client-public-key-hint</mat-hint>
</mat-form-field>
}
@if (deviceId) {
<button mat-raised-button color="primary" type="button"
(click)="rebootDevice(false)"
[disabled]="lwm2mConfigFormGroup.get('client').invalid">
{{ 'device.lwm2m-security-config.client-reboot' | translate }}
</button>
}
</ng-container>
</mat-tab>
<mat-tab label="{{ 'device.lwm2m-security-config.bootstrap-tab' | translate }}">
@ -109,11 +118,12 @@
</mat-expansion-panel>
</mat-accordion>
</div>
<button *ngIf="deviceId"
mat-raised-button color="primary" style="margin-top: 22px" type="button"
(click)="rebootDevice(true)"
[disabled]="lwm2mConfigFormGroup.get('client').invalid">
{{ 'device.lwm2m-security-config.bootstrap-reboot' | translate }}
</button>
@if (deviceId) {
<button mat-raised-button color="primary" style="margin-top: 22px" type="button"
(click)="rebootDevice(true)"
[disabled]="lwm2mConfigFormGroup.get('client').invalid">
{{ 'device.lwm2m-security-config.bootstrap-reboot' | translate }}
</button>
}
</mat-tab>
</mat-tab-group>

3
ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.scss

@ -27,4 +27,7 @@
.mat-mdc-tab-body {
padding: 16px 0;
}
.mat-mdc-tab-body-content {
padding-top: 4px;
}
}

141
ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.html

@ -16,90 +16,87 @@
-->
<section [formGroup]="deviceCredentialsMqttFormGroup">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.client-id</mat-label>
<input matInput formControlName="clientId">
<button type="button"
matSuffix
mat-icon-button
aria-label="Generate"
matTooltip="{{ 'device.generate-client-id' | translate }}"
matTooltipPosition="above"
(click)="generate('clientId')"
*ngIf="!deviceCredentialsMqttFormGroup.get('clientId').value && !disabled; else copyClientId">
<mat-icon>autorenew</mat-icon>
</button>
<ng-template #copyClientId>
<tb-copy-button
matSuffix
miniButton="false"
*ngIf="deviceCredentialsMqttFormGroup.get('clientId').value"
[copyText]="deviceCredentialsMqttFormGroup.get('clientId').value"
tooltipText="{{ 'device.copy-client-id' | translate }}"
tooltipPosition="above"
icon="content_copy">
</tb-copy-button>
</ng-template>
<mat-error *ngIf="deviceCredentialsMqttFormGroup.get('clientId').hasError('pattern')">
{{ 'device.client-id-pattern' | translate }}
</mat-error>
<ng-container matSuffix>
@if (!deviceCredentialsMqttFormGroup.get('clientId').value && !disabled) {
<button type="button"
mat-icon-button
aria-label="Generate"
matTooltip="{{ 'device.generate-client-id' | translate }}"
matTooltipPosition="above"
(click)="generate('clientId')">
<mat-icon>autorenew</mat-icon>
</button>
} @else if (deviceCredentialsMqttFormGroup.get('clientId').value) {
<tb-copy-button
miniButton="false"
[copyText]="deviceCredentialsMqttFormGroup.get('clientId').value"
tooltipText="{{ 'device.copy-client-id' | translate }}"
tooltipPosition="above"
icon="content_copy">
</tb-copy-button>
}
</ng-container>
@if (deviceCredentialsMqttFormGroup.get('clientId').hasError('pattern')) {
<mat-error>{{ 'device.client-id-pattern' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.user-name</mat-label>
<input matInput formControlName="userName" [required]="!!deviceCredentialsMqttFormGroup.get('password').value">
<button type="button"
matSuffix
mat-icon-button
aria-label="Generate"
matTooltip="{{ 'device.generate-user-name' | translate }}"
matTooltipPosition="above"
(click)="generate('userName')"
*ngIf="!deviceCredentialsMqttFormGroup.get('userName').value && !disabled; else copyUserName">
<mat-icon>autorenew</mat-icon>
</button>
<ng-template #copyUserName>
<tb-copy-button
matSuffix
miniButton="false"
*ngIf="deviceCredentialsMqttFormGroup.get('userName').value"
[copyText]="deviceCredentialsMqttFormGroup.get('userName').value"
tooltipText="{{ 'device.copy-user-name' | translate }}"
tooltipPosition="above"
icon="content_copy">
</tb-copy-button>
</ng-template>
<mat-error *ngIf="deviceCredentialsMqttFormGroup.get('userName').hasError('required')">
{{ 'device.user-name-required' | translate }}
</mat-error>
<ng-container matSuffix>
@if (!deviceCredentialsMqttFormGroup.get('userName').value && !disabled) {
<button type="button"
mat-icon-button
aria-label="Generate"
matTooltip="{{ 'device.generate-user-name' | translate }}"
matTooltipPosition="above"
(click)="generate('userName')">
<mat-icon>autorenew</mat-icon>
</button>
} @else if (deviceCredentialsMqttFormGroup.get('userName').value) {
<tb-copy-button
miniButton="false"
[copyText]="deviceCredentialsMqttFormGroup.get('userName').value"
tooltipText="{{ 'device.copy-user-name' | translate }}"
tooltipPosition="above"
icon="content_copy">
</tb-copy-button>
}
</ng-container>
@if (deviceCredentialsMqttFormGroup.get('userName').hasError('required')) {
<mat-error>{{ 'device.user-name-required' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.password</mat-label>
<input matInput formControlName="password"
autocomplete="new-password"
(ngModelChange)="passwordChanged()"
type="password">
<tb-toggle-password matSuffix></tb-toggle-password>
<button type="button"
matSuffix
mat-icon-button
aria-label="Generate"
matTooltip="{{ 'device.generate-password' | translate }}"
matTooltipPosition="above"
(click)="generate('password')"
*ngIf="!deviceCredentialsMqttFormGroup.get('password').value && !disabled; else copyPassword">
<mat-icon>autorenew</mat-icon>
</button>
<ng-template #copyPassword>
<tb-copy-button
matSuffix
miniButton="false"
*ngIf="deviceCredentialsMqttFormGroup.get('password').value"
[copyText]="deviceCredentialsMqttFormGroup.get('password').value"
tooltipText="{{ 'device.copy-password' | translate }}"
tooltipPosition="above"
icon="content_copy">
</tb-copy-button>
</ng-template>
<ng-container matSuffix>
@if (!deviceCredentialsMqttFormGroup.get('password').value && !disabled) {
<button type="button"
mat-icon-button
aria-label="Generate"
matTooltip="{{ 'device.generate-password' | translate }}"
matTooltipPosition="above"
(click)="generate('password')">
<mat-icon>autorenew</mat-icon>
</button>
} @else if (deviceCredentialsMqttFormGroup.get('password').value) {
<tb-copy-button
miniButton="false"
[copyText]="deviceCredentialsMqttFormGroup.get('password').value"
tooltipText="{{ 'device.copy-password' | translate }}"
tooltipPosition="above"
icon="content_copy">
</tb-copy-button>
}
</ng-container>
</mat-form-field>
<tb-error style="margin-top: -12px; display: block;"
[error]="deviceCredentialsMqttFormGroup.hasError('atLeastOne') ?

104
ui-ngx/src/app/modules/home/components/device/device-credentials.component.html

@ -19,71 +19,73 @@
<section [class.!hidden]="!(credentialsTypes?.length > 1)" style="margin-bottom: 16px">
<div class="tb-type-title" translate>device.credentials-type</div>
<tb-toggle-select formControlName="credentialsType" appearance="fill">
<tb-toggle-option *ngFor="let credentialsType of credentialsTypes" [value]="credentialsType">
{{ credentialTypeNamesMap.get(credentialsType) }}
</tb-toggle-option>
@for (credentialsType of credentialsTypes; track credentialsType) {
<tb-toggle-option [value]="credentialsType">
{{ credentialTypeNamesMap.get(credentialsType) }}
</tb-toggle-option>
}
</tb-toggle-select>
</section>
<div [ngSwitch]="deviceCredentialsFormGroup.get('credentialsType').value">
<ng-template [ngSwitchCase]="deviceCredentialsType.ACCESS_TOKEN">
<mat-form-field class="mat-block">
@switch (deviceCredentialsFormGroup.get('credentialsType').value) {
@case (deviceCredentialsType.ACCESS_TOKEN) {
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.access-token</mat-label>
<input matInput formControlName="credentialsId" required>
<button type="button"
matSuffix
mat-icon-button
aria-label="Generate"
matTooltip="{{ 'device.generate-access-token' | translate }}"
matTooltipPosition="above"
(click)="generate('credentialsId')"
*ngIf="!deviceCredentialsFormGroup.get('credentialsId').value && !disabled; else copyAccessToken">
<mat-icon>autorenew</mat-icon>
</button>
<ng-template #copyAccessToken>
<ng-container matSuffix>
@if (!deviceCredentialsFormGroup.get('credentialsId').value && !disabled) {
<button type="button"
mat-icon-button
aria-label="Generate"
matTooltip="{{ 'device.generate-access-token' | translate }}"
matTooltipPosition="above"
(click)="generate('credentialsId')">
<mat-icon>autorenew</mat-icon>
</button>
} @else if (deviceCredentialsFormGroup.get('credentialsId').value) {
<tb-copy-button
miniButton="false"
[copyText]="deviceCredentialsFormGroup.get('credentialsId').value"
tooltipText="{{ 'device.copy-access-token' | translate }}"
tooltipPosition="above"
icon="content_copy">
</tb-copy-button>
}
</ng-container>
@if (deviceCredentialsFormGroup.get('credentialsId').hasError('required')) {
<mat-error>{{ 'device.access-token-required' | translate }}</mat-error>
}
@if (deviceCredentialsFormGroup.get('credentialsId').hasError('pattern')) {
<mat-error>{{ 'device.access-token-invalid' | translate }}</mat-error>
}
</mat-form-field>
}
@case (deviceCredentialsType.X509_CERTIFICATE) {
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.certificate-pem-format</mat-label>
<textarea matInput formControlName="credentialsValue" cols="15" rows="5" required></textarea>
@if (deviceCredentialsFormGroup.get('credentialsValue').value) {
<tb-copy-button
matSuffix
miniButton="false"
*ngIf="deviceCredentialsFormGroup.get('credentialsId').value"
[copyText]="deviceCredentialsFormGroup.get('credentialsId').value"
tooltipText="{{ 'device.copy-access-token' | translate }}"
[copyText]="deviceCredentialsFormGroup.get('credentialsValue').value"
tooltipText="{{ 'device.copy-certificate' | translate }}"
tooltipPosition="above"
icon="content_copy">
</tb-copy-button>
</ng-template>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('required')">
{{ 'device.access-token-required' | translate }}
</mat-error>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('pattern')">
{{ 'device.access-token-invalid' | translate }}
</mat-error>
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="deviceCredentialsType.X509_CERTIFICATE">
<mat-form-field class="mat-block">
<mat-label translate>device.certificate-pem-format</mat-label>
<textarea matInput formControlName="credentialsValue" cols="15" rows="5" required></textarea>
<tb-copy-button
matSuffix
miniButton="false"
*ngIf="deviceCredentialsFormGroup.get('credentialsValue').value"
[copyText]="deviceCredentialsFormGroup.get('credentialsValue').value"
tooltipText="{{ 'device.copy-certificate' | translate }}"
tooltipPosition="above"
icon="content_copy">
</tb-copy-button>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsValue').hasError('required')">
{{ 'device.certificate-pem-format-required' | translate }}
</mat-error>
}
@if (deviceCredentialsFormGroup.get('credentialsValue').hasError('required')) {
<mat-error>{{ 'device.certificate-pem-format-required' | translate }}</mat-error>
}
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="deviceCredentialsType.MQTT_BASIC">
}
@case (deviceCredentialsType.MQTT_BASIC) {
<tb-device-credentials-mqtt-basic formControlName="credentialsValue">
</tb-device-credentials-mqtt-basic>
</ng-template>
<ng-template [ngSwitchCase]="deviceCredentialsType.LWM2M_CREDENTIALS">
}
@case (deviceCredentialsType.LWM2M_CREDENTIALS) {
<tb-device-credentials-lwm2m formControlName="credentialsValue"
[deviceId]="deviceId">
</tb-device-credentials-lwm2m>
</ng-template>
</div>
}
}
</section>

109
ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html

@ -25,9 +25,11 @@
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="(isLoading$ | async) === false"></div>
@if (isLoading$ | async) {
<mat-progress-bar color="warn" mode="indeterminate"></mat-progress-bar>
} @else {
<div style="height: 4px;"></div>
}
<div mat-dialog-content>
<mat-horizontal-stepper [linear]="true"
#addDeviceProfileStepper
@ -37,54 +39,60 @@
<mat-step [stepControl]="deviceProfileDetailsFormGroup">
<form [formGroup]="deviceProfileDetailsFormGroup">
<ng-template matStepLabel>{{ 'device-profile.device-profile-details' | translate }}</ng-template>
<fieldset [disabled]="isLoading$ | async">
<mat-form-field class="mat-block">
<fieldset [disabled]="isLoading$ | async" class="flex flex-col gap-2">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device-profile.name</mat-label>
<input matInput formControlName="name" required/>
<mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('required')">
{{ 'device-profile.name-required' | translate }}
</mat-error>
<mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('maxlength')">
{{ 'device-profile.name-max-length' | translate }}
</mat-error>
@if (deviceProfileDetailsFormGroup.get('name').hasError('required')) {
<mat-error>{{ 'device-profile.name-required' | translate }}</mat-error>
}
@if (deviceProfileDetailsFormGroup.get('name').hasError('maxlength')) {
<mat-error>{{ 'device-profile.name-max-length' | translate }}</mat-error>
}
</mat-form-field>
<tb-rule-chain-autocomplete
labelText="device-profile.default-rule-chain"
appearance="outline"
formControlName="defaultRuleChainId">
</tb-rule-chain-autocomplete>
<tb-dashboard-autocomplete
label="{{'device-profile.mobile-dashboard' | translate}}"
appearance="outline"
formControlName="defaultDashboardId">
<span tb-hint>{{'device-profile.mobile-dashboard-hint' | translate}}</span>
</tb-dashboard-autocomplete>
<tb-queue-autocomplete
[queueType]="serviceType"
appearance="outline"
formControlName="defaultQueueName">
</tb-queue-autocomplete>
<tb-rule-chain-autocomplete
labelText="device-profile.default-edge-rule-chain"
appearance="outline"
formControlName="defaultEdgeRuleChainId"
[ruleChainType]="edgeRuleChainType">
<span tb-hint>{{'device-profile.default-edge-rule-chain-hint' | translate}}</span>
</tb-rule-chain-autocomplete>
<mat-form-field class="mat-block !hidden">
<mat-form-field class="mat-block !hidden" appearance="outline">
<mat-label translate>device-profile.type</mat-label>
<mat-select formControlName="type" required>
<mat-option *ngFor="let type of deviceProfileTypes" [value]="type">
{{deviceProfileTypeTranslations.get(type) | translate}}
</mat-option>
@for (type of deviceProfileTypes; track type) {
<mat-option [value]="type">
{{ deviceProfileTypeTranslations.get(type) | translate }}
</mat-option>
}
</mat-select>
<mat-error *ngIf="deviceProfileDetailsFormGroup.get('type').hasError('required')">
{{ 'device-profile.type-required' | translate }}
</mat-error>
@if (deviceProfileDetailsFormGroup.get('type').hasError('required')) {
<mat-error>{{ 'device-profile.type-required' | translate }}</mat-error>
}
</mat-form-field>
<tb-gallery-image-input class="flex-1"
label="{{'device-profile.image' | translate}}"
formControlName="image">
</tb-gallery-image-input>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device-profile.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>
</fieldset>
</form>
@ -92,24 +100,30 @@
<mat-step [stepControl]="transportConfigFormGroup" [optional]="true">
<form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;">
<ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template>
<mat-form-field class="mat-block">
<mat-label translate>device-profile.transport-type</mat-label>
<mat-select formControlName="transportType" required>
<mat-option *ngFor="let type of deviceTransportTypes" [value]="type">
{{deviceTransportTypeTranslations.get(type) | translate}}
</mat-option>
</mat-select>
<mat-hint *ngIf="transportConfigFormGroup.get('transportType').value">
{{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}}
</mat-hint>
<mat-error *ngIf="transportConfigFormGroup.get('transportType').hasError('required')">
{{ 'device-profile.transport-type-required' | translate }}
</mat-error>
</mat-form-field>
<tb-device-profile-transport-configuration
formControlName="transportConfiguration"
isAdd="true">
</tb-device-profile-transport-configuration>
<fieldset class="flex flex-col gap-2">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device-profile.transport-type</mat-label>
<mat-select formControlName="transportType" required>
@for (type of deviceTransportTypes; track type) {
<mat-option [value]="type">
{{ deviceTransportTypeTranslations.get(type) | translate }}
</mat-option>
}
</mat-select>
@if (transportConfigFormGroup.get('transportType').value) {
<mat-hint>
{{ deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate }}
</mat-hint>
}
@if (transportConfigFormGroup.get('transportType').hasError('required')) {
<mat-error>{{ 'device-profile.transport-type-required' | translate }}</mat-error>
}
</mat-form-field>
<tb-device-profile-transport-configuration
formControlName="transportConfiguration"
isAdd="true">
</tb-device-profile-transport-configuration>
</fieldset>
</form>
</mat-step>
<mat-step [stepControl]="provisionConfigFormGroup" [optional]="true">
@ -124,15 +138,18 @@
</div>
<div mat-dialog-actions style="padding: 0">
<div class="dialog-actions-row flex flex-1 flex-row items-center justify-end">
<button mat-stroked-button *ngIf="selectedIndex > 0"
[disabled]="(isLoading$ | async)"
(click)="previousStep()">{{ 'action.back' | translate }}</button>
@if (selectedIndex > 0) {
<button mat-stroked-button
[disabled]="(isLoading$ | async)"
(click)="previousStep()">{{ 'action.back' | translate }}</button>
}
<span class="flex-1"></span>
<button mat-stroked-button
color="primary"
*ngIf="showNext"
[disabled]="(isLoading$ | async)"
(click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(selectedIndex+1) | translate)} }}</button>
@if (showNext) {
<button mat-stroked-button
color="primary"
[disabled]="(isLoading$ | async)"
(click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(selectedIndex+1) | translate)} }}</button>
}
</div>
<mat-divider style="width: 100%"></mat-divider>
<div class="dialog-actions-row flex flex-1 flex-row items-center justify-end gap-2">

1
ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss

@ -52,6 +52,7 @@
width: 100%;;
overflow-y: auto;
scrollbar-gutter: stable;
padding-top: 4px;
@media #{$mat-gt-sm} {
min-width: 500px;
}

2
ui-ngx/src/app/modules/home/components/profile/asset-profile-autocomplete.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<mat-form-field [formGroup]="selectAssetProfileFormGroup" class="mat-block" subscriptSizing="{{ subscriptSizing }}">
<mat-form-field [formGroup]="selectAssetProfileFormGroup" class="mat-block" [subscriptSizing]="subscriptSizing" [appearance]="appearance">
<mat-label>{{ 'asset-profile.asset-profile' | translate }}</mat-label>
<input matInput type="text"
#assetProfileInput

5
ui-ngx/src/app/modules/home/components/profile/asset-profile-autocomplete.component.ts

@ -44,7 +44,7 @@ import { AssetProfileId } from '@shared/models/id/asset-profile-id';
import { AssetProfile, AssetProfileInfo } from '@shared/models/asset.models';
import { AssetProfileService } from '@core/http/asset-profile.service';
import { AssetProfileDialogComponent, AssetProfileDialogData } from './asset-profile-dialog.component';
import { SubscriptSizing } from '@angular/material/form-field';
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
import { coerceBoolean } from '@shared/decorators/coercion';
import { AuthUser } from '@shared/models/user.model';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
@ -98,6 +98,9 @@ export class AssetProfileAutocompleteComponent implements ControlValueAccessor,
@Input()
hint: string;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Output()
assetProfileUpdated = new EventEmitter<AssetProfileId>();

90
ui-ngx/src/app/modules/home/components/profile/asset-profile.component.html

@ -15,70 +15,76 @@
limitations under the License.
-->
<div class="tb-details-buttons xs:flex xs:flex-col" *ngIf="!standalone">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[class.!hidden]="isEdit || isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'export')"
[class.!hidden]="isEdit">
{{'asset-profile.export' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'setDefault')"
[class.!hidden]="isEdit || entity?.default">
{{'asset-profile.set-default' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
[class.!hidden]="hideDelete() || isEdit">
{{'asset-profile.delete' | translate }}
</button>
<div class="flex flex-row xs:flex-col">
<button mat-raised-button
ngxClipboard
(cbOnSuccess)="onAssetProfileIdCopied($event)"
[cbContent]="entity?.id?.id"
@if (!standalone) {
<div class="tb-details-buttons xs:flex xs:flex-col">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[class.!hidden]="isEdit || isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'export')"
[class.!hidden]="isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>asset-profile.copyId</span>
{{'asset-profile.export' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'setDefault')"
[class.!hidden]="isEdit || entity?.default">
{{'asset-profile.set-default' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
[class.!hidden]="hideDelete() || isEdit">
{{'asset-profile.delete' | translate }}
</button>
<div class="flex flex-row xs:flex-col">
<button mat-raised-button
ngxClipboard
(cbOnSuccess)="onAssetProfileIdCopied($event)"
[cbContent]="entity?.id?.id"
[class.!hidden]="isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>asset-profile.copyId</span>
</button>
</div>
</div>
</div>
}
<div [class.mat-padding]="!standalone" class="flex flex-col">
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
<mat-form-field class="mat-block">
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;" class="flex flex-col gap-2">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>asset-profile.name</mat-label>
<input matInput formControlName="name" required/>
<mat-error *ngIf="entityForm.get('name').hasError('required')">
{{ 'asset-profile.name-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
{{ 'asset-profile.name-max-length' | translate }}
</mat-error>
@if (entityForm.get('name').hasError('required')) {
<mat-error>{{ 'asset-profile.name-required' | translate }}</mat-error>
}
@if (entityForm.get('name').hasError('maxlength')) {
<mat-error>{{ 'asset-profile.name-max-length' | translate }}</mat-error>
}
</mat-form-field>
<tb-rule-chain-autocomplete
labelText="asset-profile.default-rule-chain"
appearance="outline"
formControlName="defaultRuleChainId">
</tb-rule-chain-autocomplete>
<tb-dashboard-autocomplete
label="{{'asset-profile.mobile-dashboard' | translate}}"
appearance="outline"
formControlName="defaultDashboardId">
<span tb-hint>{{'asset-profile.mobile-dashboard-hint' | translate}}</span>
</tb-dashboard-autocomplete>
<tb-queue-autocomplete
[queueType]="serviceType"
appearance="outline"
formControlName="defaultQueueName">
</tb-queue-autocomplete>
<tb-rule-chain-autocomplete
labelText="asset-profile.default-edge-rule-chain"
appearance="outline"
formControlName="defaultEdgeRuleChainId"
[ruleChainType]="edgeRuleChainType">
<span tb-hint>{{'asset-profile.default-edge-rule-chain-hint' | translate}}</span>
@ -87,7 +93,7 @@
label="{{'asset-profile.image' | translate}}"
formControlName="image">
</tb-gallery-image-input>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>asset-profile.description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>

126
ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html

@ -16,25 +16,27 @@
-->
<div [formGroup]="provisionConfigurationFormGroup">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device-profile.provision-strategy</mat-label>
<mat-select formControlName="type" required>
<mat-option *ngFor="let type of deviceProvisionTypes" [value]="type">
{{deviceProvisionTypeTranslateMap.get(type) | translate}}
</mat-option>
@for (type of deviceProvisionTypes; track type) {
<mat-option [value]="type">
{{ getDeviceProvisionType(type) | translate }}
</mat-option>
}
</mat-select>
<mat-error *ngIf="provisionConfigurationFormGroup.get('type').hasError('required')">
{{ 'device-profile.provision-strategy-required' | translate }}
</mat-error>
@if (provisionConfigurationFormGroup.get('type').hasError('required')) {
<mat-error>{{ 'device-profile.provision-strategy-required' | translate }}</mat-error>
}
</mat-form-field>
<div [ngSwitch]="provisionConfigurationFormGroup.get('type').value">
<ng-template [ngSwitchCase]="deviceProvisionType.ALLOW_CREATE_NEW_DEVICES">
@switch (provisionConfigurationFormGroup.get('type').value) {
@case (deviceProvisionType.ALLOW_CREATE_NEW_DEVICES) {
<ng-container *ngTemplateOutlet="default"></ng-container>
</ng-template>
<ng-template [ngSwitchCase]="deviceProvisionType.CHECK_PRE_PROVISIONED_DEVICES">
}
@case (deviceProvisionType.CHECK_PRE_PROVISIONED_DEVICES) {
<ng-container *ngTemplateOutlet="default"></ng-container>
</ng-template>
<ng-template [ngSwitchCase]="deviceProvisionType.X509_CERTIFICATE_CHAIN">
}
@case (deviceProvisionType.X509_CERTIFICATE_CHAIN) {
<div class="tb-hint flex flex-1 items-center justify-start">
<span [innerHTML]="'device-profile.provision-strategy-x509.certificate-chain-hint' | translate"></span>
<span tb-help-popup="device-profile/x509-chain-hint"
@ -47,60 +49,60 @@
{{ 'device-profile.provision-strategy-x509.allow-create-new-devices' | translate }}
</mat-slide-toggle>
<div class="tb-hint" style="padding:0 40px 16px" [innerHTML]="'device-profile.provision-strategy-x509.allow-create-new-devices-hint' | translate"></div>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device-profile.provision-strategy-x509.certificate-value</mat-label>
<textarea matInput formControlName="certificateValue" cols="15" rows="5" required></textarea>
<mat-error *ngIf="provisionConfigurationFormGroup.get('certificateValue').hasError('required')">
{{ 'device-profile.provision-strategy-x509.certificate-value-required' | translate }}
</mat-error>
<textarea matInput formControlName="certificateValue" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1" required></textarea>
@if (provisionConfigurationFormGroup.get('certificateValue').hasError('required')) {
<mat-error>{{ 'device-profile.provision-strategy-x509.certificate-value-required' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device-profile.provision-strategy-x509.cn-regex-variable</mat-label>
<input matInput type="text" formControlName="certificateRegExPattern" required>
<span matSuffix
tb-help-popup="device-profile/x509-chain-regex-examples"
tb-help-popup-placement="top"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '820px'}"></span>
<mat-error *ngIf="provisionConfigurationFormGroup.get('certificateRegExPattern').hasError('required')">
{{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }}
</mat-error>
tb-help-popup="device-profile/x509-chain-regex-examples"
tb-help-popup-placement="top"
trigger-style="letter-spacing:0.25px"
[tb-help-popup-style]="{maxWidth: '820px'}"></span>
@if (provisionConfigurationFormGroup.get('certificateRegExPattern').hasError('required')) {
<mat-error>{{ 'device-profile.provision-strategy-x509.cn-regex-variable-required' | translate }}</mat-error>
}
<mat-hint translate>device-profile.provision-strategy-x509.cn-regex-variable-hint</mat-hint>
</mat-form-field>
</ng-template>
<ng-template #default>
<section class="flex flex-row xs:flex-col gt-xs:gap-2">
<mat-form-field class="mat-block flex-1">
<mat-label translate>device-profile.provision-device-key</mat-label>
<input matInput formControlName="provisionDeviceKey" required/>
<button matSuffix mat-icon-button
ngxClipboard
[cbContent]="provisionConfigurationFormGroup.get('provisionDeviceKey').value"
(cbOnSuccess)="onProvisionCopied(true)"
matTooltip="{{ 'device-profile.copy-provision-key' | translate }}"
matTooltipPosition="above">
<mat-icon svgIcon="mdi:clipboard-arrow-left" style="font-size: 20px;"></mat-icon>
</button>
<mat-error *ngIf="provisionConfigurationFormGroup.get('provisionDeviceKey').hasError('required')">
{{ 'device-profile.provision-device-key-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block flex-1">
<mat-label translate>device-profile.provision-device-secret</mat-label>
<input matInput formControlName="provisionDeviceSecret" required/>
<button matSuffix mat-icon-button
ngxClipboard
[cbContent]="provisionConfigurationFormGroup.get('provisionDeviceSecret').value"
(cbOnSuccess)="onProvisionCopied(false)"
matTooltip="{{ 'device-profile.copy-provision-secret' | translate }}"
matTooltipPosition="above">
<mat-icon svgIcon="mdi:clipboard-arrow-left" style="font-size: 20px;"></mat-icon>
</button>
<mat-error *ngIf="provisionConfigurationFormGroup.get('provisionDeviceSecret').hasError('required')">
{{ 'device-profile.provision-device-secret-required' | translate }}
</mat-error>
</mat-form-field>
</section>
</ng-template>
</div>
}
}
</div>
<ng-template #default>
<section class="flex flex-row xs:flex-col gt-xs:gap-2">
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>device-profile.provision-device-key</mat-label>
<input matInput formControlName="provisionDeviceKey" required/>
<button matSuffix mat-icon-button
ngxClipboard
[cbContent]="provisionConfigurationFormGroup.get('provisionDeviceKey').value"
(cbOnSuccess)="onProvisionCopied(true)"
matTooltip="{{ 'device-profile.copy-provision-key' | translate }}"
matTooltipPosition="above">
<mat-icon svgIcon="mdi:clipboard-arrow-left" style="font-size: 20px;"></mat-icon>
</button>
@if (provisionConfigurationFormGroup.get('provisionDeviceKey').hasError('required')) {
<mat-error>{{ 'device-profile.provision-device-key-required' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>device-profile.provision-device-secret</mat-label>
<input matInput formControlName="provisionDeviceSecret" required/>
<button matSuffix mat-icon-button
ngxClipboard
[cbContent]="provisionConfigurationFormGroup.get('provisionDeviceSecret').value"
(cbOnSuccess)="onProvisionCopied(false)"
matTooltip="{{ 'device-profile.copy-provision-secret' | translate }}"
matTooltipPosition="above">
<mat-icon svgIcon="mdi:clipboard-arrow-left" style="font-size: 20px;"></mat-icon>
</button>
@if (provisionConfigurationFormGroup.get('provisionDeviceSecret').hasError('required')) {
<mat-error>{{ 'device-profile.provision-device-secret-required' | translate }}</mat-error>
}
</mat-form-field>
</section>
</ng-template>

4
ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts

@ -213,4 +213,8 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu
this.provisionConfigurationFormGroup.get('provisionDeviceKey').reset({value: null, disabled: true}, {emitEvent: false});
}
}
getDeviceProvisionType(type: string): string {
return this.deviceProvisionTypeTranslateMap.get(type as DeviceProvisionType);
}
}

248
ui-ngx/src/app/modules/home/components/profile/device-profile.component.html

@ -15,79 +15,90 @@
limitations under the License.
-->
<div class="tb-details-buttons xs:flex xs:flex-col" *ngIf="!standalone">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[class.!hidden]="isEdit || isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'export')"
[class.!hidden]="isEdit">
{{'device-profile.export' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'setDefault')"
[class.!hidden]="isEdit || entity?.default">
{{'device-profile.set-default' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
[class.!hidden]="hideDelete() || isEdit">
{{'device-profile.delete' | translate }}
</button>
<div class="flex flex-row xs:flex-col">
<button mat-raised-button
ngxClipboard
(cbOnSuccess)="onDeviceProfileIdCopied($event)"
[cbContent]="entity?.id?.id"
@if (!standalone) {
<div class="tb-details-buttons xs:flex xs:flex-col">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[class.!hidden]="isEdit || isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'export')"
[class.!hidden]="isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>device-profile.copyId</span>
{{'device-profile.export' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'setDefault')"
[class.!hidden]="isEdit || entity?.default">
{{'device-profile.set-default' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
[class.!hidden]="hideDelete() || isEdit">
{{'device-profile.delete' | translate }}
</button>
<div class="flex flex-row xs:flex-col">
<button mat-raised-button
ngxClipboard
(cbOnSuccess)="onDeviceProfileIdCopied($event)"
[cbContent]="entity?.id?.id"
[class.!hidden]="isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>device-profile.copyId</span>
</button>
</div>
</div>
</div>
}
<div [class.mat-padding]="!standalone" class="flex flex-col">
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
<mat-form-field class="mat-block">
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;" class="flex flex-col gap-2">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device-profile.name</mat-label>
<input matInput formControlName="name" required/>
<mat-error *ngIf="entityForm.get('name').hasError('required')">
{{ 'device-profile.name-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
{{ 'device-profile.name-max-length' | translate }}
</mat-error>
@if (entityForm.get('name').hasError('required')) {
<mat-error>{{ 'device-profile.name-required' | translate }}</mat-error>
}
@if (entityForm.get('name').hasError('maxlength')) {
<mat-error>{{ 'device-profile.name-max-length' | translate }}</mat-error>
}
</mat-form-field>
<tb-rule-chain-autocomplete
labelText="device-profile.default-rule-chain"
appearance="outline"
formControlName="defaultRuleChainId">
</tb-rule-chain-autocomplete>
<tb-dashboard-autocomplete
label="{{'device-profile.mobile-dashboard' | translate}}"
appearance="outline"
formControlName="defaultDashboardId">
<span tb-hint>{{'device-profile.mobile-dashboard-hint' | translate}}</span>
@if (!entityForm.get('defaultDashboardId').disabled) {
<span tb-hint>{{'device-profile.mobile-dashboard-hint' | translate}}</span>
}
</tb-dashboard-autocomplete>
<tb-queue-autocomplete
[queueType]="serviceType"
appearance="outline"
formControlName="defaultQueueName">
</tb-queue-autocomplete>
<tb-rule-chain-autocomplete
labelText="device-profile.default-edge-rule-chain"
appearance="outline"
formControlName="defaultEdgeRuleChainId"
[ruleChainType]="edgeRuleChainType">
<span tb-hint>{{'device-profile.default-edge-rule-chain-hint' | translate}}</span>
@if (!entityForm.get('defaultEdgeRuleChainId').disabled) {
<span tb-hint>{{'device-profile.default-edge-rule-chain-hint' | translate}}</span>
}
</tb-rule-chain-autocomplete>
<tb-ota-package-autocomplete
[useFullEntityId]="true"
[showDetailsPageLink]="true"
[type]="otaUpdateType.FIRMWARE"
[deviceProfileId]="deviceProfileId?.id"
appearance="outline"
formControlName="firmwareId">
</tb-ota-package-autocomplete>
<tb-ota-package-autocomplete
@ -95,96 +106,107 @@
[showDetailsPageLink]="true"
[type]="otaUpdateType.SOFTWARE"
[deviceProfileId]="deviceProfileId?.id"
appearance="outline"
formControlName="softwareId">
</tb-ota-package-autocomplete>
<mat-form-field class="mat-block !hidden">
<mat-form-field class="mat-block !hidden" appearance="outline">
<mat-label translate>device-profile.type</mat-label>
<mat-select formControlName="type" required>
<mat-option *ngFor="let type of deviceProfileTypes" [value]="type">
{{deviceProfileTypeTranslations.get(type) | translate}}
</mat-option>
@for (type of deviceProfileTypes; track type) {
<mat-option [value]="type">
{{ deviceProfileTypeTranslations.get(type) | translate }}
</mat-option>
}
</mat-select>
<mat-error *ngIf="entityForm.get('type').hasError('required')">
{{ 'device-profile.type-required' | translate }}
</mat-error>
@if (entityForm.get('type').hasError('required')) {
<mat-error>{{ 'device-profile.type-required' | translate }}</mat-error>
}
</mat-form-field>
<tb-gallery-image-input class="flex-1"
label="{{'device-profile.image' | translate}}"
formControlName="image">
</tb-gallery-image-input>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device-profile.description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>
<mat-form-field *ngIf="standalone" class="mat-block">
<mat-label translate>device-profile.transport-type</mat-label>
<mat-select formControlName="transportType" required>
<mat-option *ngFor="let type of deviceTransportTypes" [value]="type">
{{deviceTransportTypeTranslations.get(type) | translate}}
</mat-option>
</mat-select>
<mat-error *ngIf="entityForm.get('transportType').hasError('required')">
{{ 'device-profile.transport-type-required' | translate }}
</mat-error>
</mat-form-field>
<div formGroupName="profileData" *ngIf="standalone" style="padding-bottom: 16px;">
<mat-accordion multi="true">
<mat-expansion-panel *ngIf="displayProfileConfiguration" [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div translate>device-profile.profile-configuration</div>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-device-profile-configuration
formControlName="configuration">
</tb-device-profile-configuration>
</ng-template>
</mat-expansion-panel>
<mat-expansion-panel *ngIf="displayTransportConfiguration" [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div translate>device-profile.transport-configuration</div>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-device-profile-transport-configuration
formControlName="transportConfiguration"
[isAdd] = "isTransportTypeChanged">
</tb-device-profile-transport-configuration>
</ng-template>
</mat-expansion-panel>
@if (entityForm.get('profileData.alarms').value?.length) {
<mat-expansion-panel [expanded]="false">
@if (standalone) {
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device-profile.transport-type</mat-label>
<mat-select formControlName="transportType" required>
@for (type of deviceTransportTypes; track type) {
<mat-option [value]="type">
{{ deviceTransportTypeTranslations.get(type) | translate }}
</mat-option>
}
</mat-select>
@if (entityForm.get('transportType').hasError('required')) {
<mat-error>{{ 'device-profile.transport-type-required' | translate }}</mat-error>
}
</mat-form-field>
<div formGroupName="profileData" style="padding-bottom: 16px;">
<mat-accordion multi="true">
@if (displayProfileConfiguration) {
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div translate>device-profile.profile-configuration</div>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-device-profile-configuration
formControlName="configuration">
</tb-device-profile-configuration>
</ng-template>
</mat-expansion-panel>
}
@if (displayTransportConfiguration) {
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div translate>device-profile.transport-configuration</div>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-device-profile-transport-configuration
formControlName="transportConfiguration"
[isAdd]="isTransportTypeChanged">
</tb-device-profile-transport-configuration>
</ng-template>
</mat-expansion-panel>
}
@if (entityForm.get('profileData.alarms').value?.length) {
<mat-expansion-panel [expanded]="false">
<mat-expansion-panel-header>
<mat-panel-title>
<div>{{'device-profile.alarm-rules-with-count' | translate:
{count: entityForm.get('profileData.alarms').value ?
entityForm.get('profileData.alarms').value.length : 0} }}</div>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-device-profile-alarms
formControlName="alarms"
[deviceProfileId]="deviceProfileId">
</tb-device-profile-alarms>
</ng-template>
</mat-expansion-panel>
}
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div>{{'device-profile.alarm-rules-with-count' | translate:
{count: entityForm.get('profileData.alarms').value ?
entityForm.get('profileData.alarms').value.length : 0} }}</div>
<div translate>device-profile.device-provisioning</div>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-device-profile-alarms
formControlName="alarms"
[deviceProfileId]="deviceProfileId">
</tb-device-profile-alarms>
<tb-device-profile-provision-configuration
formControlName="provisionConfiguration">
</tb-device-profile-provision-configuration>
</ng-template>
</mat-expansion-panel>
}
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div translate>device-profile.device-provisioning</div>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-device-profile-provision-configuration
formControlName="provisionConfiguration">
</tb-device-profile-provision-configuration>
</ng-template>
</mat-expansion-panel>
</mat-accordion>
</div>
</mat-accordion>
</div>
}
</fieldset>
</form>
</div>

138
ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html

@ -20,81 +20,81 @@
<fieldset class="fields-group">
<legend class="group-title" translate>device-profile.coap-device-type</legend>
<div class="flex flex-col gap-2">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-select formControlName="coapDeviceType" required>
<mat-option *ngFor="let type of coapTransportDeviceTypes" [value]="type">
{{ coapTransportDeviceTypeTranslations.get(type) | translate}}
</mat-option>
@for (type of coapTransportDeviceTypes; track type) {
<mat-option [value]="type">
{{ coapTransportDeviceTypeTranslations.get(type) | translate }}
</mat-option>
}
</mat-select>
<mat-error *ngIf="coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType').hasError('required')">
{{ 'device-profile.coap-device-type-required' | translate }}
</mat-error>
@if (coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType').hasError('required')) {
<mat-error>{{ 'device-profile.coap-device-type-required' | translate }}</mat-error>
}
</mat-form-field>
</div>
</fieldset>
<div *ngIf="coapDeviceTypeDefault" formGroupName="transportPayloadTypeConfiguration">
<fieldset class="fields-group">
<legend class="group-title" translate>device-profile.coap-device-payload-type</legend>
<div class="flex flex-col gap-2">
<mat-form-field class="mat-block">
<mat-select formControlName="transportPayloadType" required>
<mat-option *ngFor="let type of transportPayloadTypes" [value]="type">
{{ transportPayloadTypeTranslations.get(type) | translate}}
</mat-option>
</mat-select>
<mat-error *ngIf="coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.transportPayloadTypeConfiguration.transportPayloadType').hasError('required')">
{{ 'device-profile.mqtt-payload-type-required' | translate }}
</mat-error>
</mat-form-field>
<div *ngIf="protoPayloadType" class="flex flex-col">
<ng-container>
<tb-protobuf-content
class="flex-1"
formControlName="deviceTelemetryProtoSchema"
label="{{ 'device-profile.telemetry-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
<mat-error *ngIf="coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.transportPayloadTypeConfiguration.deviceTelemetryProtoSchema').hasError('required')">
{{ 'device-profile.telemetry-proto-schema-required' | translate}}
</mat-error>
</ng-container>
<ng-container>
<tb-protobuf-content
class="flex-1"
formControlName="deviceAttributesProtoSchema"
label="{{ 'device-profile.attributes-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
<mat-error *ngIf="coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.transportPayloadTypeConfiguration.deviceAttributesProtoSchema').hasError('required')">
{{ 'device-profile.attributes-proto-schema-required' | translate}}
</mat-error>
</ng-container>
<ng-container>
<tb-protobuf-content
class="flex-1"
formControlName="deviceRpcRequestProtoSchema"
label="{{ 'device-profile.rpc-request-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
<mat-error *ngIf="coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.transportPayloadTypeConfiguration.deviceRpcRequestProtoSchema').hasError('required')">
{{ 'device-profile.rpc-request-proto-schema-required' | translate}}
</mat-error>
</ng-container>
<ng-container>
<tb-protobuf-content
class="flex-1"
formControlName="deviceRpcResponseProtoSchema"
label="{{ 'device-profile.rpc-response-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
<mat-error *ngIf="coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.transportPayloadTypeConfiguration.deviceRpcResponseProtoSchema').hasError('required')">
{{ 'device-profile.rpc-response-proto-schema-required' | translate}}
</mat-error>
</ng-container>
@if (coapDeviceTypeDefault) {
<div formGroupName="transportPayloadTypeConfiguration">
<fieldset class="fields-group">
<legend class="group-title" translate>device-profile.coap-device-payload-type</legend>
<div class="flex flex-col gap-2">
<mat-form-field class="mat-block" appearance="outline">
<mat-select formControlName="transportPayloadType" required>
@for (type of transportPayloadTypes; track type) {
<mat-option [value]="type">
{{ transportPayloadTypeTranslations.get(type) | translate }}
</mat-option>
}
</mat-select>
@if (coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.transportPayloadTypeConfiguration.transportPayloadType').hasError('required')) {
<mat-error>{{ 'device-profile.mqtt-payload-type-required' | translate }}</mat-error>
}
</mat-form-field>
@if (protoPayloadType) {
<div class="flex flex-col">
<tb-protobuf-content
class="flex-1"
formControlName="deviceTelemetryProtoSchema"
label="{{ 'device-profile.telemetry-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
@if (coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.transportPayloadTypeConfiguration.deviceTelemetryProtoSchema').hasError('required')) {
<mat-error>{{ 'device-profile.telemetry-proto-schema-required' | translate }}</mat-error>
}
<tb-protobuf-content
class="flex-1"
formControlName="deviceAttributesProtoSchema"
label="{{ 'device-profile.attributes-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
@if (coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.transportPayloadTypeConfiguration.deviceAttributesProtoSchema').hasError('required')) {
<mat-error>{{ 'device-profile.attributes-proto-schema-required' | translate }}</mat-error>
}
<tb-protobuf-content
class="flex-1"
formControlName="deviceRpcRequestProtoSchema"
label="{{ 'device-profile.rpc-request-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
@if (coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.transportPayloadTypeConfiguration.deviceRpcRequestProtoSchema').hasError('required')) {
<mat-error>{{ 'device-profile.rpc-request-proto-schema-required' | translate }}</mat-error>
}
<tb-protobuf-content
class="flex-1"
formControlName="deviceRpcResponseProtoSchema"
label="{{ 'device-profile.rpc-response-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
@if (coapTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.transportPayloadTypeConfiguration.deviceRpcResponseProtoSchema').hasError('required')) {
<mat-error>{{ 'device-profile.rpc-response-proto-schema-required' | translate }}</mat-error>
}
</div>
}
</div>
</div>
</fieldset>
</div>
</fieldset>
</div>
}
</section>
<section formGroupName="clientSettings">
<fieldset class="fields-group">

67
ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.html

@ -16,37 +16,48 @@
-->
<section [formGroup]="parentForm">
<mat-form-field class="mat-block flex-1">
<mat-label *ngIf="isDeviceSetting">{{ "device-profile.power-saving-mode" | translate }}</mat-label>
<mat-form-field class="mat-block flex-1" appearance="outline">
@if (isDeviceSetting) {
<mat-label>{{ "device-profile.power-saving-mode" | translate }}</mat-label>
}
<mat-select formControlName="powerMode">
<mat-option [value]="null" *ngIf="isDeviceSetting">{{ "device-profile.power-saving-mode-type.default" | translate }}</mat-option>
<mat-option *ngFor="let powerMod of powerMods" [value]="powerMod">
{{ powerModeTranslationMap.get(powerMod) | translate}}
</mat-option>
@if (isDeviceSetting) {
<mat-option [value]="null">{{ "device-profile.power-saving-mode-type.default" | translate }}</mat-option>
}
@for (powerMod of powerMods; track powerMod) {
<mat-option [value]="powerMod">
{{ powerModeTranslationMap.get(powerMod) | translate}}
</mat-option>
}
</mat-select>
</mat-form-field>
<section class="mat-block flex-1" *ngIf="parentForm.get('powerMode').value === 'E_DRX'">
<tb-time-unit-select [labelText]="'device-profile.edrx-cycle'"
[requiredText]="'device-profile.edrx-cycle-required'"
[patternText]="'device-profile.edrx-cycle-pattern'"
[minTime]="5120"
[minText]="'device-profile.edrx-cycle-min'"
formControlName="edrxCycle">
</tb-time-unit-select>
<tb-time-unit-select [labelText]="'device-profile.paging-transmission-window'"
[requiredText]="'device-profile.paging-transmission-window-required'"
[patternText]="'device-profile.paging-transmission-window-pattern'"
@if (parentForm.get('powerMode').value === 'E_DRX') {
<section class="mat-block flex-1">
<tb-time-unit-select [labelText]="'device-profile.edrx-cycle'"
[requiredText]="'device-profile.edrx-cycle-required'"
[patternText]="'device-profile.edrx-cycle-pattern'"
[minTime]="5120"
[minText]="'device-profile.edrx-cycle-min'"
appearance="outline"
formControlName="edrxCycle">
</tb-time-unit-select>
<tb-time-unit-select [labelText]="'device-profile.paging-transmission-window'"
[requiredText]="'device-profile.paging-transmission-window-required'"
[patternText]="'device-profile.paging-transmission-window-pattern'"
[minTime]="1280"
[minText]="'device-profile.paging-transmission-window-min'"
appearance="outline"
formControlName="pagingTransmissionWindow">
</tb-time-unit-select>
</section>
} @else if (parentForm.get('powerMode').value === 'PSM') {
<tb-time-unit-select [labelText]="'device-profile.psm-activity-timer'"
[requiredText]="'device-profile.psm-activity-timer-required'"
[patternText]="'device-profile.psm-activity-timer-pattern'"
[minTime]="1280"
[minText]="'device-profile.paging-transmission-window-min'"
formControlName="pagingTransmissionWindow">
[minText]="'device-profile.psm-activity-timer-min'"
appearance="outline"
formControlName="psmActivityTimer">
</tb-time-unit-select>
</section>
<tb-time-unit-select *ngIf="parentForm.get('powerMode').value === 'PSM'"
[labelText]="'device-profile.psm-activity-timer'"
[requiredText]="'device-profile.psm-activity-timer-required'"
[patternText]="'device-profile.psm-activity-timer-pattern'"
[minTime]="1280"
[minText]="'device-profile.psm-activity-timer-min'"
formControlName="psmActivityTimer">
</tb-time-unit-select>
}
</section>

4
ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.html

@ -16,7 +16,7 @@
-->
<section [formGroup]="timeUnitSelectFormGroup" class="flex flex-row xs:flex-col gt-xs:gap-2">
<mat-form-field class="mat-block flex-1">
<mat-form-field class="mat-block flex-1" [appearance]="appearance">
<mat-label>{{ labelText | translate }}</mat-label>
<input matInput type="number" min="0" formControlName="time" required>
<mat-error *ngIf="timeUnitSelectFormGroup.get('time').hasError('required')">
@ -29,7 +29,7 @@
{{ (minText || patternText) | translate : {min: minTime/1000} }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block flex-1">
<mat-form-field class="mat-block flex-1" [appearance]="appearance">
<mat-label translate>device-profile.condition-duration-time-unit</mat-label>
<mat-select formControlName="unit">
<mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">

4
ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.ts

@ -37,6 +37,7 @@ import {
timeUnitTranslationMap
} from '@shared/models/time/time.models';
import { isDefinedAndNotNull, isNumber } from '@core/utils';
import { MatFormFieldAppearance } from '@angular/material/form-field';
interface FormGroupModel {
time: number;
@ -105,6 +106,9 @@ export class TimeUnitSelectComponent implements OnInit, OnDestroy, ControlValueA
@Input()
minText: string;
@Input()
appearance: MatFormFieldAppearance = 'fill';
private propagateChange = (v: any) => {
}

38
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.html

@ -17,25 +17,31 @@
-->
<div class="flex flex-col">
<mat-accordion multi="true">
<div *ngFor="let serverConfig of serverConfigsFromArray.controls; trackBy: trackByParams; let $index = index;">
<tb-profile-lwm2m-device-config-server
[formControl]="serverConfig"
[isBootstrap]="serverConfig.value.bootstrapServerIs"
(removeServer)="removeServerConfig($event, $index)"
(isTransportWasRunWithBootstrapChange)="updateIsTransportWasRunWithBootstrap($event)">
</tb-profile-lwm2m-device-config-server>
</div>
@for (serverConfig of serverConfigsFromArray.controls; track trackByParams($index)) {
<div>
<tb-profile-lwm2m-device-config-server
[formControl]="serverConfig"
[isBootstrap]="serverConfig.value.bootstrapServerIs"
(removeServer)="removeServerConfig($event, $index)"
(isTransportWasRunWithBootstrapChange)="updateIsTransportWasRunWithBootstrap($event)">
</tb-profile-lwm2m-device-config-server>
</div>
}
</mat-accordion>
<div *ngIf="!serverConfigsFromArray.controls.length" style="margin:32px 0">
@if (!serverConfigsFromArray.controls.length) {
<div style="margin:32px 0">
<span translate
class="tb-prompt flex items-center justify-center">device-profile.lwm2m.no-config-servers</span>
</div>
<div *ngIf="!disabled" style="padding-top: 16px;">
<button mat-raised-button color="primary"
type="button"
(click)="addServerConfig()">
</div>
}
@if (!disabled) {
<div style="padding-top: 16px;">
<button mat-raised-button color="primary"
type="button"
(click)="addServerConfig()">
<span>{{ (isBootstrapServerNotAvailable() ? 'device-profile.lwm2m.add-lwm2m-server-config' :
'device-profile.lwm2m.add-server-config') | translate }}</span>
</button>
</div>
</button>
</div>
}
</div>

9
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.ts

@ -18,11 +18,12 @@ import { Component, EventEmitter, forwardRef, Input, OnDestroy, OnInit, Output }
import {
AbstractControl,
ControlValueAccessor,
UntypedFormArray,
UntypedFormBuilder, UntypedFormControl,
UntypedFormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR
NG_VALUE_ACCESSOR,
FormControl,
FormArray
} from '@angular/forms';
import { of, Subject } from 'rxjs';
import { ServerSecurityConfig } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models';
@ -104,8 +105,8 @@ export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValu
this.destroy$.complete();
}
get serverConfigsFromArray(): UntypedFormArray {
return this.bootstrapConfigServersFormGroup.get('serverConfigs') as UntypedFormArray;
get serverConfigsFromArray(): FormArray<FormControl> {
return this.bootstrapConfigServersFormGroup.get('serverConfigs') as FormArray<FormControl>;
}
setDisabledState(isDisabled: boolean): void {

234
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html

@ -22,160 +22,180 @@
<div class="flex flex-1 flex-row items-center justify-start">
<div style="min-width:150px">{{ (serverFormGroup.get('bootstrapServerIs').value ?
'device-profile.lwm2m.bootstrap-server' : 'device-profile.lwm2m.lwm2m-server') | translate }}</div>
<div *ngIf="!serverPanel.expanded" style="font-size:14px" class="no-wrap flex flex-row">
<div style="margin-left:32px">{{ ('device-profile.lwm2m.short-id' | translate) + ': ' }}
<span style="font-style: italic">{{ serverFormGroup.get('shortServerId').value ? serverFormGroup.get('shortServerId').value : '' }}</span>
@if (!serverPanel.expanded) {
<div style="font-size:14px" class="no-wrap flex flex-row">
<div style="margin-left:32px">{{ ('device-profile.lwm2m.short-id' | translate) + ': ' }}
<span style="font-style: italic">{{ serverFormGroup.get('shortServerId').value ? serverFormGroup.get('shortServerId').value : '' }}</span>
</div>
<div style="margin-left:32px">{{ ('device-profile.lwm2m.mode' | translate) + ': ' }}
<span style="font-style: italic">{{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[serverFormGroup.get('securityMode').value]) }}</span>
</div>
</div>
<div style="margin-left:32px">{{ ('device-profile.lwm2m.mode' | translate) + ': ' }}
<span style="font-style: italic">{{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[serverFormGroup.get('securityMode').value]) }}</span>
</div>
</div>
}
</div>
</mat-panel-title>
<span class="flex-full"></span>
<button *ngIf="!disabled" mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeServer.emit($event)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
@if (!disabled) {
<button mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeServer.emit($event)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
}
</div>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section>
<div class="flex flex-row gap-2 xs:flex-col xs:gap-0">
<mat-form-field class="flex-1">
<mat-form-field class="flex-1" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.mode' | translate }}</mat-label>
<mat-select formControlName="securityMode">
<mat-option *ngFor="let securityMode of securityConfigLwM2MTypes"
[value]="securityMode">
{{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }}
</mat-option>
@for (securityMode of securityConfigLwM2MTypes; track securityMode) {
<mat-option [value]="securityMode">
{{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="flex-1" *ngIf="!isBootstrap">
<mat-label>{{ 'device-profile.lwm2m.short-id' | translate }}</mat-label>
<mat-icon *ngIf="!disabled" class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ (isBootstrap ? 'device-profile.lwm2m.short-id-tooltip-bootstrap': 'device-profile.lwm2m.short-id-tooltip') | translate }}">help</mat-icon>
<input matInput type="number" formControlName="shortServerId" required
[min]="isBootstrap ? undefined : shortServerIdMin"
[max]="isBootstrap ? undefined : shortServerIdMax">
<mat-error *ngIf="serverFormGroup.get('shortServerId').hasError('required')">
{{ 'device-profile.lwm2m.short-id-required' | translate }}
</mat-error>
<mat-error *ngIf="serverFormGroup.get('shortServerId').hasError('pattern')">
{{ (isBootstrap ? 'device-profile.lwm2m.short-id-pattern-bs' : 'device-profile.lwm2m.short-id-pattern') | translate }}
</mat-error>
<mat-error *ngIf="serverFormGroup.get('shortServerId').hasError('min') ||
serverFormGroup.get('shortServerId').hasError('max')">
{{ 'device-profile.lwm2m.short-id-range' | translate : {min: shortServerIdMin, max: shortServerIdMax} }}
</mat-error>
</mat-form-field>
@if (!isBootstrap) {
<mat-form-field class="flex-1" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.short-id' | translate }}</mat-label>
@if (!disabled) {
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ (isBootstrap ? 'device-profile.lwm2m.short-id-tooltip-bootstrap' : 'device-profile.lwm2m.short-id-tooltip') | translate }}">help</mat-icon>
}
<input matInput type="number" formControlName="shortServerId" required
[min]="isBootstrap ? undefined : shortServerIdMin"
[max]="isBootstrap ? undefined : shortServerIdMax">
@if (serverFormGroup.get('shortServerId').hasError('required')) {
<mat-error>{{ 'device-profile.lwm2m.short-id-required' | translate }}</mat-error>
}
@if (serverFormGroup.get('shortServerId').hasError('pattern')) {
<mat-error>{{ (isBootstrap ? 'device-profile.lwm2m.short-id-pattern-bs' : 'device-profile.lwm2m.short-id-pattern') | translate }}</mat-error>
}
@if (serverFormGroup.get('shortServerId').hasError('min') ||
serverFormGroup.get('shortServerId').hasError('max')) {
<mat-error>{{ 'device-profile.lwm2m.short-id-range' | translate : {min: shortServerIdMin, max: shortServerIdMax} }}</mat-error>
}
</mat-form-field>
}
</div>
<div class="flex flex-row gap-2 xs:flex-col xs:gap-0">
<mat-form-field class="flex-1">
<mat-form-field class="flex-1" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.server-host' | translate }}</mat-label>
<input matInput type="text" formControlName="host" required>
<mat-error *ngIf="serverFormGroup.get('host').hasError('required')">
{{ 'device-profile.lwm2m.server-host-required' | translate }}
</mat-error>
@if (serverFormGroup.get('host').hasError('required')) {
<mat-error>{{ 'device-profile.lwm2m.server-host-required' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-1">
<mat-form-field class="flex-1" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.server-port' | translate }}</mat-label>
<input matInput type="number" formControlName="port" required min="0" max="65535" readonly>
<mat-error *ngIf="serverFormGroup.get('port').hasError('required')">
{{ 'device-profile.lwm2m.server-port-required' | translate }}
</mat-error>
<mat-error *ngIf="serverFormGroup.get('port').hasError('pattern')">
{{ 'device-profile.lwm2m.server-port-pattern' | translate }}
</mat-error>
<mat-error *ngIf="serverFormGroup.get('port').hasError('min') ||
serverFormGroup.get('port').hasError('max')">
{{ 'device-profile.lwm2m.server-port-range' | translate }}
</mat-error>
@if (serverFormGroup.get('port').hasError('required')) {
<mat-error>{{ 'device-profile.lwm2m.server-port-required' | translate }}</mat-error>
}
@if (serverFormGroup.get('port').hasError('pattern')) {
<mat-error>{{ 'device-profile.lwm2m.server-port-pattern' | translate }}</mat-error>
}
@if (serverFormGroup.get('port').hasError('min') ||
serverFormGroup.get('port').hasError('max')) {
<mat-error>{{ 'device-profile.lwm2m.server-port-range' | translate }}</mat-error>
}
</mat-form-field>
</div>
<div class="flex flex-row gap-2 xs:flex-col xs:gap-0">
<mat-form-field class="flex-1">
<mat-form-field class="flex-1" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.client-hold-off-time' | translate }}</mat-label>
<mat-icon *ngIf="!disabled" class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{'device-profile.lwm2m.client-hold-off-time-tooltip' | translate }}">help</mat-icon>
@if (!disabled) {
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ 'device-profile.lwm2m.client-hold-off-time-tooltip' | translate }}">help</mat-icon>
}
<input matInput type="number" formControlName="clientHoldOffTime" required min="0">
<mat-error *ngIf="serverFormGroup.get('clientHoldOffTime').hasError('required')">
{{ 'device-profile.lwm2m.client-hold-off-time-required' | translate }}
</mat-error>
<mat-error *ngIf="serverFormGroup.get('clientHoldOffTime').hasError('min') ||
serverFormGroup.get('clientHoldOffTime').hasError('pattern')">
{{ 'device-profile.lwm2m.client-hold-off-time-pattern' | translate }}
</mat-error>
@if (serverFormGroup.get('clientHoldOffTime').hasError('required')) {
<mat-error>{{ 'device-profile.lwm2m.client-hold-off-time-required' | translate }}</mat-error>
}
@if (serverFormGroup.get('clientHoldOffTime').hasError('min') ||
serverFormGroup.get('clientHoldOffTime').hasError('pattern')) {
<mat-error>{{ 'device-profile.lwm2m.client-hold-off-time-pattern' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-1">
<mat-form-field class="flex-1" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.account-after-timeout' | translate }}</mat-label>
<mat-icon *ngIf="!disabled" class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{'device-profile.lwm2m.account-after-timeout-tooltip' | translate }}">help</mat-icon>
@if (!disabled) {
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ 'device-profile.lwm2m.account-after-timeout-tooltip' | translate }}">help</mat-icon>
}
<input matInput type="number" formControlName="bootstrapServerAccountTimeout" required min="0">
<mat-error *ngIf="serverFormGroup.get('bootstrapServerAccountTimeout').hasError('required')">
{{ 'device-profile.lwm2m.account-after-timeout-required' | translate }}
</mat-error>
<mat-error *ngIf="serverFormGroup.get('bootstrapServerAccountTimeout').hasError('min') ||
serverFormGroup.get('bootstrapServerAccountTimeout').hasError('pattern')">
{{ 'device-profile.lwm2m.account-after-timeout-pattern' | translate }}
</mat-error>
@if (serverFormGroup.get('bootstrapServerAccountTimeout').hasError('required')) {
<mat-error>{{ 'device-profile.lwm2m.account-after-timeout-required' | translate }}</mat-error>
}
@if (serverFormGroup.get('bootstrapServerAccountTimeout').hasError('min') ||
serverFormGroup.get('bootstrapServerAccountTimeout').hasError('pattern')) {
<mat-error>{{ 'device-profile.lwm2m.account-after-timeout-pattern' | translate }}</mat-error>
}
</mat-form-field>
</div>
<div *ngIf="serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK ||
serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509">
<mat-form-field class="mat-block">
@if (serverFormGroup.get('securityMode').value === securityConfigLwM2MType.RPK ||
serverFormGroup.get('securityMode').value === securityConfigLwM2MType.X509) {
<mat-form-field class="mat-block" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.server-public-key' | translate }}</mat-label>
<mat-icon *ngIf="!disabled" class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ publicKeyOrIdTooltipNamesMap.get(serverFormGroup.get('securityMode').value) | translate }}">help</mat-icon>
@if (!disabled) {
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ publicKeyOrIdTooltipNamesMap.get(serverFormGroup.get('securityMode').value) | translate }}">help</mat-icon>
}
<textarea matInput
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cols="1" required
formControlName="serverPublicKey"
required>
formControlName="serverPublicKey">
</textarea>
<mat-error *ngIf="serverFormGroup.get('serverPublicKey').hasError('required')">
{{ 'device-profile.lwm2m.server-public-key-required' | translate }}
</mat-error>
@if (serverFormGroup.get('serverPublicKey').hasError('required')) {
<mat-error>{{ 'device-profile.lwm2m.server-public-key-required' | translate }}</mat-error>
}
</mat-form-field>
</div>
}
<div class="flex flex-row gap-2 xs:flex-col xs:gap-0">
<mat-form-field class="flex-1">
<mat-form-field class="flex-1" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.lifetime' | translate }}</mat-label>
<input matInput type="number" min="0" formControlName="lifetime" required>
<mat-error *ngIf="serverFormGroup.get('lifetime').hasError('required')">
{{ 'device-profile.lwm2m.lifetime-required' | translate }}
</mat-error>
<mat-error *ngIf="serverFormGroup.get('lifetime').hasError('pattern') ||
serverFormGroup.get('lifetime').hasError('min')">
{{ 'device-profile.lwm2m.lifetime-pattern' | translate }}
</mat-error>
@if (serverFormGroup.get('lifetime').hasError('required')) {
<mat-error>{{ 'device-profile.lwm2m.lifetime-required' | translate }}</mat-error>
}
@if (serverFormGroup.get('lifetime').hasError('pattern') ||
serverFormGroup.get('lifetime').hasError('min')) {
<mat-error>{{ 'device-profile.lwm2m.lifetime-pattern' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-1">
<mat-form-field class="flex-1" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.default-min-period' | translate }}</mat-label>
<mat-icon *ngIf="!disabled" class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{'device-profile.lwm2m.default-min-period-tooltip' | translate }}">help</mat-icon>
@if (!disabled) {
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ 'device-profile.lwm2m.default-min-period-tooltip' | translate }}">help</mat-icon>
}
<input matInput type="number" min="0" formControlName="defaultMinPeriod" required>
<mat-error *ngIf="serverFormGroup.get('defaultMinPeriod').hasError('required')">
{{ 'device-profile.lwm2m.default-min-period-required' | translate }}
</mat-error>
<mat-error *ngIf="serverFormGroup.get('defaultMinPeriod').hasError('pattern') ||
serverFormGroup.get('defaultMinPeriod').hasError('min')">
{{ 'device-profile.lwm2m.default-min-period-pattern' | translate }}
</mat-error>
@if (serverFormGroup.get('defaultMinPeriod').hasError('required')) {
<mat-error>{{ 'device-profile.lwm2m.default-min-period-required' | translate }}</mat-error>
}
@if (serverFormGroup.get('defaultMinPeriod').hasError('pattern') ||
serverFormGroup.get('defaultMinPeriod').hasError('min')) {
<mat-error>{{ 'device-profile.lwm2m.default-min-period-pattern' | translate }}</mat-error>
}
</mat-form-field>
</div>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.binding' | translate }}</mat-label>
<mat-icon *ngIf="!disabled" class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{'device-profile.lwm2m.binding-tooltip' | translate }}">help</mat-icon>
@if (!disabled) {
<mat-icon class="mat-primary" aria-hidden="false" aria-label="help-icon" matSuffix style="cursor:pointer;"
matTooltip="{{ 'device-profile.lwm2m.binding-tooltip' | translate }}">help</mat-icon>
}
<mat-select formControlName="binding">
<mat-option *ngFor="let bindingMode of bindingModeTypes" [value]="bindingMode">
{{ bindingModeTypeNamesMap.get(bindingMode) | translate }}
</mat-option>
@for (bindingMode of bindingModeTypes; track bindingMode) {
<mat-option [value]="bindingMode">
{{ bindingModeTypeNamesMap.get(bindingMode) | translate }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-checkbox formControlName="notifIfDisabled" color="primary">

82
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html

@ -19,10 +19,11 @@
<mat-tab-group dynamicHeight>
<mat-tab label="{{ 'device-profile.lwm2m.model-tab' | translate }}">
<ng-template matTabContent>
<section [formGroup]="lwm2mDeviceProfileFormGroup">
<section [formGroup]="lwm2mDeviceProfileFormGroup" class="mt-2">
<tb-profile-lwm2m-object-list
(addList)="addObjectsList($event)"
(removeList)="removeObjectsList($event)"
appearance="outline"
formControlName="objectIds">
</tb-profile-lwm2m-object-list>
<mat-checkbox formControlName="initAttrTelAsObsStrategy">
@ -30,18 +31,20 @@
{{ 'device-profile.lwm2m.init-attr-tel-as-obs-strategy' | translate }}
</span>
</mat-checkbox>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device-profile.lwm2m.observe-strategy.observe-strategy</mat-label>
<mat-select formControlName="observeStrategy">
<mat-select-trigger>
{{ observeStrategyMap.get(lwm2mDeviceProfileFormGroup.get('observeStrategy').value)?.name | translate }}
</mat-select-trigger>
<mat-option *ngFor="let strategy of observeStrategyList" [value]="strategy">
{{ observeStrategyMap.get(strategy).name | translate }}
<small style="display: block;">
{{ observeStrategyMap.get(strategy).description | translate }}
</small>
</mat-option>
@for (strategy of observeStrategyList; track strategy) {
<mat-option [value]="strategy">
{{ observeStrategyMap.get(strategy).name | translate }}
<small style="display: block;">
{{ observeStrategyMap.get(strategy).description | translate }}
</small>
</mat-option>
}
</mat-select>
</mat-form-field>
<tb-profile-lwm2m-observe-attr-telemetry
@ -52,17 +55,20 @@
</mat-tab>
<mat-tab label="{{ 'device-profile.lwm2m.bootstrap-tab' | translate }}">
<section [formGroup]="lwm2mDeviceProfileFormGroup">
<div *ngIf="!isTransportWasRunWithBootstrap" style="padding:16px 0 0 8px;" class="flex flex-row items-end justify-start">
<mat-icon color="warn" class="material-icons" style="margin-right:8px;">warning</mat-icon>
<span style="font-weight:500">LwM2M transport was run without bootstrap server</span>
</div>
@if (!isTransportWasRunWithBootstrap) {
<div style="padding:16px 0 0 8px;" class="flex flex-row items-end justify-start">
<mat-icon color="warn" class="material-icons" style="margin-right:8px;">warning</mat-icon>
<span style="font-weight:500">LwM2M transport was run without bootstrap server</span>
</div>
}
<mat-checkbox formControlName="bootstrapServerUpdateEnable" style="margin-left:8px;margin-top:20px">
{{ 'device-profile.lwm2m.include-bootstrap-server' | translate }}
</mat-checkbox>
<section style="padding: 20px 2px">
<tb-profile-lwm2m-bootstrap-config-servers formControlName="bootstrap"
[isBootstrapServerUpdateEnable]="isBootstrapServerUpdateEnable"
[(isTransportWasRunWithBootstrap)]="isTransportWasRunWithBootstrap">
<tb-profile-lwm2m-bootstrap-config-servers
formControlName="bootstrap"
[isBootstrapServerUpdateEnable]="isBootstrapServerUpdateEnable"
[(isTransportWasRunWithBootstrap)]="isTransportWasRunWithBootstrap">
</tb-profile-lwm2m-bootstrap-config-servers>
</section>
</section>
@ -79,7 +85,7 @@
</mat-checkbox>
<fieldset class="fields-group">
<legend class="group-title" translate>device-profile.lwm2m.fw-update</legend>
<mat-form-field class="mat-block flex-1">
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.fw-update-strategy' | translate }}</mat-label>
<mat-select formControlName="fwUpdateStrategy">
<mat-option [value]=1>{{ 'device-profile.lwm2m.fw-update-strategy-package' | translate }}</mat-option>
@ -87,30 +93,34 @@
<mat-option [value]=3>{{ 'device-profile.lwm2m.fw-update-strategy-data' | translate }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block flex-1" *ngIf="lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').value === 2">
<mat-label>{{ 'device-profile.lwm2m.fw-update-resource' | translate }}</mat-label>
<input matInput formControlName="fwUpdateResource" required>
<mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateResource').hasError('required')">
{{ 'device-profile.lwm2m.fw-update-resource-required' | translate }}
</mat-error>
</mat-form-field>
@if (lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').value === 2) {
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.fw-update-resource' | translate }}</mat-label>
<input matInput formControlName="fwUpdateResource" required>
@if (lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateResource').hasError('required')) {
<mat-error>{{ 'device-profile.lwm2m.fw-update-resource-required' | translate }}</mat-error>
}
</mat-form-field>
}
</fieldset>
<fieldset class="fields-group">
<legend class="group-title" translate>device-profile.lwm2m.sw-update</legend>
<mat-form-field class="mat-block flex-1">
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.sw-update-strategy' | translate }}</mat-label>
<mat-select formControlName="swUpdateStrategy">
<mat-option [value]=1>{{ 'device-profile.lwm2m.sw-update-strategy-package' | translate }}</mat-option>
<mat-option [value]=2>{{ 'device-profile.lwm2m.sw-update-strategy-package-uri' | translate }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block flex-1" *ngIf="lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateStrategy').value === 2">
<mat-label>{{ 'device-profile.lwm2m.sw-update-resource' | translate }}</mat-label>
<input matInput formControlName="swUpdateResource" required>
<mat-error *ngIf="lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateResource').hasError('required')">
{{ 'device-profile.lwm2m.sw-update-resource-required' | translate }}
</mat-error>
</mat-form-field>
@if (lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateStrategy').value === 2) {
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label>{{ 'device-profile.lwm2m.sw-update-resource' | translate }}</mat-label>
<input matInput formControlName="swUpdateResource" required>
@if (lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateResource').hasError('required')) {
<mat-error>{{ 'device-profile.lwm2m.sw-update-resource-required' | translate }}</mat-error>
}
</mat-form-field>
}
</fieldset>
</fieldset>
<fieldset class="fields-group">
@ -120,11 +130,13 @@
</fieldset>
<fieldset class="fields-group">
<legend class="group-title" translate>device-profile.lwm2m.default-object-id</legend>
<mat-form-field class="flex-1">
<mat-form-field class="flex-1" appearance="outline">
<mat-select formControlName="defaultObjectIDVer">
<mat-option *ngFor="let objectIDVer of objectIDVers" [value]="objectIDVer">
{{ objectIDVerTranslationMap.get(objectIDVer) | translate }}
</mat-option>
@for (objectIDVer of objectIDVers; track objectIDVer) {
<mat-option [value]="objectIDVer">
{{ objectIDVerTranslationMap.get(objectIDVer) | translate }}
</mat-option>
}
</mat-select>
</mat-form-field>
</fieldset>

2
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<mat-form-field [formGroup]="lwm2mListFormGroup" class="mat-block">
<mat-form-field [formGroup]="lwm2mListFormGroup" class="mat-block" [appearance]="appearance">
<mat-label translate>device-profile.lwm2m.object-list</mat-label>
<mat-chip-grid #chipList formControlName="objectsList" [required]="required">
<mat-chip-row

4
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts

@ -35,6 +35,7 @@ import { isDefined, isDefinedAndNotNull, isObject, isString } from '@core/utils'
import { PageLink } from '@shared/models/page/page-link';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { MatFormFieldAppearance } from '@angular/material/form-field';
@Component({
selector: 'tb-profile-lwm2m-object-list',
@ -74,6 +75,9 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V
this.updateValidators();
}
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Output()
addList = new EventEmitter<any>();

105
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-instances.component.html

@ -17,57 +17,58 @@
-->
<section [formGroup]="instancesFormGroup">
<mat-accordion multi="true" formArrayName="instances">
<mat-expansion-panel
class="instance-row"
*ngFor="let instances of instancesFormArray.controls; let $index = index; trackBy: trackByParams"
[formGroupName]="$index"
[expanded]="isExpend"
[disabled]="isExpend">
<mat-expansion-panel-header>
<mat-panel-title class="tb-panel flex flex-row">
<div class="flex max-w-30% flex-full items-center justify-start">
<span class="tb-panel-title">{{ 'device-profile.lwm2m.instance' | translate }}</span>&nbsp;#{{instances.get('id').value}}
</div>
<div class="flex max-w-10% flex-full items-center justify-center">
<mat-checkbox color="warn"
[disabled]="disabled"
[checked]="getChecked(instances, 'attribute')"
(click)="$event.stopPropagation()"
(change)="changeInstanceResourcesCheckBox($event.checked, instances, 'attribute')"
[indeterminate]="getIndeterminate(instances, 'attribute')">
</mat-checkbox>
</div>
<div class="flex max-w-10% flex-full items-center justify-center">
<mat-checkbox color="primary"
[disabled]="disabled"
[checked]="getChecked(instances, 'telemetry')"
(click)="$event.stopPropagation()"
(change)="changeInstanceResourcesCheckBox($event.checked, instances, 'telemetry')"
[indeterminate]="getIndeterminate(instances, 'telemetry')">
</mat-checkbox>
</div>
<div class="flex max-w-10% flex-full items-center justify-center">
<mat-checkbox color="primary"
[disabled]="disableObserve(instances)"
[checked]="getChecked(instances, 'observe')"
(click)="$event.stopPropagation()"
(change)="changeInstanceResourcesCheckBox($event.checked, instances, 'observe')"
[indeterminate]="getIndeterminate(instances, 'observe')">
</mat-checkbox>
</div>
<span class="flex-1"></span>
<tb-profile-lwm2m-attributes
formControlName="attributes"
[isAttributeTelemetry]="disableObserveInstance(instances)"
[modelName]="getNameInstance(instances.value)">
</tb-profile-lwm2m-attributes>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-profile-lwm2m-observe-attr-telemetry-resource
formControlName="resources">
</tb-profile-lwm2m-observe-attr-telemetry-resource>
</ng-template>
</mat-expansion-panel>
@for (instances of instancesFormArray.controls; track trackByParams($index, instances.value)) {
<mat-expansion-panel
class="instance-row"
[formGroupName]="$index"
[expanded]="isExpend"
[disabled]="isExpend">
<mat-expansion-panel-header>
<mat-panel-title class="tb-panel flex flex-row">
<div class="flex max-w-30% flex-full items-center justify-start">
<span class="tb-panel-title">{{ 'device-profile.lwm2m.instance' | translate }}</span>&nbsp;#{{ instances.get('id').value }}
</div>
<div class="flex max-w-10% flex-full items-center justify-center">
<mat-checkbox color="warn"
[disabled]="disabled"
[checked]="getChecked(instances, 'attribute')"
(click)="$event.stopPropagation()"
(change)="changeInstanceResourcesCheckBox($event.checked, instances, 'attribute')"
[indeterminate]="getIndeterminate(instances, 'attribute')">
</mat-checkbox>
</div>
<div class="flex max-w-10% flex-full items-center justify-center">
<mat-checkbox color="primary"
[disabled]="disabled"
[checked]="getChecked(instances, 'telemetry')"
(click)="$event.stopPropagation()"
(change)="changeInstanceResourcesCheckBox($event.checked, instances, 'telemetry')"
[indeterminate]="getIndeterminate(instances, 'telemetry')">
</mat-checkbox>
</div>
<div class="flex max-w-10% flex-full items-center justify-center">
<mat-checkbox color="primary"
[disabled]="disableObserve(instances)"
[checked]="getChecked(instances, 'observe')"
(click)="$event.stopPropagation()"
(change)="changeInstanceResourcesCheckBox($event.checked, instances, 'observe')"
[indeterminate]="getIndeterminate(instances, 'observe')">
</mat-checkbox>
</div>
<span class="flex-1"></span>
<tb-profile-lwm2m-attributes
formControlName="attributes"
[isAttributeTelemetry]="disableObserveInstance(instances)"
[modelName]="getNameInstance(instances.value)">
</tb-profile-lwm2m-attributes>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-profile-lwm2m-observe-attr-telemetry-resource
formControlName="resources">
</tb-profile-lwm2m-observe-attr-telemetry-resource>
</ng-template>
</mat-expansion-panel>
}
</mat-accordion>
</section>

71
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.html

@ -34,41 +34,42 @@
</div>
</div>
<mat-divider></mat-divider>
<div formArrayName="resources"
*ngFor="let resourceLwM2M of resourcesFormArray.controls; let $index = index; trackBy: trackByParams">
<div [formGroupName]="$index" class="resource-list flex flex-row items-center justify-start">
<div class="resource-name max-w-30% flex-full">
#{{resourceLwM2M.get('id').value}} <span class="name">{{resourceLwM2M.get('name').value}}</span>
<div formArrayName="resources">
@for (resourceLwM2M of resourcesFormArray.controls; track trackByParams($index, resourceLwM2M.value)) {
<div [formGroupName]="$index" class="resource-list flex flex-row items-center justify-start">
<div class="resource-name max-w-30% flex-full">
#{{ resourceLwM2M.get('id').value }} <span class="name">{{ resourceLwM2M.get('name').value }}</span>
</div>
<div class="flex max-w-10% flex-full items-center justify-center">
<mat-checkbox formControlName="attribute" color="warn">
</mat-checkbox>
</div>
<div class="flex max-w-10% flex-full items-center justify-center">
<mat-checkbox formControlName="telemetry" color="primary">
</mat-checkbox>
</div>
<div class="flex max-w-10% flex-full items-center justify-center">
<mat-checkbox formControlName="observe" color="primary"
matTooltip="{{ 'device-profile.lwm2m.edit-observe-select' | translate }}"
[matTooltipDisabled]="disabled || !isDisabledObserve($index)"
matTooltipPosition="above">
</mat-checkbox>
</div>
<mat-form-field class="max-w-33% flex-full" appearance="outline">
<input matInput type="text" formControlName="keyName" required>
@if (resourceLwM2M.get('keyName').hasError('required') ||
resourceLwM2M.get('keyName').hasError('pattern')) {
<mat-error>{{ 'device-profile.lwm2m.key-name-required' | translate }}</mat-error>
}
</mat-form-field>
<span class="flex-1"></span>
<tb-profile-lwm2m-attributes
formControlName="attributes"
[isAttributeTelemetry]="isDisabledObserve($index)"
isResource="true"
[modelName]="getNameResourceLwm2m(resourceLwM2M.value)">
</tb-profile-lwm2m-attributes>
</div>
<div class="flex max-w-10% flex-full items-center justify-center">
<mat-checkbox formControlName="attribute" color="warn">
</mat-checkbox>
</div>
<div class="flex max-w-10% flex-full items-center justify-center">
<mat-checkbox formControlName="telemetry" color="primary">
</mat-checkbox>
</div>
<div class="flex max-w-10% flex-full items-center justify-center">
<mat-checkbox formControlName="observe" color="primary"
matTooltip="{{ 'device-profile.lwm2m.edit-observe-select' | translate }}"
[matTooltipDisabled]="disabled || !isDisabledObserve($index)"
matTooltipPosition="above">
</mat-checkbox>
</div>
<mat-form-field class="max-w-33% flex-full">
<input matInput type="text" formControlName="keyName" required>
<mat-error *ngIf="resourceLwM2M.get('keyName').hasError('required') ||
resourceLwM2M.get('keyName').hasError('pattern')">
{{ 'device-profile.lwm2m.key-name-required' | translate }}
</mat-error>
</mat-form-field>
<span class="flex-1"></span>
<tb-profile-lwm2m-attributes
formControlName="attributes"
[isAttributeTelemetry]="isDisabledObserve($index)"
isResource="true"
[modelName]="getNameResourceLwm2m(resourceLwM2M.value)">
</tb-profile-lwm2m-attributes>
</div>
}
</div>
</section>

54
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html

@ -17,31 +17,33 @@
-->
<section [formGroup]="modelsFormGroup">
<mat-accordion multi="true" formArrayName="models">
<mat-expansion-panel
*ngFor="let objectLwM2M of modelsFormArray.controls; let $index = index; trackBy: trackByParams"
[formGroupName]="$index">
<mat-expansion-panel-header >
<mat-panel-title class="flex items-center justify-start">
<span class="tb-panel-title">{{ objectLwM2M.get('name').value}}</span>&nbsp;#{{ objectLwM2M.get('keyId').value}}
<span class="flex-1"></span>
<tb-profile-lwm2m-attributes
formControlName="attributes"
[modelName]="getNameObject(objectLwM2M.value)">
</tb-profile-lwm2m-attributes>
<button type="button"
*ngIf="!disabled && objectLwM2M.get('multiple').value"
mat-icon-button (click)="addInstances($event, objectLwM2M)"
matTooltip="{{'device-profile.lwm2m.add-new-instances' | translate}}"
matTooltipPosition="above">
<mat-icon class="material-icons">note_add</mat-icon>
</button>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-profile-lwm2m-observe-attr-telemetry-instances
formControlName="instances">
</tb-profile-lwm2m-observe-attr-telemetry-instances>
</ng-template>
</mat-expansion-panel>
@for (objectLwM2M of modelsFormArray.controls; track trackByParams($index, objectLwM2M.value)) {
<mat-expansion-panel [formGroupName]="$index">
<mat-expansion-panel-header>
<mat-panel-title class="flex items-center justify-start">
<span class="tb-panel-title">{{ objectLwM2M.get('name').value }}</span>&nbsp;#{{ objectLwM2M.get('keyId').value }}
<span class="flex-1"></span>
<tb-profile-lwm2m-attributes
formControlName="attributes"
[modelName]="getNameObject(objectLwM2M.value)">
</tb-profile-lwm2m-attributes>
@if (!disabled && objectLwM2M.get('multiple').value) {
<button type="button"
mat-icon-button
(click)="addInstances($event, objectLwM2M)"
matTooltip="{{ 'device-profile.lwm2m.add-new-instances' | translate }}"
matTooltipPosition="above">
<mat-icon class="material-icons">note_add</mat-icon>
</button>
}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-profile-lwm2m-observe-attr-telemetry-instances
formControlName="instances">
</tb-profile-lwm2m-observe-attr-telemetry-instances>
</ng-template>
</mat-expansion-panel>
}
</mat-accordion>
</section>

288
ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html

@ -19,156 +19,150 @@
<mat-checkbox formControlName="sparkplug">
{{ 'device-profile.mqtt-device-topic-filters-spark-plug' | translate }}
</mat-checkbox>
<div *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplug').value"
class="tb-hint" innerHTML="{{ 'device-profile.mqtt-device-topic-filters-spark-plug-hint' | translate }}"></div>
<tb-string-items-list *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplug').value"
editable
label="{{ 'device-profile.mqtt-device-topic-filters-spark-plug-attribute-metric-names' | translate }}"
placeholder="{{'device-profile.mqtt-device-topic-filters-spark-plug-attribute-metric-names' | translate}}"
hint="{{ 'device-profile.mqtt-device-topic-filters-spark-plug-attribute-metric-names-hint' | translate }}"
floatLabel="always"
subscriptSizing="dynamic"
formControlName="sparkplugAttributesMetricNames">
</tb-string-items-list>
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplug').value) {
<div class="tb-hint" [innerHTML]="'device-profile.mqtt-device-topic-filters-spark-plug-hint' | translate"></div>
<tb-string-items-list
editable
label="{{ 'device-profile.mqtt-device-topic-filters-spark-plug-attribute-metric-names' | translate }}"
placeholder="{{ 'device-profile.mqtt-device-topic-filters-spark-plug-attribute-metric-names' | translate }}"
hint="{{ 'device-profile.mqtt-device-topic-filters-spark-plug-attribute-metric-names-hint' | translate }}"
floatLabel="always"
subscriptSizing="dynamic"
appearance="outline"
formControlName="sparkplugAttributesMetricNames">
</tb-string-items-list>
}
</form>
<form [formGroup]="mqttDeviceProfileTransportConfigurationFormGroup" style="padding-bottom: 16px;" *ngIf="!mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplug').value">
<fieldset class="fields-group">
<legend class="group-title" translate>device-profile.mqtt-device-topic-filters</legend>
<div class="flex flex-col gap-2">
<div class="flex flex-row gap-2 xs:flex-col">
<mat-form-field class="flex-1">
<mat-label translate>device-profile.telemetry-topic-filter</mat-label>
<input matInput required
formControlName="deviceTelemetryTopic"
type="text">
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('deviceTelemetryTopic').hasError('required')">
{{ 'device-profile.telemetry-topic-filter-required' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('deviceTelemetryTopic').hasError('invalidSingleTopicCharacter')">
{{ 'device-profile.not-valid-single-character' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('deviceTelemetryTopic').hasError('invalidMultiTopicCharacter')">
{{ 'device-profile.not-valid-multi-character' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="flex-1">
<mat-label translate>device-profile.attributes-topic-filter</mat-label>
<input matInput required
formControlName="deviceAttributesTopic"
type="text">
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesTopic').hasError('required')">
{{ 'device-profile.attributes-topic-filter-required' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesTopic').hasError('invalidSingleTopicCharacter')">
{{ 'device-profile.not-valid-single-character' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesTopic').hasError('invalidMultiTopicCharacter')">
{{ 'device-profile.not-valid-multi-character' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="flex-1">
<mat-label translate>device-profile.attributes-subscribe-topic-filter</mat-label>
<input matInput required
formControlName="deviceAttributesSubscribeTopic"
type="text">
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesSubscribeTopic').hasError('required')">
{{ 'device-profile.attributes-subscribe-topic-filter-required' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesSubscribeTopic').hasError('invalidSingleTopicCharacter')">
{{ 'device-profile.not-valid-single-character' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesSubscribeTopic').hasError('invalidMultiTopicCharacter')">
{{ 'device-profile.not-valid-multi-character' | translate}}
</mat-error>
</mat-form-field>
</div>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.hasError('unique')">
{{ 'device-profile.mqtt-device-topic-filters-unique' | translate }}
</mat-error>
<div class="tb-hint" innerHTML="{{ 'device-profile.support-level-wildcards' | translate }}"></div>
<div class="tb-hint" innerHTML="{{ 'device-profile.single-level-wildcards-hint' | translate }}"></div>
<div class="tb-hint" innerHTML="{{ 'device-profile.multi-level-wildcards-hint' | translate }}"></div>
</div>
</fieldset>
<section formGroupName="transportPayloadTypeConfiguration">
@if (!mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplug').value) {
<form [formGroup]="mqttDeviceProfileTransportConfigurationFormGroup" style="padding-bottom: 16px;">
<fieldset class="fields-group">
<legend class="group-title" translate>device-profile.mqtt-device-payload-type</legend>
<legend class="group-title" translate>device-profile.mqtt-device-topic-filters</legend>
<div class="flex flex-col gap-2">
<mat-form-field class="mat-block">
<mat-select formControlName="transportPayloadType" required>
<mat-option *ngFor="let type of transportPayloadTypes" [value]="type">
{{transportPayloadTypeTranslations.get(type) | translate}}
</mat-option>
</mat-select>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType').hasError('required')">
{{ 'device-profile.mqtt-payload-type-required' | translate }}
</mat-error>
</mat-form-field>
<div *ngIf="protoPayloadType" style="padding-bottom: 20px">
<mat-checkbox formControlName="enableCompatibilityWithJsonPayloadFormat">
{{ 'device-profile.mqtt-enable-compatibility-with-json-payload-format' | translate }}
</mat-checkbox>
<div class="tb-hint" innerHTML="{{ 'device-profile.mqtt-enable-compatibility-with-json-payload-format-hint' | translate }}"></div>
<div *ngIf="compatibilityWithJsonPayloadFormatEnabled">
<mat-checkbox formControlName="useJsonPayloadFormatForDefaultDownlinkTopics">
{{ 'device-profile.mqtt-use-json-format-for-default-downlink-topics' | translate }}
</mat-checkbox>
<div class="tb-hint" innerHTML="{{ 'device-profile.mqtt-use-json-format-for-default-downlink-topics-hint' | translate }}"></div>
</div>
</div>
<div *ngIf="protoPayloadType" class="flex flex-col">
<ng-container>
<tb-protobuf-content
class="flex-1"
formControlName="deviceTelemetryProtoSchema"
label="{{ 'device-profile.telemetry-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.deviceTelemetryProtoSchema').hasError('required')">
{{ 'device-profile.telemetry-proto-schema-required' | translate}}
</mat-error>
</ng-container>
<ng-container>
<tb-protobuf-content
class="flex-1"
formControlName="deviceAttributesProtoSchema"
label="{{ 'device-profile.attributes-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.deviceAttributesProtoSchema').hasError('required')">
{{ 'device-profile.attributes-proto-schema-required' | translate}}
</mat-error>
</ng-container>
<ng-container>
<tb-protobuf-content
class="flex-1"
formControlName="deviceRpcRequestProtoSchema"
label="{{ 'device-profile.rpc-request-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.deviceRpcRequestProtoSchema').hasError('required')">
{{ 'device-profile.rpc-request-proto-schema-required' | translate}}
</mat-error>
</ng-container>
<ng-container>
<tb-protobuf-content
class="flex-1"
formControlName="deviceRpcResponseProtoSchema"
label="{{ 'device-profile.rpc-response-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.deviceRpcResponseProtoSchema').hasError('required')">
{{ 'device-profile.rpc-response-proto-schema-required' | translate}}
</mat-error>
</ng-container>
<div class="flex flex-row gap-2 xs:flex-col">
<mat-form-field class="flex-1" appearance="outline">
<mat-label translate>device-profile.telemetry-topic-filter</mat-label>
<input matInput required formControlName="deviceTelemetryTopic" type="text">
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('deviceTelemetryTopic').hasError('required')) {
<mat-error>{{ 'device-profile.telemetry-topic-filter-required' | translate }}</mat-error>
}
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('deviceTelemetryTopic').hasError('invalidSingleTopicCharacter')) {
<mat-error>{{ 'device-profile.not-valid-single-character' | translate }}</mat-error>
}
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('deviceTelemetryTopic').hasError('invalidMultiTopicCharacter')) {
<mat-error>{{ 'device-profile.not-valid-multi-character' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-1" appearance="outline">
<mat-label translate>device-profile.attributes-topic-filter</mat-label>
<input matInput required formControlName="deviceAttributesTopic" type="text">
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesTopic').hasError('required')) {
<mat-error>{{ 'device-profile.attributes-topic-filter-required' | translate }}</mat-error>
}
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesTopic').hasError('invalidSingleTopicCharacter')) {
<mat-error>{{ 'device-profile.not-valid-single-character' | translate }}</mat-error>
}
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesTopic').hasError('invalidMultiTopicCharacter')) {
<mat-error>{{ 'device-profile.not-valid-multi-character' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-1" appearance="outline">
<mat-label translate>device-profile.attributes-subscribe-topic-filter</mat-label>
<input matInput required formControlName="deviceAttributesSubscribeTopic" type="text">
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesSubscribeTopic').hasError('required')) {
<mat-error>{{ 'device-profile.attributes-subscribe-topic-filter-required' | translate }}</mat-error>
}
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesSubscribeTopic').hasError('invalidSingleTopicCharacter')) {
<mat-error>{{ 'device-profile.not-valid-single-character' | translate }}</mat-error>
}
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('deviceAttributesSubscribeTopic').hasError('invalidMultiTopicCharacter')) {
<mat-error>{{ 'device-profile.not-valid-multi-character' | translate }}</mat-error>
}
</mat-form-field>
</div>
@if (mqttDeviceProfileTransportConfigurationFormGroup.hasError('unique')) {
<mat-error>{{ 'device-profile.mqtt-device-topic-filters-unique' | translate }}</mat-error>
}
<div class="tb-hint" [innerHTML]="'device-profile.support-level-wildcards' | translate"></div>
<div class="tb-hint" [innerHTML]="'device-profile.single-level-wildcards-hint' | translate"></div>
<div class="tb-hint" [innerHTML]="'device-profile.multi-level-wildcards-hint' | translate"></div>
</div>
</fieldset>
</section>
<div style="padding-bottom: 20px">
<mat-checkbox formControlName="sendAckOnValidationException">
{{ 'device-profile.mqtt-send-ack-on-validation-exception' | translate }}
</mat-checkbox>
<div class="tb-hint" style="max-width: 800px" innerHTML="{{ 'device-profile.mqtt-send-ack-on-validation-exception-hint' | translate }}"></div>
</div>
</form>
<section formGroupName="transportPayloadTypeConfiguration">
<fieldset class="fields-group">
<legend class="group-title" translate>device-profile.mqtt-device-payload-type</legend>
<div class="flex flex-col gap-2">
<mat-form-field class="mat-block" appearance="outline">
<mat-select formControlName="transportPayloadType" required>
@for (type of transportPayloadTypes; track type) {
<mat-option [value]="type">
{{ getTransportPayloadType(type) | translate }}
</mat-option>
}
</mat-select>
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType').hasError('required')) {
<mat-error>{{ 'device-profile.mqtt-payload-type-required' | translate }}</mat-error>
}
</mat-form-field>
@if (protoPayloadType) {
<div style="padding-bottom: 20px">
<mat-checkbox formControlName="enableCompatibilityWithJsonPayloadFormat">
{{ 'device-profile.mqtt-enable-compatibility-with-json-payload-format' | translate }}
</mat-checkbox>
<div class="tb-hint" [innerHTML]="'device-profile.mqtt-enable-compatibility-with-json-payload-format-hint' | translate"></div>
@if (compatibilityWithJsonPayloadFormatEnabled) {
<mat-checkbox formControlName="useJsonPayloadFormatForDefaultDownlinkTopics">
{{ 'device-profile.mqtt-use-json-format-for-default-downlink-topics' | translate }}
</mat-checkbox>
<div class="tb-hint" [innerHTML]="'device-profile.mqtt-use-json-format-for-default-downlink-topics-hint' | translate"></div>
}
</div>
<div class="flex flex-col">
<tb-protobuf-content
class="flex-1"
formControlName="deviceTelemetryProtoSchema"
label="{{ 'device-profile.telemetry-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.deviceTelemetryProtoSchema').hasError('required')) {
<mat-error>{{ 'device-profile.telemetry-proto-schema-required' | translate }}</mat-error>
}
<tb-protobuf-content
class="flex-1"
formControlName="deviceAttributesProtoSchema"
label="{{ 'device-profile.attributes-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.deviceAttributesProtoSchema').hasError('required')) {
<mat-error>{{ 'device-profile.attributes-proto-schema-required' | translate }}</mat-error>
}
<tb-protobuf-content
class="flex-1"
formControlName="deviceRpcRequestProtoSchema"
label="{{ 'device-profile.rpc-request-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.deviceRpcRequestProtoSchema').hasError('required')) {
<mat-error>{{ 'device-profile.rpc-request-proto-schema-required' | translate }}</mat-error>
}
<tb-protobuf-content
class="flex-1"
formControlName="deviceRpcResponseProtoSchema"
label="{{ 'device-profile.rpc-response-proto-schema' | translate }}"
[fillHeight]="true">
</tb-protobuf-content>
@if (mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.deviceRpcResponseProtoSchema').hasError('required')) {
<mat-error>{{ 'device-profile.rpc-response-proto-schema-required' | translate }}</mat-error>
}
</div>
}
</div>
</fieldset>
</section>
<div style="padding-bottom: 20px">
<mat-checkbox formControlName="sendAckOnValidationException">
{{ 'device-profile.mqtt-send-ack-on-validation-exception' | translate }}
</mat-checkbox>
<div class="tb-hint" style="max-width: 800px" [innerHTML]="'device-profile.mqtt-send-ack-on-validation-exception-hint' | translate"></div>
</div>
</form>
}

4
ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts

@ -252,4 +252,8 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
}
return null;
}
getTransportPayloadType(type: string): string {
return this.transportPayloadTypeTranslations.get(type as TransportPayloadType);
}
}

109
ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.html

@ -16,57 +16,66 @@
-->
<div class="flex flex-col">
<div *ngFor="let deviceProfileCommunication of communicationConfigFormArray.controls; let $index = index;
last as isLast;"
class="scope-row flex flex-row items-center justify-start gap-2" [formGroup]="deviceProfileCommunication">
<div class="communication-config">
<mat-form-field class="spec mat-block" floatLabel="always" hideRequiredMarker>
<mat-label translate>device-profile.snmp.scope</mat-label>
<mat-select formControlName="spec" required>
<mat-option *ngFor="let snmpSpecType of snmpSpecTypes" [value]="snmpSpecType"
[disabled]="isDisabledSeverity(snmpSpecType, $index)">
{{ snmpSpecTypeTranslationMap.get(snmpSpecType) }}
</mat-option>
</mat-select>
<mat-error *ngIf="deviceProfileCommunication.get('spec').hasError('required')">
{{ 'device-profile.snmp.scope-required' | translate }}
</mat-error>
</mat-form-field>
<mat-divider vertical></mat-divider>
<section class="flex flex-1 flex-col">
<mat-form-field *ngIf="isShowFrequency(deviceProfileCommunication.get('spec').value)">
<mat-label translate>device-profile.snmp.querying-frequency</mat-label>
<input matInput formControlName="queryingFrequencyMs" type="number" min="0" required/>
<mat-error *ngIf="deviceProfileCommunication.get('queryingFrequencyMs').hasError('required')">
{{ 'device-profile.snmp.querying-frequency-required' | translate }}
</mat-error>
<mat-error *ngIf="deviceProfileCommunication.get('queryingFrequencyMs').hasError('pattern') ||
deviceProfileCommunication.get('queryingFrequencyMs').hasError('min')">
{{ 'device-profile.snmp.querying-frequency-invalid-format' | translate }}
</mat-error>
@for (deviceProfileCommunication of communicationConfigFormArray.controls; track $index) {
<div class="scope-row flex flex-row items-center justify-start gap-2" [formGroup]="deviceProfileCommunication">
<div class="communication-config">
<mat-form-field class="spec mat-block" appearance="outline" floatLabel="always" hideRequiredMarker>
<mat-label translate>device-profile.snmp.scope</mat-label>
<mat-select formControlName="spec" required>
@for (snmpSpecType of snmpSpecTypes; track snmpSpecType) {
<mat-option [value]="snmpSpecType"
[disabled]="isDisabledSeverity(snmpSpecType, $index)">
{{ snmpSpecTypeTranslationMap.get(snmpSpecType) }}
</mat-option>
}
</mat-select>
@if (deviceProfileCommunication.get('spec').hasError('required')) {
<mat-error>{{ 'device-profile.snmp.scope-required' | translate }}</mat-error>
}
</mat-form-field>
<tb-snmp-device-profile-mapping formControlName="mappings">
</tb-snmp-device-profile-mapping>
</section>
<mat-divider vertical></mat-divider>
<section class="flex flex-1 flex-col">
@if (isShowFrequency(deviceProfileCommunication.get('spec').value)) {
<mat-form-field appearance="outline">
<mat-label translate>device-profile.snmp.querying-frequency</mat-label>
<input matInput formControlName="queryingFrequencyMs" type="number" min="0" required/>
@if (deviceProfileCommunication.get('queryingFrequencyMs').hasError('required')) {
<mat-error>{{ 'device-profile.snmp.querying-frequency-required' | translate }}</mat-error>
}
@if (deviceProfileCommunication.get('queryingFrequencyMs').hasError('pattern') ||
deviceProfileCommunication.get('queryingFrequencyMs').hasError('min')) {
<mat-error>{{ 'device-profile.snmp.querying-frequency-invalid-format' | translate }}</mat-error>
}
</mat-form-field>
}
<tb-snmp-device-profile-mapping formControlName="mappings">
</tb-snmp-device-profile-mapping>
</section>
</div>
@if (!disabled) {
<button mat-icon-button color="primary" style="min-width: 40px;"
type="button"
(click)="removeCommunicationConfig($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>remove_circle_outline</mat-icon>
</button>
}
</div>
<button *ngIf="!disabled"
mat-icon-button color="primary" style="min-width: 40px;"
type="button"
(click)="removeCommunicationConfig($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>remove_circle_outline</mat-icon>
</button>
</div>
<div *ngIf="!communicationConfigFormArray.controls.length && !disabled">
}
@if (!communicationConfigFormArray.controls.length && !disabled) {
<div>
<span class="tb-prompt required required-text flex items-center justify-center" translate>device-profile.snmp.please-add-communication-config</span>
</div>
<div *ngIf="!disabled && isAddEnabled">
<button mat-stroked-button color="primary"
type="button"
(click)="addCommunicationConfig()">
<mat-icon class="button-icon">add_circle_outline</mat-icon>
{{ 'device-profile.snmp.add-communication-config' | translate }}
</button>
</div>
</div>
}
@if (!disabled && isAddEnabled) {
<div>
<button mat-stroked-button color="primary"
type="button"
(click)="addCommunicationConfig()">
<mat-icon class="button-icon">add_circle_outline</mat-icon>
{{ 'device-profile.snmp.add-communication-config' | translate }}
</button>
</div>
}
</div>

6
ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.ts

@ -24,7 +24,7 @@ import {
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator,
Validators
Validators, FormArray, FormGroup
} from '@angular/forms';
import { SnmpCommunicationConfig, SnmpSpecType, SnmpSpecTypeTranslationMap } from '@shared/models/device.models';
import { Subject } from 'rxjs';
@ -79,8 +79,8 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On
this.destroy$.complete();
}
get communicationConfigFormArray(): UntypedFormArray {
return this.deviceProfileCommunicationConfig.get('communicationConfig') as UntypedFormArray;
get communicationConfigFormArray(): FormArray<FormGroup> {
return this.deviceProfileCommunicationConfig.get('communicationConfig') as FormArray<FormGroup>;
}
registerOnChange(fn: any): void {

32
ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-transport-configuration.component.html

@ -17,27 +17,27 @@
-->
<form [formGroup]="snmpDeviceProfileTransportConfigurationFormGroup" style="padding: 8px 0 16px;">
<section class="flex flex-row gap-2 xs:flex-col">
<mat-form-field class="flex-1">
<mat-form-field class="flex-1" appearance="outline">
<mat-label translate>device-profile.snmp.timeout-ms</mat-label>
<input matInput formControlName="timeoutMs" type="number" min="0" required/>
<mat-error *ngIf="snmpDeviceProfileTransportConfigurationFormGroup.get('timeoutMs').hasError('required')">
{{ 'device-profile.snmp.timeout-ms-required' | translate }}
</mat-error>
<mat-error *ngIf="snmpDeviceProfileTransportConfigurationFormGroup.get('timeoutMs').hasError('pattern') ||
snmpDeviceProfileTransportConfigurationFormGroup.get('timeoutMs').hasError('min')">
{{ 'device-profile.snmp.timeout-ms-invalid-format' | translate }}
</mat-error>
@if (snmpDeviceProfileTransportConfigurationFormGroup.get('timeoutMs').hasError('required')) {
<mat-error>{{ 'device-profile.snmp.timeout-ms-required' | translate }}</mat-error>
}
@if (snmpDeviceProfileTransportConfigurationFormGroup.get('timeoutMs').hasError('pattern') ||
snmpDeviceProfileTransportConfigurationFormGroup.get('timeoutMs').hasError('min')) {
<mat-error>{{ 'device-profile.snmp.timeout-ms-invalid-format' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-1">
<mat-form-field class="flex-1" appearance="outline">
<mat-label translate>device-profile.snmp.retries</mat-label>
<input matInput formControlName="retries" type="number" min="0" required/>
<mat-error *ngIf="snmpDeviceProfileTransportConfigurationFormGroup.get('retries').hasError('required')">
{{ 'device-profile.snmp.retries-required' | translate }}
</mat-error>
<mat-error *ngIf="snmpDeviceProfileTransportConfigurationFormGroup.get('retries').hasError('pattern') ||
snmpDeviceProfileTransportConfigurationFormGroup.get('retries').hasError('min')">
{{ 'device-profile.snmp.retries-invalid-format' | translate }}
</mat-error>
@if (snmpDeviceProfileTransportConfigurationFormGroup.get('retries').hasError('required')) {
<mat-error>{{ 'device-profile.snmp.retries-required' | translate }}</mat-error>
}
@if (snmpDeviceProfileTransportConfigurationFormGroup.get('retries').hasError('pattern') ||
snmpDeviceProfileTransportConfigurationFormGroup.get('retries').hasError('min')) {
<mat-error>{{ 'device-profile.snmp.retries-invalid-format' | translate }}</mat-error>
}
</mat-form-field>
</section>
<div class="tb-small" style="padding-bottom: 8px" translate>device-profile.snmp.communication-configs</div>

94
ui-ngx/src/app/modules/home/components/resources/resources-library.component.html

@ -15,57 +15,67 @@
limitations under the License.
-->
<div class="tb-details-buttons xs:flex xs:flex-col" *ngIf="!standalone">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[class.!hidden]="isEdit || isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'downloadResource')"
[class.!hidden]="isEdit">
{{ 'resource.download' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'deleteResource')"
[class.!hidden]="hideDelete() || isEdit">
{{ 'resource.delete' | translate }}
</button>
<div class="flex flex-row xs:flex-col">
<button mat-raised-button
ngxClipboard
(cbOnSuccess)="onResourceIdCopied()"
[cbContent]="entity?.id?.id"
@if (!standalone) {
<div class="tb-details-buttons xs:flex xs:flex-col">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[class.!hidden]="isEdit || isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'downloadResource')"
[class.!hidden]="isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>resource.copyId</span>
{{ 'resource.download' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'deleteResource')"
[class.!hidden]="hideDelete() || isEdit">
{{ 'resource.delete' | translate }}
</button>
<div class="flex flex-row xs:flex-col">
<button mat-raised-button
ngxClipboard
(cbOnSuccess)="onResourceIdCopied()"
[cbContent]="entity?.id?.id"
[class.!hidden]="isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>resource.copyId</span>
</button>
</div>
</div>
</div>
}
<div class="mat-padding flex flex-col">
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>resource.resource-type</mat-label>
<mat-select formControlName="resourceType" required>
<mat-option *ngFor="let resourceType of resourceTypes" [value]="resourceType">
{{ resourceTypesTranslationMap.get(resourceType) | translate }}
</mat-option>
@for (resourceType of resourceTypes ; track resourceType) {
<mat-option [value]="resourceType">
{{ resourceTypesTranslationMap.get(resourceType) | translate }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block" *ngIf="entityForm.get('resourceType').value !== resourceType.LWM2M_MODEL || !isAdd">
<mat-label translate>resource.title</mat-label>
<input matInput formControlName="title" required>
<mat-error *ngIf="entityForm.get('title').hasError('required')">
{{ 'resource.title-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
{{ 'resource.title-max-length' | translate }}
</mat-error>
</mat-form-field>
@if (entityForm.get('resourceType').value !== resourceType.LWM2M_MODEL || !isAdd) {
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>resource.title</mat-label>
<input matInput formControlName="title" required>
@if (entityForm.get('title').hasError('required')) {
<mat-error>
{{ 'resource.title-required' | translate }}
</mat-error>
}
@if (entityForm.get('title').hasError('maxlength')) {
<mat-error>
{{ 'resource.title-max-length' | translate }}
</mat-error>
}
</mat-form-field>
}
@if (isAdd || ((isAdd || isEdit) && entityForm.get('resourceType').value === resourceType.GENERAL)) {
<tb-file-input formControlName="data"
required
@ -83,7 +93,7 @@
</tb-file-input>
} @else {
<div class="flex flex-row xs:flex-col sm:gap-2 md:flex-col gt-md:gap-2">
<mat-form-field class="flex-1">
<mat-form-field class="flex-1" appearance="outline">
<mat-label translate>resource.file-name</mat-label>
<input matInput formControlName="fileName" type="text">
</mat-form-field>

2
ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<mat-form-field [formGroup]="selectRuleChainFormGroup" class="mat-block">
<mat-form-field [formGroup]="selectRuleChainFormGroup" class="mat-block" [appearance]="appearance">
<mat-label>{{ labelText | translate }}</mat-label>
<input matInput type="text"
#ruleChainInput

4
ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts

@ -31,6 +31,7 @@ import { RuleChainService } from '@core/http/rule-chain.service';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { RuleChainType } from '@app/shared/models/rule-chain.models';
import { getEntityDetailsPageURL } from '@core/utils';
import { MatFormFieldAppearance } from '@angular/material/form-field';
@Component({
selector: 'tb-rule-chain-autocomplete',
@ -58,6 +59,9 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI
@Input()
ruleChainType: RuleChainType = RuleChainType.CORE;
@Input()
appearance: MatFormFieldAppearance = 'fill';
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;

84
ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html

@ -26,20 +26,26 @@
<span class="flex-1"></span>
<div tb-help="repositorySettings"></div>
</mat-card-header>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="!hideLoadingBar && isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="hideLoadingBar || (isLoading$ | async) === false"></div>
@if (!hideLoadingBar && isLoading$ | async) {
<mat-progress-bar color="warn" mode="indeterminate">
</mat-progress-bar>
}
@if (hideLoadingBar || (isLoading$ | async) === false) {
<div style="height: 4px;"></div>
}
<mat-card-content style="padding-top: 16px;">
<form [formGroup]="repositorySettingsForm" #formDirective="ngForm" (ngSubmit)="save()">
<fieldset [disabled]="isLoading$ | async">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>admin.repository-url</mat-label>
<input matInput required formControlName="repositoryUri">
<mat-error translate *ngIf="repositorySettingsForm.get('repositoryUri').hasError('required')">
admin.repository-url-required
</mat-error>
@if (repositorySettingsForm.get('repositoryUri').hasError('required')) {
<mat-error translate>
admin.repository-url-required
</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>admin.default-branch</mat-label>
<input matInput formControlName="defaultBranch">
</mat-form-field>
@ -53,31 +59,37 @@
</div>
<fieldset [disabled]="isLoading$ | async" class="fields-group">
<legend class="group-title" translate>admin.authentication-settings</legend>
<mat-form-field class="mat-block flex-1">
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>admin.auth-method</mat-label>
<mat-select required formControlName="authMethod">
<mat-option *ngFor="let method of repositoryAuthMethods" [value]="method">
{{repositoryAuthMethodTranslations.get(method) | translate}}
</mat-option>
@for ( method of repositoryAuthMethods; track method) {
<mat-option [value]="method">
{{repositoryAuthMethodTranslations.get(method) | translate}}
</mat-option>
}
</mat-select>
</mat-form-field>
<section [class.!hidden]="repositorySettingsForm.get('authMethod').value !== repositoryAuthMethod.USERNAME_PASSWORD" class="flex flex-col">
<mat-form-field class="mat-block flex-1">
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>common.username</mat-label>
<input matInput formControlName="username" placeholder="{{ 'common.enter-username' | translate }}"
autocomplete="new-username"/>
</mat-form-field>
<mat-checkbox *ngIf="showChangePassword" (change)="changePasswordChanged()"
[(ngModel)]="changePassword" [ngModelOptions]="{ standalone: true }">
{{ 'admin.change-password-access-token' | translate }}
</mat-checkbox>
<mat-form-field class="mat-block" *ngIf="changePassword || !showChangePassword">
<mat-label translate>admin.password-access-token</mat-label>
<input matInput formControlName="password" type="password"
placeholder="{{ 'common.enter-password' | translate }}" autocomplete="new-password"/>
<tb-toggle-password matSuffix></tb-toggle-password>
<mat-hint [innerHTML] = "'admin.auth-method-username-password-hint' | translate"></mat-hint>
</mat-form-field>
@if (showChangePassword) {
<mat-checkbox (change)="changePasswordChanged()"
[(ngModel)]="changePassword" [ngModelOptions]="{ standalone: true }">
{{ 'admin.change-password-access-token' | translate }}
</mat-checkbox>
}
@if (changePassword || !showChangePassword) {
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>admin.password-access-token</mat-label>
<input matInput formControlName="password" type="password"
placeholder="{{ 'common.enter-password' | translate }}" autocomplete="new-password"/>
<tb-toggle-password matSuffix></tb-toggle-password>
<mat-hint [innerHTML] = "'admin.auth-method-username-password-hint' | translate"></mat-hint>
</mat-form-field>
}
</section>
<section [class.!hidden]="repositorySettingsForm.get('authMethod').value !== repositoryAuthMethod.PRIVATE_KEY" class="flex flex-col">
<tb-file-input [existingFileName]="repositorySettingsForm.get('privateKeyFileName').value"
@ -87,16 +99,20 @@
[label]="'admin.private-key' | translate"
(fileNameChanged)="repositorySettingsForm.get('privateKeyFileName').patchValue($event)">
</tb-file-input>
<mat-checkbox *ngIf="showChangePrivateKeyPassword" (change)="changePrivateKeyPasswordChanged()"
[(ngModel)]="changePrivateKeyPassword" [ngModelOptions]="{ standalone: true }">
{{ 'admin.change-passphrase' | translate }}
</mat-checkbox>
<mat-form-field class="mat-block" *ngIf="changePrivateKeyPassword || !showChangePrivateKeyPassword">
<mat-label translate>admin.passphrase</mat-label>
<input matInput formControlName="privateKeyPassword" type="password"
placeholder="{{ 'admin.enter-passphrase' | translate }}" autocomplete="new-password"/>
<tb-toggle-password matSuffix></tb-toggle-password>
</mat-form-field>
@if (showChangePrivateKeyPassword) {
<mat-checkbox (change)="changePrivateKeyPasswordChanged()"
[(ngModel)]="changePrivateKeyPassword" [ngModelOptions]="{ standalone: true }">
{{ 'admin.change-passphrase' | translate }}
</mat-checkbox>
}
@if (changePrivateKeyPassword || !showChangePrivateKeyPassword) {
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>admin.passphrase</mat-label>
<input matInput formControlName="privateKeyPassword" type="password"
placeholder="{{ 'admin.enter-passphrase' | translate }}" autocomplete="new-password"/>
<tb-toggle-password matSuffix></tb-toggle-password>
</mat-form-field>
}
</section>
</fieldset>
<div class="flex flex-row items-center justify-end gap-4 xs:flex-col xs:items-stretch xs:justify-end">

65
ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html

@ -25,8 +25,11 @@
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
@if (isLoading$ | async) {
<mat-progress-bar color="warn" mode="indeterminate"></mat-progress-bar>
} @else {
<div style="height: 4px;"></div>
}
<div mat-dialog-content>
<mat-horizontal-stepper #addDeviceWizardStepper
[linear]="true"
@ -38,29 +41,30 @@
</ng-template>
<mat-step [stepControl]="deviceWizardFormGroup">
<form [formGroup]="deviceWizardFormGroup" style="padding-bottom: 16px;">
<ng-template matStepLabel>{{ 'device.wizard.device-details' | translate}}</ng-template>
<fieldset [disabled]="isLoading$ | async">
<mat-form-field class="mat-block">
<ng-template matStepLabel>{{ 'device.wizard.device-details' | translate }}</ng-template>
<fieldset [disabled]="isLoading$ | async" class="flex flex-col gap-1">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('required')">
{{ 'device.name-required' | translate }}
</mat-error>
<mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('maxlength')">
{{ 'device.name-max-length' | translate }}
</mat-error>
@if (deviceWizardFormGroup.get('name').hasError('required')) {
<mat-error>{{ 'device.name-required' | translate }}</mat-error>
}
@if (deviceWizardFormGroup.get('name').hasError('maxlength')) {
<mat-error>{{ 'device.name-max-length' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.label</mat-label>
<input matInput formControlName="label">
<mat-error *ngIf="deviceWizardFormGroup.get('label').hasError('maxlength')">
{{ 'device.label-max-length' | translate }}
</mat-error>
@if (deviceWizardFormGroup.get('label').hasError('maxlength')) {
<mat-error>{{ 'device.label-max-length' | translate }}</mat-error>
}
</mat-form-field>
<tb-device-profile-autocomplete
required
addNewProfile
selectDefaultProfile
appearance="outline"
formControlName="deviceProfileId"
(deviceProfileChanged)="deviceProfileChanged($event)">
</tb-device-profile-autocomplete>
@ -68,18 +72,20 @@
<mat-slide-toggle formControlName="gateway" style="min-width: 165px">
{{ 'device.is-gateway' | translate }}
</mat-slide-toggle>
<mat-slide-toggle *ngIf="deviceWizardFormGroup.get('gateway').value"
formControlName="overwriteActivityTime">
{{ 'device.overwrite-activity-time' | translate }}
</mat-slide-toggle>
@if (deviceWizardFormGroup.get('gateway').value) {
<mat-slide-toggle formControlName="overwriteActivityTime">
{{ 'device.overwrite-activity-time' | translate }}
</mat-slide-toggle>
}
</div>
<tb-entity-autocomplete
useFullEntityId
formControlName="customerId"
labelText="device.assign-to-customer"
appearance="outline"
[entityType]="entityType.CUSTOMER">
</tb-entity-autocomplete>
<mat-form-field class="mat-block" subscriptSizing="dynamic">
<mat-form-field class="mat-block" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>device.description</mat-label>
<textarea matInput formControlName="description" rows="1"></textarea>
</mat-form-field>
@ -100,15 +106,18 @@
</div>
<div mat-dialog-actions class="tb-dialog-actions">
<div class="dialog-actions-row">
<button mat-stroked-button *ngIf="selectedIndex > 0"
[disabled]="(isLoading$ | async)"
(click)="previousStep()">{{ 'action.back' | translate }}</button>
@if (selectedIndex > 0) {
<button mat-stroked-button
[disabled]="(isLoading$ | async)"
(click)="previousStep()">{{ 'action.back' | translate }}</button>
}
<span class="flex-1"></span>
<button mat-stroked-button
color="primary"
*ngIf="showNext"
[disabled]="(isLoading$ | async)"
(click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(selectedIndex+1) | translate)} }}</button>
@if (showNext) {
<button mat-stroked-button
color="primary"
[disabled]="(isLoading$ | async)"
(click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(selectedIndex+1) | translate)} }}</button>
}
</div>
<mat-divider style="width: 100%"></mat-divider>
<div class="dialog-actions-row">

1
ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss

@ -74,6 +74,7 @@
max-height: 100%;
overflow-y: auto;
scrollbar-gutter: stable;
padding-top: 4px;
}
}
}

125
ui-ngx/src/app/modules/home/pages/admin/resource/js-resource.component.html

@ -48,71 +48,82 @@
<div class="mat-padding flex flex-col">
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>javascript.javascript-type</mat-label>
<mat-select formControlName="resourceSubType" required>
<mat-option *ngFor="let resourceSubType of jsResourceSubTypes" [value]="resourceSubType">
{{ ResourceSubTypeTranslationMap.get(resourceSubType) | translate }}
</mat-option>
@for (resourceSubType of jsResourceSubTypes; track resourceSubType) {
<mat-option [value]="resourceSubType">
{{ ResourceSubTypeTranslationMap.get(resourceSubType) | translate }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>resource.title</mat-label>
<input matInput formControlName="title" required>
<mat-error *ngIf="entityForm.get('title').hasError('required')">
{{ 'resource.title-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
{{ 'resource.title-max-length' | translate }}
</mat-error>
@if (entityForm.get('title').hasError('required')) {
<mat-error>
{{ 'resource.title-required' | translate }}
</mat-error>
}
@if (entityForm.get('title').hasError('maxlength')) {
<mat-error>
{{ 'resource.title-max-length' | translate }}
</mat-error>
}
</mat-form-field>
<tb-file-input *ngIf="(isAdd || isEdit) && entityForm.get('resourceSubType').value === ResourceSubType.EXTENSION"
formControlName="data"
[required]="isAdd"
label="{{ 'javascript.resource-file' | translate }}"
[workFromFileObj]="true"
[maxSizeByte]="maxResourceSize"
[allowedExtensions]="getAllowedExtensions()"
[contentConvertFunction]="convertToBase64File"
[accept]="getAcceptType()"
dropLabel="{{'javascript.drop-resource-file-or' | translate}}"
[existingFileName]="entityForm.get('fileName')?.value"
(fileNameChanged)="entityForm?.get('fileName').patchValue($event)">
</tb-file-input>
<tb-js-func *ngIf="entityForm.get('resourceSubType').value === ResourceSubType.MODULE"
[helpPopupStyle]="{ width: '1000px' }"
helpId="resource/js-resource-module_fn"
formControlName="content"
required
hideBrackets
hideLabel
minHeight="300px">
<div toolbarStartButton
class="flex flex-row gap-4">
<label class="tb-title no-padding tb-required"
[class.tb-error]="entityForm.get('content').invalid && entityForm.get('content').touched"
style="font-size: 16px;">
{{ 'javascript.module-script' | translate }}</label>
<tb-file-input *ngIf="(isAdd || isEdit)"
asButton
uploadButtonText="{{ 'javascript.upload-from-file' | translate }}"
uploadButtonClass="tb-ignore-browse-file-button-style"
[maxSizeByte]="maxResourceSize"
[allowedExtensions]="getAllowedExtensions()"
[accept]="getAcceptType()"
[ngModel]=""
[ngModelOptions]="{ standalone: true }"
(ngModelChange)="uploadContentFromFile($event)"
(fileNameChanged)="entityForm?.get('fileName').patchValue($event)">
</tb-file-input>
@if ((isAdd || isEdit) && entityForm.get('resourceSubType').value === ResourceSubType.EXTENSION) {
<tb-file-input formControlName="data"
[required]="isAdd"
label="{{ 'javascript.resource-file' | translate }}"
[workFromFileObj]="true"
[maxSizeByte]="maxResourceSize"
[allowedExtensions]="getAllowedExtensions()"
[contentConvertFunction]="convertToBase64File"
[accept]="getAcceptType()"
dropLabel="{{'javascript.drop-resource-file-or' | translate}}"
[existingFileName]="entityForm.get('fileName')?.value"
(fileNameChanged)="entityForm?.get('fileName').patchValue($event)">
</tb-file-input>
}
@if (entityForm.get('resourceSubType').value === ResourceSubType.MODULE) {
<tb-js-func [helpPopupStyle]="{ width: '1000px' }"
helpId="resource/js-resource-module_fn"
formControlName="content"
required
hideBrackets
hideLabel
minHeight="300px">
<div toolbarStartButton
class="flex flex-row gap-4">
<label class="tb-title no-padding tb-required"
[class.tb-error]="entityForm.get('content').invalid && entityForm.get('content').touched"
style="font-size: 16px;">
{{ 'javascript.module-script' | translate }}</label>
@if ((isAdd || isEdit)) {
<tb-file-input asButton
uploadButtonText="{{ 'javascript.upload-from-file' | translate }}"
uploadButtonClass="tb-ignore-browse-file-button-style"
[maxSizeByte]="maxResourceSize"
[allowedExtensions]="getAllowedExtensions()"
[accept]="getAcceptType()"
[ngModel]=""
[ngModelOptions]="{ standalone: true }"
(ngModelChange)="uploadContentFromFile($event)"
(fileNameChanged)="entityForm?.get('fileName').patchValue($event)">
</tb-file-input>
}
</div>
</tb-js-func>
}
@if (!isAdd && !isEdit && entityForm.get('resourceSubType').value === ResourceSubType.EXTENSION) {
<div class="flex flex-row xs:flex-col sm:gap-2 md:flex-col gt-md:gap-2">
<mat-form-field class="flex-1">
<mat-label translate>resource.file-name</mat-label>
<input matInput formControlName="fileName" type="text">
</mat-form-field>
</div>
</tb-js-func>
<div *ngIf="!isAdd && !isEdit && entityForm.get('resourceSubType').value === ResourceSubType.EXTENSION" class="flex flex-row xs:flex-col sm:gap-2 md:flex-col gt-md:gap-2">
<mat-form-field class="flex-1">
<mat-label translate>resource.file-name</mat-label>
<input matInput formControlName="fileName" type="text">
</mat-form-field>
</div>
}
</fieldset>
</form>
</div>

2
ui-ngx/src/app/modules/home/pages/admin/resource/js-resource.component.ts

@ -173,4 +173,6 @@ export class JsResourceComponent extends EntityComponent<Resource> implements On
this.entityForm.get('content').enable({ emitEvent: false });
}
}
protected readonly resourceType = ResourceType;
}

45
ui-ngx/src/app/modules/home/pages/asset/asset.component.html

@ -64,7 +64,7 @@
</div>
</div>
<div class="mat-padding flex flex-col">
<mat-form-field class="mat-block"
<mat-form-field class="mat-block" appearance="outline"
[class.!hidden]="isEdit || !isAssignedToCustomer(entity)
|| entity?.customerIsPublic || assetScope !== 'tenant'">
<mat-label translate>asset.assignedToCustomer</mat-label>
@ -75,40 +75,43 @@
{{ 'asset.asset-public' | translate }}
</div>
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>asset.name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="entityForm.get('name').hasError('required')">
{{ 'asset.name-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
{{ 'asset.name-max-length' | translate }}
</mat-error>
@if (entityForm.get('name').hasError('required')) {
<mat-error>{{ 'asset.name-required' | translate }}</mat-error>
}
@if (entityForm.get('name').hasError('maxlength')) {
<mat-error>{{ 'asset.name-max-length' | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>asset.label</mat-label>
<input matInput formControlName="label">
<mat-error *ngIf="entityForm.get('label').hasError('maxlength')">
{{ 'asset.label-max-length' | translate }}
</mat-error>
@if (entityForm.get('label').hasError('maxlength')) {
<mat-error>{{ 'asset.label-max-length' | translate }}</mat-error>
}
</mat-form-field>
<tb-asset-profile-autocomplete
[selectDefaultProfile]="isAdd"
required
formControlName="assetProfileId"
appearance="outline"
[showDetailsPageLink]="true"
(assetProfileUpdated)="onAssetProfileUpdated()">
</tb-asset-profile-autocomplete>
<tb-entity-autocomplete
*ngIf="isAdd"
useFullEntityId
formControlName="customerId"
labelText="asset.assign-to-customer"
[entityType]="entityType.CUSTOMER">
</tb-entity-autocomplete>
@if (isAdd) {
<tb-entity-autocomplete
useFullEntityId
formControlName="customerId"
labelText="asset.assign-to-customer"
appearance="outline"
[entityType]="entityType.CUSTOMER">
</tb-entity-autocomplete>
}
<div formGroupName="additionalInfo">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>asset.description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>

64
ui-ngx/src/app/modules/home/pages/customer/customer.component.html

@ -46,13 +46,14 @@
[class.!hidden]="isEdit">
{{'customer.manage-dashboards' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'manageEdges')"
[class.!hidden]="isEdit"
*ngIf="edgesSupportEnabled()">
{{'customer.manage-edges' | translate }}
</button>
@if (edgesSupportEnabled()) {
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'manageEdges')"
[class.!hidden]="isEdit">
{{'customer.manage-edges' | translate }}
</button>
}
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
@ -72,35 +73,38 @@
</div>
<div class="mat-padding flex flex-col">
<form [formGroup]="entityForm">
<fieldset [class.!hidden]="isPublic" [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<fieldset [class.!hidden]="isPublic" [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>customer.title</mat-label>
<input matInput formControlName="title" required/>
<mat-error *ngIf="entityForm.get('title').hasError('required')">
{{ 'customer.title-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
{{ 'customer.title-max-length' | translate }}
</mat-error>
@if (entityForm.get('title').hasError('required')) {
<mat-error>{{ 'customer.title-required' | translate }}</mat-error>
}
@if (entityForm.get('title').hasError('maxlength')) {
<mat-error>{{ 'customer.title-max-length' | translate }}</mat-error>
}
</mat-form-field>
<tb-contact [parentForm]="entityForm" [isEdit]="isEdit"></tb-contact>
<div formGroupName="additionalInfo" class="flex flex-col">
<section class="tb-default-dashboard flex flex-1 flex-col" *ngIf="entity?.id">
<section class="flex flex-1 flex-col gt-sm:flex-row">
<tb-dashboard-autocomplete
class="flex-1"
label="{{ 'dashboard.home-dashboard' | translate }}"
formControlName="homeDashboardId"
[dashboardsScope]="'customer'"
[customerId]="entity?.id.id"
[selectFirstDashboard]="false"
></tb-dashboard-autocomplete>
<mat-checkbox class="flex-1" formControlName="homeDashboardHideToolbar">
{{ 'dashboard.home-dashboard-hide-toolbar' | translate }}
</mat-checkbox>
@if (entity?.id) {
<section class="tb-default-dashboard flex flex-1 flex-col">
<section class="flex flex-1 flex-col gt-sm:flex-row">
<tb-dashboard-autocomplete
class="flex-1"
label="{{ 'dashboard.home-dashboard' | translate }}"
formControlName="homeDashboardId"
appearance="outline"
[dashboardsScope]="'customer'"
[customerId]="entity?.id.id"
[selectFirstDashboard]="false">
</tb-dashboard-autocomplete>
<mat-checkbox class="flex-1" formControlName="homeDashboardHideToolbar">
{{ 'dashboard.home-dashboard-hide-toolbar' | translate }}
</mat-checkbox>
</section>
</section>
</section>
<mat-form-field class="mat-block">
}
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>customer.description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>

38
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.html

@ -83,7 +83,7 @@
</div>
</div>
<div class="mat-padding flex flex-col">
<mat-form-field class="mat-block"
<mat-form-field class="mat-block" appearance="outline"
[class.!hidden]="isEdit || !assignedCustomersText?.length
|| dashboardScope !== 'tenant'">
<mat-label translate>dashboard.assignedToCustomers</mat-label>
@ -98,7 +98,7 @@
shareHashTags="thingsboard, iot">
</tb-social-share-panel>
<div class="flex flex-row">
<mat-form-field class="mat-block flex-1">
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>dashboard.public-link</mat-label>
<input matInput disabled [ngModel]="publicLink">
</mat-form-field>
@ -113,29 +113,31 @@
</div>
</div>
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>dashboard.title</mat-label>
<input matInput formControlName="title" required>
<mat-error *ngIf="entityForm.get('title').hasError('required')">
{{ 'dashboard.title-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
{{ 'dashboard.title-max-length' | translate }}
</mat-error>
@if (entityForm.get('title').hasError('required')) {
<mat-error>{{ 'dashboard.title-required' | translate }}</mat-error>
}
@if (entityForm.get('title').hasError('maxlength')) {
<mat-error>{{ 'dashboard.title-max-length' | translate }}</mat-error>
}
</mat-form-field>
<tb-entity-list
*ngIf="isAdd"
formControlName="assignedCustomerIds"
labelText="{{ 'dashboard.assigned-customers' | translate }}"
[entityType]="entityType.CUSTOMER">
</tb-entity-list>
@if (isAdd) {
<tb-entity-list
formControlName="assignedCustomerIds"
labelText="{{ 'dashboard.assigned-customers' | translate }}"
appearance="outline"
[entityType]="entityType.CUSTOMER">
</tb-entity-list>
}
<div class="tb-form-panel stroked no-gap">
<div class="tb-form-panel-title" translate>dashboard.mobile-app-settings</div>
<mat-slide-toggle formControlName="mobileHide" class="mat-slide margin fixed-title-width">
{{ 'dashboard.mobile-hide' | translate }}
</mat-slide-toggle>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>dashboard.mobile-order</mat-label>
<input matInput formControlName="mobileOrder" type="number" step="1">
</mat-form-field>
@ -145,7 +147,7 @@
</tb-gallery-image-input>
</div>
<div formGroupName="configuration" class="flex flex-col mt-5">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>dashboard.description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>

40
ui-ngx/src/app/modules/home/pages/device/device.component.html

@ -83,7 +83,7 @@
</div>
</div>
<div class="mat-padding flex flex-col">
<mat-form-field class="mat-block"
<mat-form-field class="mat-block" appearance="outline"
[class.!hidden]="isEdit || !isAssignedToCustomer(entity)
|| entity?.customerIsPublic || deviceScope !== 'tenant'">
<mat-label translate>device.assignedToCustomer</mat-label>
@ -94,37 +94,39 @@
{{ 'device.device-public' | translate }}
</div>
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="entityForm.get('name').hasError('required')">
{{ 'device.name-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
{{ 'device.name-max-length' | translate }}
</mat-error>
@if (entityForm.get('name').hasError('required')) {
<mat-error>{{ 'device.name-required' | translate }}</mat-error>
}
@if (entityForm.get('name').hasError('maxlength')) {
<mat-error>{{ 'device.name-max-length' | translate }}</mat-error>
}
</mat-form-field>
<tb-device-profile-autocomplete
[selectDefaultProfile]="isAdd"
required
formControlName="deviceProfileId"
appearance="outline"
[showDetailsPageLink]="true"
(deviceProfileUpdated)="onDeviceProfileUpdated()"
(deviceProfileChanged)="onDeviceProfileChanged($event)">
</tb-device-profile-autocomplete>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.label</mat-label>
<input matInput formControlName="label">
<mat-error *ngIf="entityForm.get('label').hasError('maxlength')">
{{ 'device.label-max-length' | translate }}
</mat-error>
@if (entityForm.get('label').hasError('maxlength')) {
<mat-error>{{ 'device.label-max-length' | translate }}</mat-error>
}
</mat-form-field>
<tb-ota-package-autocomplete
[useFullEntityId]="true"
[showDetailsPageLink]="true"
[type]="otaUpdateType.FIRMWARE"
[deviceProfileId]="entityForm.get('deviceProfileId').value?.id"
appearance="outline"
formControlName="firmwareId">
</tb-ota-package-autocomplete>
<tb-ota-package-autocomplete
@ -132,6 +134,7 @@
[showDetailsPageLink]="true"
[type]="otaUpdateType.SOFTWARE"
[deviceProfileId]="entityForm.get('deviceProfileId').value?.id"
appearance="outline"
formControlName="softwareId">
</tb-ota-package-autocomplete>
<tb-device-data
@ -143,12 +146,13 @@
<mat-slide-toggle formControlName="gateway" style="min-width: 165px">
{{ 'device.is-gateway' | translate }}
</mat-slide-toggle>
<mat-slide-toggle *ngIf="entityForm.get('additionalInfo.gateway').value"
formControlName="overwriteActivityTime">
{{ 'device.overwrite-activity-time' | translate }}
</mat-slide-toggle>
@if (entityForm.get('additionalInfo.gateway').value) {
<mat-slide-toggle formControlName="overwriteActivityTime">
{{ 'device.overwrite-activity-time' | translate }}
</mat-slide-toggle>
}
</div>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>

90
ui-ngx/src/app/modules/home/pages/edge/edge.component.html

@ -114,30 +114,27 @@
</button>
</div>
<div class="flex flex-row xs:flex-col">
<div [ngSwitch]="upgradeAvailable">
<ng-container *ngSwitchCase="false">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'openInstallInstructions')"
[class.!hidden]="isEdit || edgeScope === 'customer_user'">
<mat-icon>info_outline</mat-icon>
<span>{{ 'edge.install-connect-instructions' | translate }}</span>
</button>
</ng-container>
<ng-container *ngSwitchCase="true">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'openUpgradeInstructions')"
[class.!hidden]="isEdit || edgeScope === 'customer_user'">
<mat-icon>info_outline</mat-icon>
<span>{{ 'edge.upgrade-instructions' | translate }}</span>
</button>
</ng-container>
</div>
@if (!upgradeAvailable) {
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'openInstallInstructions')"
[class.!hidden]="isEdit || edgeScope === 'customer_user'">
<mat-icon>info_outline</mat-icon>
<span>{{ 'edge.install-connect-instructions' | translate }}</span>
</button>
} @else {
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'openUpgradeInstructions')"
[class.!hidden]="isEdit || edgeScope === 'customer_user'">
<mat-icon>info_outline</mat-icon>
<span>{{ 'edge.upgrade-instructions' | translate }}</span>
</button>
}
</div>
</div>
<div class="mat-padding flex flex-col">
<mat-form-field class="mat-block"
<mat-form-field class="mat-block" appearance="outline"
[class.!hidden]="isEdit || !isAssignedToCustomer(entity)
|| entity?.customerIsPublic || edgeScope !== 'tenant'">
<mat-label translate>edge.assignedToCustomer</mat-label>
@ -148,55 +145,58 @@
{{ 'edge.edge-public' | translate }}
</div>
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>edge.name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="entityForm.get('name').hasError('required')">
{{ 'edge.name-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
{{ 'edge.name-max-length' | translate }}
</mat-error>
@if (entityForm.get('name').hasError('required')) {
<mat-error>{{ 'edge.name-required' | translate }}</mat-error>
}
@if (entityForm.get('name').hasError('maxlength')) {
<mat-error>{{ 'edge.name-max-length' | translate }}</mat-error>
}
</mat-form-field>
<tb-entity-subtype-autocomplete
formControlName="type"
appearance="outline"
[required]="true"
[entityType]="entityType.EDGE">
</tb-entity-subtype-autocomplete>
</fieldset>
<div class="flex flex-row" [class.!hidden]="edgeScope === 'customer_user'">
<mat-form-field class="mat-block flex-1">
<mat-label translate>edge.edge-key</mat-label>
<input matInput formControlName="routingKey">
</mat-form-field>
<button mat-icon-button type="button" ngxClipboard (cbOnSuccess)="onEdgeInfoCopied('key')"
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>edge.edge-key</mat-label>
<input matInput formControlName="routingKey">
</mat-form-field>
<button mat-icon-button type="button" ngxClipboard
(cbOnSuccess)="onEdgeInfoCopied('key')"
[cbContent]="entityForm?.get('routingKey').value"
matTooltip="{{'edge.copy-edge-key' | translate}}">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
</button>
</div>
<div class="flex flex-row" [class.!hidden]="edgeScope === 'customer_user'">
<mat-form-field class="mat-block flex-1">
<mat-label translate>edge.edge-secret</mat-label>
<input matInput formControlName="secret">
</mat-form-field>
<button mat-icon-button type="button" ngxClipboard (cbOnSuccess)="onEdgeInfoCopied('secret')"
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>edge.edge-secret</mat-label>
<input matInput formControlName="secret">
</mat-form-field>
<button mat-icon-button type="button" ngxClipboard
(cbOnSuccess)="onEdgeInfoCopied('secret')"
[cbContent]="entityForm?.get('secret').value"
matTooltip="{{'edge.copy-edge-secret' | translate}}">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
</button>
</div>
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>edge.label</mat-label>
<input matInput formControlName="label">
<mat-error *ngIf="entityForm.get('label').hasError('maxlength')">
{{ 'edge.label-max-length' | translate }}
</mat-error>
@if (entityForm.get('label').hasError('maxlength')) {
<mat-error>{{ 'edge.label-max-length' | translate }}</mat-error>
}
</mat-form-field>
<div formGroupName="additionalInfo" class="flex flex-col">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>edge.description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>

43
ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html

@ -64,7 +64,7 @@
</div>
</div>
<div class="mat-padding flex flex-col">
<mat-form-field class="mat-block"
<mat-form-field class="mat-block" appearance="outline"
[class.!hidden]="isEdit || !isAssignedToCustomer(entity)
|| entity?.customerIsPublic || entityViewScope !== 'tenant'">
<mat-label translate>entity-view.assignedToCustomer</mat-label>
@ -75,29 +75,30 @@
{{ 'entity-view.entity-view-public' | translate }}
</div>
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>entity-view.name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="entityForm.get('name').hasError('required')">
{{ 'entity-view.name-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
{{ 'entity-view.name-max-length' | translate }}
</mat-error>
@if (entityForm.get('name').hasError('required')) {
<mat-error>{{ 'entity-view.name-required' | translate }}</mat-error>
}
@if (entityForm.get('name').hasError('maxlength')) {
<mat-error>{{ 'entity-view.name-max-length' | translate }}</mat-error>
}
</mat-form-field>
<tb-entity-subtype-autocomplete
formControlName="type"
appearance="outline"
[required]="true"
[entityType]="entityType.ENTITY_VIEW"
>
[entityType]="entityType.ENTITY_VIEW">
</tb-entity-subtype-autocomplete>
<section class="flex flex-col">
<label translate class="tb-title no-padding">entity-view.target-entity</label>
<label translate class="tb-title">entity-view.target-entity</label>
<tb-entity-select class="flex-1"
[required]="true"
[allowedEntityTypes]="allowedEntityTypes"
formControlName="entityId">
formControlName="entityId"
appearance="outline">
</tb-entity-select>
</section>
<div class="mat-accordion-container" formGroupName="keys">
@ -114,21 +115,24 @@
[entityId]="selectedEntityId | async"
formControlName="cs"
keysText="entity-view.client-attributes-placeholder"
[dataKeyType]="dataKeyType.attribute">
[dataKeyType]="dataKeyType.attribute"
appearance="outline">
</tb-entity-keys-list>
<label translate class="tb-title no-padding">entity-view.shared-attributes</label>
<tb-entity-keys-list
[entityId]="selectedEntityId | async"
formControlName="sh"
keysText="entity-view.shared-attributes-placeholder"
[dataKeyType]="dataKeyType.attribute">
[dataKeyType]="dataKeyType.attribute"
appearance="outline">
</tb-entity-keys-list>
<label translate class="tb-title no-padding">entity-view.server-attributes</label>
<tb-entity-keys-list
[entityId]="selectedEntityId | async"
formControlName="ss"
keysText="entity-view.server-attributes-placeholder"
[dataKeyType]="dataKeyType.attribute">
[dataKeyType]="dataKeyType.attribute"
appearance="outline">
</tb-entity-keys-list>
</mat-expansion-panel>
<mat-expansion-panel [expanded]="true">
@ -143,7 +147,8 @@
[entityId]="selectedEntityId | async"
formControlName="timeseries"
keysText="entity-view.timeseries-placeholder"
[dataKeyType]="dataKeyType.timeseries">
[dataKeyType]="dataKeyType.timeseries"
appearance="outline">
</tb-entity-keys-list>
</mat-expansion-panel>
</mat-accordion>
@ -152,14 +157,16 @@
dateText="{{ 'entity-view.start-ts' | translate }}"
[maxDate]="maxStartTimeMs | async"
formControlName="startTimeMs"
appearance="outline"
></tb-datetime>
<tb-datetime class="block"
dateText="{{ 'entity-view.end-ts' | translate }}"
[minDate]="minEndTimeMs | async"
formControlName="endTimeMs"
appearance="outline"
></tb-datetime>
<div formGroupName="additionalInfo">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>entity-view.description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>

150
ui-ngx/src/app/modules/home/pages/mobile/bundes/layout/mobile-layout.component.html

@ -22,38 +22,38 @@
<mat-slide-toggle class="whitespace-nowrap xs:!hidden" [formControl]="showHiddenPages">{{ 'mobile.show-hidden-pages' | translate }}</mat-slide-toggle>
</div>
<span class="flex-1"></span>
<button *ngIf="!readonly" class="tb-mobile-layout-button lt-lg:!hidden"
mat-stroked-button
color="primary"
(click)="hideAll()">
<tb-icon matButtonIcon>visibility_off</tb-icon>
<div tbTruncateWithTooltip>{{ 'mobile.hide-all-pages' | translate }}</div>
</button>
<button *ngIf="!readonly" class="tb-mobile-layout-button lt-lg:!hidden"
mat-stroked-button
color="primary"
(click)="resetToDefault()">
<tb-icon matButtonIcon>mdi:broom</tb-icon>
<div tbTruncateWithTooltip>{{ 'mobile.reset-to-default-pages' | translate }}</div>
</button>
<button *ngIf="!readonly"
type="button"
mat-stroked-button
class="tb-box-button gt-md:!hidden"
matTooltip="{{ 'mobile.hide-all-pages' | translate }}"
matTooltipPosition="above"
(click)="hideAll()">
<tb-icon color="primary" matButtonIcon>visibility_off</tb-icon>
</button>
<button *ngIf="!readonly"
type="button"
mat-stroked-button
class="tb-box-button gt-md:!hidden"
matTooltip="{{ 'mobile.reset-to-default-pages' | translate }}"
matTooltipPosition="above"
(click)="resetToDefault()">
<tb-icon color="primary" matButtonIcon>mdi:broom</tb-icon>
</button>
@if (!readonly) {
<button class="tb-mobile-layout-button lt-lg:!hidden"
mat-stroked-button
color="primary"
(click)="hideAll()">
<tb-icon matButtonIcon>visibility_off</tb-icon>
<div tbTruncateWithTooltip>{{ 'mobile.hide-all-pages' | translate }}</div>
</button>
<button class="tb-mobile-layout-button lt-lg:!hidden"
mat-stroked-button
color="primary"
(click)="resetToDefault()">
<tb-icon matButtonIcon>mdi:broom</tb-icon>
<div tbTruncateWithTooltip>{{ 'mobile.reset-to-default-pages' | translate }}</div>
</button>
<button type="button"
mat-stroked-button
class="tb-box-button gt-md:!hidden"
matTooltip="{{ 'mobile.hide-all-pages' | translate }}"
matTooltipPosition="above"
(click)="hideAll()">
<tb-icon color="primary" matButtonIcon>visibility_off</tb-icon>
</button>
<button type="button"
mat-stroked-button
class="tb-box-button gt-md:!hidden"
matTooltip="{{ 'mobile.reset-to-default-pages' | translate }}"
matTooltipPosition="above"
(click)="resetToDefault()">
<tb-icon color="primary" matButtonIcon>mdi:broom</tb-icon>
</button>
}
<!-- <div tb-help="mobileLayout"></div>-->
</div>
<div #mobilePagesContainer class="tb-drop-list tb-mobile-items-container"
@ -61,45 +61,55 @@
cdkDropListOrientation="vertical"
(cdkDropListDropped)="mobileItemDrop($event)"
[cdkDropListDisabled]="!dragEnabled">
<div class="tb-mobile-item-row-container" *ngFor="let menuItemControl of visibleMobilePagesControls(); trackBy: trackByMenuItem;
let $index = index; last as isLast;">
<div cdkDrag [cdkDragDisabled]="!dragEnabled"
class="tb-draggable tb-mobile-item-row">
<div *ngIf="isCustom(menuItemControl)" class="tb-mobile-item-label">
{{ 'mobile.custom-page' | translate }}
@for (menuItemControl of visibleMobilePagesControls(); track trackByMenuItem($index, menuItemControl);) {
<div class="tb-mobile-item-row-container">
<div cdkDrag [cdkDragDisabled]="!dragEnabled"
class="tb-draggable tb-mobile-item-row">
@if (isCustom(menuItemControl)) {
<div class="tb-mobile-item-label">
{{ 'mobile.custom-page' | translate }}
</div>
}
@if (dragEnabled) {
<button cdkDragHandle
mat-icon-button
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above"
class="tb-mat-40 tb-drag-handle">
<tb-icon>drag_indicator</tb-icon>
</button>
}
<tb-mobile-menu-item-row style="flex: 1; min-width: 0;"
[formControl]="menuItemControl"
[maxIconNameBlockWidth]="maxIconNameBlockWidth"
[hideItems]="hideItems$"
(pageRemoved)="removeCustomPage($index)">
</tb-mobile-menu-item-row>
</div>
<button cdkDragHandle *ngIf="dragEnabled"
mat-icon-button
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above"
class="tb-mat-40 tb-drag-handle">
<tb-icon>drag_indicator</tb-icon>
</button>
<tb-mobile-menu-item-row style="flex: 1; min-width: 0;"
[formControl]="menuItemControl"
[maxIconNameBlockWidth]="maxIconNameBlockWidth"
[hideItems]="hideItems$"
(pageRemoved)="removeCustomPage($index)">
</tb-mobile-menu-item-row>
@if (!$last && !readonly) {
<button (click)="addCustomMobilePage($index)"
class="tb-add-mobile-item-button"
[class.divider]="showMenuDivider($index)"
mat-button
matTooltip="{{ 'mobile.add-specific-page' | translate }}"
matTooltipPosition="above"
color="primary">
<div class="tb-add-mobile-label-container tb-primary-fill">
<mat-icon class="tb-mat-18" color="primary">add</mat-icon>
@if (showMenuDivider($index)) {
<span class="tb-mobile-divider-label">{{ getDividerLabel($index) | translate }}</span>
}
</div>
</button>
}
</div>
<button *ngIf="!isLast && !readonly"
(click)="addCustomMobilePage($index)"
class="tb-add-mobile-item-button"
[class.divider]="showMenuDivider($index)"
mat-button
matTooltip="{{ 'mobile.add-specific-page' | translate }}"
matTooltipPosition="above"
color="primary">
<div class="tb-add-mobile-label-container tb-primary-fill">
<mat-icon class="tb-mat-18" color="primary">add</mat-icon>
<span class="tb-mobile-divider-label" *ngIf="showMenuDivider($index)">{{ getDividerLabel($index) | translate }}</span>
</div>
</button>
</div>
}
</div>
<button *ngIf="!readonly" (click)="addCustomMobilePage()"
mat-button color="primary" class="tb-primary-fill" style="min-height: 52px;">
<mat-icon color="primary">add</mat-icon>
<span translate>mobile.add-specific-page</span>
</button>
@if (!readonly) {
<button (click)="addCustomMobilePage()"
mat-button color="primary" class="tb-primary-fill" style="min-height: 52px;">
<mat-icon color="primary">add</mat-icon>
<span translate>mobile.add-specific-page</span>
</button>
}
</div>

109
ui-ngx/src/app/modules/home/pages/mobile/bundes/layout/mobile-page-item-row.component.html

@ -34,58 +34,67 @@
<div class="tb-mobile-page-item-input-block xs:!hidden">
<mat-form-field class="tb-suffix-absolute flex" appearance="outline" subscriptSizing="dynamic">
<input matInput [required]="isCustomMenuItem" formControlName="label" placeholder="{{ itemNamePlaceholder }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
matTooltip="{{ 'mobile.page-name-required' | translate }}"
*ngIf="mobilePageRowForm.get('label').hasError('required') && mobilePageRowForm.get('label').touched"
class="tb-error">
warning
</mat-icon>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
matTooltip="{{ 'mobile.page-name-cannot-contain-only-spaces' | translate }}"
*ngIf="mobilePageRowForm.get('label').hasError('pattern') && mobilePageRowForm.get('label').touched"
class="tb-error">
warning
</mat-icon>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
matTooltip="{{ 'mobile.page-name-max-length' | translate }}"
*ngIf="mobilePageRowForm.get('label').hasError('maxlength') && mobilePageRowForm.get('label').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
<mat-form-field *ngIf="isCustomMenuItem"
class="tb-mobile-page-item-info flex" appearance="outline" subscriptSizing="dynamic">
<input matInput readonly [placeholder]="itemInfo">
@if (mobilePageRowForm.get('label').hasError('required') && mobilePageRowForm.get('label').touched) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
matTooltip="{{ 'mobile.page-name-required' | translate }}"
class="tb-error">
warning
</mat-icon>
}
@if (mobilePageRowForm.get('label').hasError('pattern') && mobilePageRowForm.get('label').touched) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
matTooltip="{{ 'mobile.page-name-cannot-contain-only-spaces' | translate }}"
class="tb-error">
warning
</mat-icon>
}
@if (mobilePageRowForm.get('label').hasError('maxlength') && mobilePageRowForm.get('label').touched) {
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
matTooltip="{{ 'mobile.page-name-max-length' | translate }}"
class="tb-error">
warning
</mat-icon>
}
</mat-form-field>
@if (isCustomMenuItem) {
<mat-form-field class="tb-mobile-page-item-info flex" appearance="outline" subscriptSizing="dynamic">
<input matInput readonly [placeholder]="itemInfo">
</mat-form-field>
}
</div>
</div>
<button *ngIf="!disabled"
type="button"
mat-icon-button
#editButton
(click)="edit($event, editButton)"
matTooltip="{{ 'mobile.edit-page' | translate }}"
matTooltipPosition="above">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button *ngIf="!disabled && isDefaultMenuItem"
[disabled]="!isCleanupEnabled"
matTooltip="{{ 'mobile.reset-to-page-default' | translate }}"
matTooltipPosition="above"
(click)="cleanup()">
<tb-icon>mdi:broom</tb-icon>
</button>
<button mat-icon-button *ngIf="!disabled && isCustomMenuItem"
matTooltip="{{ 'mobile.delete-page' | translate }}"
matTooltipPosition="above"
(click)="delete()">
<mat-icon>delete</mat-icon>
</button>
@if (!disabled) {
<button type="button"
mat-icon-button
#editButton
(click)="edit($event, editButton)"
matTooltip="{{ 'mobile.edit-page' | translate }}"
matTooltipPosition="above">
<mat-icon>edit</mat-icon>
</button>
@if (isDefaultMenuItem) {
<button mat-icon-button
[disabled]="!isCleanupEnabled"
matTooltip="{{ 'mobile.reset-to-page-default' | translate }}"
matTooltipPosition="above"
(click)="cleanup()">
<tb-icon>mdi:broom</tb-icon>
</button>
}
@if (isCustomMenuItem) {
<button mat-icon-button
matTooltip="{{ 'mobile.delete-page' | translate }}"
matTooltipPosition="above"
(click)="delete()">
<mat-icon>delete</mat-icon>
</button>
}
}
</div>
</div>

39
ui-ngx/src/app/modules/home/pages/mobile/bundes/mobile-bundle-dialog.component.html

@ -25,9 +25,12 @@
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div *ngIf="!(isLoading$ | async)"></div>
@if (isLoading$ | async) {
<mat-progress-bar color="warn" mode="indeterminate"></mat-progress-bar>
}
@if (!(isLoading$ | async)) {
<div></div>
}
<div mat-dialog-content class="xs:!p-0">
<mat-horizontal-stepper [linear]="isAdd" labelPosition="end" #addMobileBundle [orientation]="(stepperOrientation | async)"
(selectionChange)="changeStep($event)">
@ -41,15 +44,21 @@
<mat-label translate>mobile.title</mat-label>
<input matInput required formControlName="title">
<mat-hint> </mat-hint>
<mat-error *ngIf="bundlesForms.get('title').hasError('required')">
{{ 'mobile.title-required' | translate }}
</mat-error>
<mat-error *ngIf="bundlesForms.get('title').hasError('pattern')">
{{ 'mobile.title-cannot-contain-only-spaces' | translate }}
</mat-error>
<mat-error *ngIf="bundlesForms.get('title').hasError('maxlength')">
{{ 'mobile.title-max-length' | translate }}
</mat-error>
@if (bundlesForms.get('title').hasError('required')) {
<mat-error>
{{ 'mobile.title-required' | translate }}
</mat-error>
}
@if (bundlesForms.get('title').hasError('pattern')) {
<mat-error>
{{ 'mobile.title-cannot-contain-only-spaces' | translate }}
</mat-error>
}
@if (bundlesForms.get('title').hasError('maxlength')) {
<mat-error>
{{ 'mobile.title-max-length' | translate }}
</mat-error>
}
</mat-form-field>
<tb-entity-autocomplete
allowCreateNew
@ -106,8 +115,10 @@
</mat-horizontal-stepper>
</div>
<div mat-dialog-actions>
<button mat-stroked-button *ngIf="selectedIndex > 0"
(click)="backStep()">{{ 'action.back' | translate }}</button>
@if (selectedIndex > 0) {
<button mat-stroked-button
(click)="backStep()">{{ 'action.back' | translate }}</button>
}
<span class="flex-1"></span>
<button mat-raised-button
color="primary"

257
ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html

@ -26,134 +26,153 @@
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="(isLoading$ | async) === false"></div>
@if (isLoading$ | async) {
<mat-progress-bar color="warn" mode="indeterminate"></mat-progress-bar>
} @else {
<div style="height: 4px;"></div>
}
<div mat-dialog-content>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="targetNotificationForm.get('name').hasError('required')">
{{ 'notification.name-required' | translate }}
</mat-error>
@if (targetNotificationForm.get('name').hasError('required')) {
<mat-error>{{ 'notification.name-required' | translate }}</mat-error>
}
</mat-form-field>
<section formGroupName="configuration">
<label class="tb-title">{{ 'notification.type' | translate }}</label>
<mat-radio-group formControlName="type">
<mat-radio-button *ngFor="let notificationTargetType of notificationTargetTypes" [value]="notificationTargetType">
<div class="flex flex-col">
<div>{{ notificationTargetTypeTranslationMap.get(notificationTargetType) | translate }}</div>
</div>
</mat-radio-button>
</mat-radio-group>
<section formGroupName="usersFilter"
*ngIf="targetNotificationForm.get('configuration.type').value === notificationTargetType.PLATFORM_USERS">
<mat-form-field class="mat-block" subscriptSizing="dynamic">
<mat-label translate>notification.recipient-type.user-filters</mat-label>
<mat-select formControlName="type">
<mat-option *ngFor="let type of notificationTargetConfigTypes" [value]="type">
{{ notificationTargetConfigTypeInfoMap.get(type).name | translate }}
</mat-option>
</mat-select>
<mat-hint>{{ notificationTargetConfigTypeInfoMap.get(targetNotificationForm.get('configuration.usersFilter.type').value).hint | translate }}</mat-hint>
</mat-form-field>
<ng-container [ngSwitch]="targetNotificationForm.get('configuration.usersFilter.type').value">
<ng-container *ngSwitchCase="notificationTargetConfigType.TENANT_ADMINISTRATORS">
<section *ngIf="isSysAdmin()">
<div class="flex flex-1 items-center justify-center">
<tb-toggle-select class="tb-notification-tenant-group" appearance="fill"
formControlName="filterByTenants">
<tb-toggle-option [value]="true">{{ 'tenant.tenant' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="false">{{ 'tenant-profile.tenant-profile' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<ng-container *ngIf="targetNotificationForm.get('configuration.usersFilter.filterByTenants').value; else tenantProfiles">
<tb-entity-list
formControlName="tenantsIds"
syncIdsWithDB
subscriptSizing="dynamic"
labelText="{{ 'tenant.tenants' | translate }}"
placeholderText="{{ 'tenant.tenants' | translate }}"
hint="{{ 'notification.tenants-list-rule-hint' | translate }}"
[entityType]="entityType.TENANT">
</tb-entity-list>
</ng-container>
<ng-template #tenantProfiles>
<tb-entity-list
formControlName="tenantProfilesIds"
syncIdsWithDB
subscriptSizing="dynamic"
labelText="{{ 'tenant-profile.tenant-profiles' | translate }}"
placeholderText="{{ 'tenant-profile.tenant-profiles' | translate }}"
hint="{{ 'notification.tenant-profiles-list-rule-hint' | translate }}"
[entityType]="entityType.TENANT_PROFILE">
</tb-entity-list>
</ng-template>
</section>
</ng-container>
<ng-container *ngSwitchCase="notificationTargetConfigType.USER_LIST">
<tb-entity-list
required
syncIdsWithDB
formControlName="usersIds"
[entityType]="entityType.USER"
labelText="{{ 'user.user-list' | translate }}"
placeholderText="{{ 'user.user' | translate }}"
requiredText="{{ 'user.user-list-required' | translate }}">
</tb-entity-list>
</ng-container>
<ng-container *ngSwitchCase="notificationTargetConfigType.CUSTOMER_USERS">
<tb-entity-autocomplete
required
formControlName="customerId"
[entityType]="entityType.CUSTOMER">
</tb-entity-autocomplete>
</ng-container>
</ng-container>
</section>
<section *ngIf="targetNotificationForm.get('configuration.type').value === notificationTargetType.SLACK">
<label class="tb-title">{{ 'notification.slack-chanel-type' | translate }}</label>
<mat-radio-group formControlName="conversationType">
<mat-radio-button *ngFor="let slackChanelType of slackChanelTypes" [value]="slackChanelType">
{{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }}
@for (notificationTargetType of notificationTargetTypes; track notificationTargetType) {
<mat-radio-button [value]="notificationTargetType">
<div class="flex flex-col">
<div>{{ notificationTargetTypeTranslationMap.get(notificationTargetType) | translate }}</div>
</div>
</mat-radio-button>
</mat-radio-group>
<tb-slack-conversation-autocomplete
formControlName="conversation" required
[slackChanelType]="targetNotificationForm.get('configuration.conversationType').value">
</tb-slack-conversation-autocomplete>
</section>
<section *ngIf="targetNotificationForm.get('configuration.type').value === notificationTargetType.MICROSOFT_TEAMS">
<section class="tb-form-row" style="margin-bottom: 16px">
<mat-slide-toggle class="mat-slide margin" formControlName="useOldApi">
{{ "notification.use-old-api" | translate }}
</mat-slide-toggle>
<a mat-icon-button
href="https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/"
target="_blank"
matTooltip="{{ 'notification.use-deprecated-webhook-connectors' | translate }}"
matTooltipPosition="above"
class="tb-mat-20">
<mat-icon>open_in_new</mat-icon>
</a>
}
</mat-radio-group>
@if (targetNotificationForm.get('configuration.type').value === notificationTargetType.PLATFORM_USERS) {
<section formGroupName="usersFilter">
<mat-form-field class="mat-block" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>notification.recipient-type.user-filters</mat-label>
<mat-select formControlName="type">
@for (type of notificationTargetConfigTypes; track type) {
<mat-option [value]="type">
{{ notificationTargetConfigTypeInfoMap.get(type).name | translate }}
</mat-option>
}
</mat-select>
<mat-hint>{{ notificationTargetConfigTypeInfoMap.get(targetNotificationForm.get('configuration.usersFilter.type').value).hint | translate }}</mat-hint>
</mat-form-field>
@switch (targetNotificationForm.get('configuration.usersFilter.type').value) {
@case (notificationTargetConfigType.TENANT_ADMINISTRATORS) {
@if (isSysAdmin()) {
<section>
<div class="flex flex-1 items-center justify-center">
<tb-toggle-select class="tb-notification-tenant-group" appearance="fill"
formControlName="filterByTenants">
<tb-toggle-option [value]="true">{{ 'tenant.tenant' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="false">{{ 'tenant-profile.tenant-profile' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
@if (targetNotificationForm.get('configuration.usersFilter.filterByTenants').value) {
<tb-entity-list
formControlName="tenantsIds"
syncIdsWithDB
appearance="outline"
subscriptSizing="dynamic"
labelText="{{ 'tenant.tenants' | translate }}"
placeholderText="{{ 'tenant.tenants' | translate }}"
hint="{{ 'notification.tenants-list-rule-hint' | translate }}"
[entityType]="entityType.TENANT">
</tb-entity-list>
} @else {
<tb-entity-list
formControlName="tenantProfilesIds"
syncIdsWithDB
appearance="outline"
subscriptSizing="dynamic"
labelText="{{ 'tenant-profile.tenant-profiles' | translate }}"
placeholderText="{{ 'tenant-profile.tenant-profiles' | translate }}"
hint="{{ 'notification.tenant-profiles-list-rule-hint' | translate }}"
[entityType]="entityType.TENANT_PROFILE">
</tb-entity-list>
}
</section>
}
}
@case (notificationTargetConfigType.USER_LIST) {
<tb-entity-list
required
syncIdsWithDB
appearance="outline"
formControlName="usersIds"
[entityType]="entityType.USER"
labelText="{{ 'user.user-list' | translate }}"
placeholderText="{{ 'user.user' | translate }}"
requiredText="{{ 'user.user-list-required' | translate }}">
</tb-entity-list>
}
@case (notificationTargetConfigType.CUSTOMER_USERS) {
<tb-entity-autocomplete
required
appearance="outline"
formControlName="customerId"
[entityType]="entityType.CUSTOMER">
</tb-entity-autocomplete>
}
}
</section>
}
@if (targetNotificationForm.get('configuration.type').value === notificationTargetType.SLACK) {
<section>
<label class="tb-title">{{ 'notification.slack-chanel-type' | translate }}</label>
<mat-radio-group formControlName="conversationType">
@for (slackChanelType of slackChanelTypes; track slackChanelType) {
<mat-radio-button [value]="slackChanelType">
{{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }}
</mat-radio-button>
}
</mat-radio-group>
<tb-slack-conversation-autocomplete
formControlName="conversation" required
appearance="outline"
[slackChanelType]="targetNotificationForm.get('configuration.conversationType').value">
</tb-slack-conversation-autocomplete>
</section>
}
@if (targetNotificationForm.get('configuration.type').value === notificationTargetType.MICROSOFT_TEAMS) {
<section>
<section class="tb-form-row" style="margin-bottom: 16px">
<mat-slide-toggle class="mat-slide margin" formControlName="useOldApi">
{{ "notification.use-old-api" | translate }}
</mat-slide-toggle>
<a mat-icon-button
href="https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/"
target="_blank"
matTooltip="{{ 'notification.use-deprecated-webhook-connectors' | translate }}"
matTooltipPosition="above"
class="tb-mat-20">
<mat-icon>open_in_new</mat-icon>
</a>
</section>
<mat-form-field class="mat-block" appearance="outline">
<mat-label>{{ (targetNotificationForm.get('configuration.useOldApi').value ?
'notification.webhook-url' : 'notification.workflow-url') | translate }}</mat-label>
<input matInput formControlName="webhookUrl">
@if (targetNotificationForm.get('configuration.webhookUrl').hasError('required')) {
<mat-error>{{ (targetNotificationForm.get('configuration.useOldApi').value ? 'notification.webhook-url-required' : 'notification.workflow-url-required') | translate }}</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.channel-name</mat-label>
<input matInput formControlName="channelName">
@if (targetNotificationForm.get('configuration.channelName').hasError('required')) {
<mat-error>{{ 'notification.channel-name-required' | translate }}</mat-error>
}
</mat-form-field>
</section>
<mat-form-field class="mat-block">
<mat-label>{{ (targetNotificationForm.get('configuration.useOldApi').value ?
'notification.webhook-url' : 'notification.workflow-url') | translate }}</mat-label>
<input matInput formControlName="webhookUrl">
<mat-error *ngIf="targetNotificationForm.get('configuration.webhookUrl').hasError('required')">
{{ (targetNotificationForm.get('configuration.useOldApi').value ? 'notification.webhook-url-required' : 'notification.workflow-url-required') | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.channel-name</mat-label>
<input matInput formControlName="channelName">
<mat-error *ngIf="targetNotificationForm.get('configuration.channelName').hasError('required')">
{{ 'notification.channel-name-required' | translate }}
</mat-error>
</mat-form-field>
</section>
<mat-form-field class="mat-block">
}
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>

17
ui-ngx/src/app/modules/home/pages/notification/rule/escalation-form.component.html

@ -17,25 +17,28 @@
-->
<form [formGroup]="escalationFormGroup" class="flex flex-col">
<div class="escalation flex flex-row items-center justify-center xs:flex-col xs:items-stretch xs:justify-start">
<div class="flex-1" *ngIf="systemEscalation; else selectTime" translate>notification.first-recipient</div>
<ng-template #selectTime>
@if (systemEscalation) {
<div class="flex-1" translate>notification.first-recipient</div>
} @else {
<div class="flex flex-1 flex-row items-center justify-center xs:flex-col">
<span translate>notification.after</span>
<tb-timeinterval
appearance="outline"
subscriptSizing="dynamic"
formControlName="delayInSec"
min="60000"
max="604800000"
disabledAdvanced></tb-timeinterval>
[min]="60000"
[max]="604800000"
disabledAdvanced=""></tb-timeinterval>
<span class="escalation-notify flex-1" translate>notification.notify</span>
</div>
</ng-template>
}
<tb-entity-list
required
class="flex-full xs:max-h-60% gt-xs:max-w-60%"
formControlName="targets"
appearance="outline"
subscriptSizing="dynamic"
entityType="{{ entityType.NOTIFICATION_TARGET }}"
[entityType]="entityType.NOTIFICATION_TARGET"
subType="{{ notificationType.ALARM }}"
[placeholderText]="escalationFormGroup.get('targets').value ? '' : ('notification.add-recipient' | translate)">
<button #createTargetButton

62
ui-ngx/src/app/modules/home/pages/notification/rule/escalations.component.html

@ -16,32 +16,38 @@
-->
<div class="flex flex-col">
<div class="tb-tenant-profile-queues flex flex-row items-center"
*ngFor="let escalationControl of escalationsFormArray.controls;
let $index = index; last as isLast;"
[class.pb-2]="!isLast">
<tb-escalation-form class="flex-1"
[formControl]="escalationControl"
[systemEscalation]="$index === 0">
</tb-escalation-form>
<span *ngIf="$index === 0" style="width: 48px;"></span>
<button *ngIf="!($index === 0) && !disabled" mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeEscalation($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
<div *ngIf="!escalationsFormArray.controls.length">
<span translate
class="tb-prompt flex items-center justify-center">notification.no-rule</span>
</div>
<div *ngIf="!disabled" style="padding: 16px 0;">
<button mat-raised-button color="primary"
type="button"
(click)="addEscalation()">
<span translate>notification.add-stage</span>
</button>
</div>
@for (escalationControl of escalationsFormArray.controls; track $index; let isLast = $last) {
<div class="tb-tenant-profile-queues flex flex-row items-center" [class.pb-2]="!isLast">
<tb-escalation-form class="flex-1"
[formControl]="escalationControl"
[systemEscalation]="$index === 0">
</tb-escalation-form>
@if ($index === 0) {
<span style="width: 48px;"></span>
}
@if (!($index === 0) && !disabled) {
<button mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeEscalation($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
}
</div>
}
@if (!escalationsFormArray.controls.length) {
<div>
<span translate class="tb-prompt flex items-center justify-center">notification.no-rule</span>
</div>
}
@if (!disabled) {
<div style="padding: 16px 0;">
<button mat-raised-button color="primary"
type="button"
(click)="addEscalation()">
<span translate>notification.add-stage</span>
</button>
</div>
}
</div>

1167
ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html

File diff suppressed because it is too large

1
ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.scss

@ -112,6 +112,7 @@
max-height: 100%;
width: 100%;;
overflow-y: auto;
padding-top: 4px;
scrollbar-gutter: stable;
@media #{$mat-gt-sm} {
min-width: 500px;

278
ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.html

@ -44,17 +44,18 @@
<tb-toggle-option [value]="true">{{ 'notification.use-template' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<div *ngIf="notificationRequestForm.get('useTemplate').value; else scratchTemplate">
<tb-template-autocomplete
required allowCreate allowEdit
formControlName="templateId"
[notificationTypes]="notificationType.GENERAL">
</tb-template-autocomplete>
<ng-container *ngTemplateOutlet="recipientsList"></ng-container>
</div>
<ng-template #scratchTemplate>
<ng-container *ngTemplateOutlet="recipientsList"></ng-container>
<section formGroupName="template">
<div>
@if (notificationRequestForm.get('useTemplate').value) {
<tb-template-autocomplete
required allowCreate allowEdit
appearance="outline"
formControlName="templateId"
[notificationTypes]="notificationType.GENERAL">
</tb-template-autocomplete>
<ng-container *ngTemplateOutlet="recipientsList"></ng-container>
} @else {
<ng-container *ngTemplateOutlet="recipientsList"></ng-container>
<section formGroupName="template">
<section formGroupName="configuration">
<div class="delivery-title">
<div>
@ -101,16 +102,18 @@
</section>
</section>
</section>
</ng-template>
}
</div>
<ng-template #recipientsList>
<tb-entity-list
syncIdsWithDB
required
formControlName="targets"
appearance="outline"
labelText="{{ 'notification.recipients' | translate }}"
placeholderText="{{ 'notification.recipient' | translate }}"
requiredText="{{ 'notification.recipients-required' | translate }}"
entityType="{{ entityType.NOTIFICATION_TARGET }}"
[entityType]="entityType.NOTIFICATION_TARGET"
subType="{{ notificationType.GENERAL }}">
<button #createTargetButton
mat-button color="primary" matSuffix
@ -123,129 +126,156 @@
<mat-slide-toggle formControlName="enabled" class="toggle">
{{ 'notification.scheduler-later' | translate }}
</mat-slide-toggle>
<div *ngIf="notificationRequestForm.get('additionalConfig.enabled').value" class="flex flex-col">
<tb-timezone-select userTimezoneByDefault
required
formControlName="timezone">
</tb-timezone-select>
<mat-form-field>
<mat-label translate>notification.time</mat-label>
<mat-datetimepicker-toggle [for]="startTimePicker" matPrefix></mat-datetimepicker-toggle>
<mat-datetimepicker #startTimePicker type="datetime" openOnFocus="true"></mat-datetimepicker>
<input required matInput class="flex-1" formControlName="time" [min]="minDate()" [max]="maxDate()" [matDatetimepicker]="startTimePicker">
</mat-form-field>
</div>
@if (notificationRequestForm.get('additionalConfig.enabled').value) {
<div class="flex flex-col">
<tb-timezone-select userTimezoneByDefault
required
appearance="outline"
formControlName="timezone">
</tb-timezone-select>
<mat-form-field appearance="outline">
<mat-label translate>notification.time</mat-label>
<mat-datetimepicker-toggle [for]="startTimePicker" matPrefix></mat-datetimepicker-toggle>
<mat-datetimepicker #startTimePicker type="datetime" openOnFocus="true"></mat-datetimepicker>
<input required matInput class="flex-1" formControlName="time" [min]="minDate()" [max]="maxDate()" [matDatetimepicker]="startTimePicker">
</mat-form-field>
</div>
}
</section>
</form>
</mat-step>
<mat-step *ngIf="!notificationRequestForm.get('useTemplate').value"
[stepControl]="notificationTemplateConfigurationForm" #composeStep=matStep>
<ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template>
<ng-template matStepContent>
<form [formGroup]="notificationTemplateConfigurationForm">
<tb-template-configuration
notificationType="{{ notificationType.GENERAL }}"
[predefinedDeliveryMethodsTemplate]="deliveryConfiguration"
[interacted]="composeStep.interacted"
formControlName="deliveryMethodsTemplates">
</tb-template-configuration>
</form>
</ng-template>
</mat-step>
@if (!notificationRequestForm.get('useTemplate').value) {
<mat-step [stepControl]="notificationTemplateConfigurationForm" #composeStep=matStep>
<ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template>
<ng-template matStepContent>
<form [formGroup]="notificationTemplateConfigurationForm">
<tb-template-configuration
[notificationType]="notificationType.GENERAL"
[predefinedDeliveryMethodsTemplate]="deliveryConfiguration"
[interacted]="composeStep.interacted"
formControlName="deliveryMethodsTemplates">
</tb-template-configuration>
</form>
</ng-template>
</mat-step>
}
<mat-step>
<ng-template matStepLabel>{{ 'notification.review' | translate }}</ng-template>
<div class="tb-loader" *ngIf="(isLoading$ | async) && !preview">
<mat-progress-spinner color="warn" mode="indeterminate"
strokeWidth="5">
</mat-progress-spinner>
</div>
<div *ngIf="preview" class="tb-form-panel no-padding no-border">
<section class="preview-group tb-form-panel stroked no-gap">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">supervisor_account</tb-icon>
<div class="tb-form-panel-title">{{ 'notification.recipients-count' | translate : {count: preview.totalRecipientsCount} }}</div>
</div>
<div class="details-recipients" *ngIf="notificationRequestForm.get('targets').value?.length > 1">
<div *ngFor="let detail of preview.recipientsCountByTarget | keyvalue" class="details-recipient">
<span class="number">{{ detail.value }}</span>{{ detail.key }}
@if ((isLoading$ | async) && !preview) {
<div class="tb-loader">
<mat-progress-spinner color="warn" mode="indeterminate"
strokeWidth="5">
</mat-progress-spinner>
</div>
}
@if (preview) {
<div class="tb-form-panel no-padding no-border">
<section class="preview-group tb-form-panel stroked no-gap">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">supervisor_account</tb-icon>
<div class="tb-form-panel-title">{{ 'notification.recipients-count' | translate : {count: preview.totalRecipientsCount} }}</div>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<mat-chip-set>
<mat-chip-row class="tb-chip-row-ellipsis" *ngFor="let recipientTitle of preview.recipientsPreview">
<span>{{ recipientTitle }}</span>
</mat-chip-row>
</mat-chip-set>
</section>
<section class="preview-group notification tb-form-panel stroked no-gap" *ngIf="preview.processedTemplates.WEB?.enabled">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">mdi:bell-badge</tb-icon>
<div class="tb-form-panel-title" translate>notification.delivery-method.web-preview</div>
</div>
<div class="web-preview">
<tb-notification preview [notification]="preview.processedTemplates.WEB"></tb-notification>
</div>
</section>
<section class="preview-group notification tb-form-panel stroked no-gap" *ngIf="preview.processedTemplates.MOBILE_APP?.enabled">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">mdi:cellphone-text</tb-icon>
<div class="tb-form-panel-title" translate>notification.delivery-method.mobile-app-preview</div>
</div>
<div class="notification-content">
<div class="subject">{{ preview.processedTemplates.MOBILE_APP.subject }}</div>
<div>{{ preview.processedTemplates.MOBILE_APP.body }}</div>
</div>
</section>
<section class="preview-group notification tb-form-panel stroked no-gap" *ngIf="preview.processedTemplates.SMS?.enabled">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">mdi:message-processing</tb-icon>
<div class="tb-form-panel-title" translate>notification.delivery-method.sms-preview</div>
</div>
<div class="notification-content">
{{ preview.processedTemplates.SMS.body }}
</div>
</section>
<section class="preview-group notification tb-form-panel stroked no-gap" *ngIf="preview.processedTemplates.EMAIL?.enabled">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">mdi:email</tb-icon>
<div class="tb-form-panel-title" translate>notification.delivery-method.email-preview</div>
</div>
<div class="notification-content">
<div class="subject">{{ preview.processedTemplates.EMAIL.subject }}</div>
<mat-divider></mat-divider>
<editor [init]="tinyMceOptions" [initialValue]="preview.processedTemplates.EMAIL.body"></editor>
</div>
</section>
<section class="preview-group notification tb-form-panel stroked no-gap" *ngIf="preview.processedTemplates.SLACK?.enabled">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">mdi:slack</tb-icon>
<div class="tb-form-panel-title" translate>notification.delivery-method.slack-preview</div>
</div>
<div class="notification-content">
{{ preview.processedTemplates.SLACK.body }}
</div>
</section>
<section class="preview-group notification tb-form-panel stroked no-gap" *ngIf="preview.processedTemplates.MICROSOFT_TEAMS?.enabled">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">mdi:microsoft-teams</tb-icon>
<div class="tb-form-panel-title" translate>notification.delivery-method.microsoft-teams-preview</div>
</div>
<div class="notification-content mini" [style.background-color]="preview.processedTemplates.MICROSOFT_TEAMS?.themeColor">
<div class="subject">{{ preview.processedTemplates.MICROSOFT_TEAMS.subject }}</div>
{{ preview.processedTemplates.MICROSOFT_TEAMS.body }}
@if (preview.processedTemplates.MICROSOFT_TEAMS.button?.enabled) {
<button mat-raised-button>{{ preview.processedTemplates.MICROSOFT_TEAMS.button?.text }}</button>
@if (notificationRequestForm.get('targets').value?.length > 1) {
<div class="details-recipients">
@for (detail of (preview.recipientsCountByTarget | keyvalue); track detail.key) {
<div class="details-recipient">
<span class="number">{{ detail.value }}</span>{{ detail.key }}
</div>
}
</div>
}
</div>
</section>
</div>
<mat-divider class="divider"></mat-divider>
<mat-chip-set>
@for (recipientTitle of preview.recipientsPreview; track recipientTitle) {
<mat-chip-row class="tb-chip-row-ellipsis">
<span>{{ recipientTitle }}</span>
</mat-chip-row>
}
</mat-chip-set>
</section>
@if (preview.processedTemplates.WEB?.enabled) {
<section class="preview-group notification tb-form-panel stroked no-gap">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">mdi:bell-badge</tb-icon>
<div class="tb-form-panel-title" translate>notification.delivery-method.web-preview</div>
</div>
<div class="web-preview">
<tb-notification preview [notification]="preview.processedTemplates.WEB"></tb-notification>
</div>
</section>
}
@if (preview.processedTemplates.MOBILE_APP?.enabled) {
<section class="preview-group notification tb-form-panel stroked no-gap">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">mdi:cellphone-text</tb-icon>
<div class="tb-form-panel-title" translate>notification.delivery-method.mobile-app-preview</div>
</div>
<div class="notification-content">
<div class="subject">{{ preview.processedTemplates.MOBILE_APP.subject }}</div>
<div>{{ preview.processedTemplates.MOBILE_APP.body }}</div>
</div>
</section>
}
@if (preview.processedTemplates.SMS?.enabled) {
<section class="preview-group notification tb-form-panel stroked no-gap">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">mdi:message-processing</tb-icon>
<div class="tb-form-panel-title" translate>notification.delivery-method.sms-preview</div>
</div>
<div class="notification-content">
{{ preview.processedTemplates.SMS.body }}
</div>
</section>
}
@if (preview.processedTemplates.EMAIL?.enabled) {
<section class="preview-group notification tb-form-panel stroked no-gap">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">mdi:email</tb-icon>
<div class="tb-form-panel-title" translate>notification.delivery-method.email-preview</div>
</div>
<div class="notification-content">
<div class="subject">{{ preview.processedTemplates.EMAIL.subject }}</div>
<mat-divider></mat-divider>
<editor [init]="tinyMceOptions" [initialValue]="preview.processedTemplates.EMAIL.body"></editor>
</div>
</section>
}
@if (preview.processedTemplates.SLACK?.enabled) {
<section class="preview-group notification tb-form-panel stroked no-gap">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">mdi:slack</tb-icon>
<div class="tb-form-panel-title" translate>notification.delivery-method.slack-preview</div>
</div>
<div class="notification-content">
{{ preview.processedTemplates.SLACK.body }}
</div>
</section>
}
@if (preview.processedTemplates.MICROSOFT_TEAMS?.enabled) {
<section class="preview-group notification tb-form-panel stroked no-gap">
<div class="flex flex-row items-center justify-start gap-2">
<tb-icon class="tb-mat-18">mdi:microsoft-teams</tb-icon>
<div class="tb-form-panel-title" translate>notification.delivery-method.microsoft-teams-preview</div>
</div>
<div class="notification-content mini" [style.background-color]="preview.processedTemplates.MICROSOFT_TEAMS?.themeColor">
<div class="subject">{{ preview.processedTemplates.MICROSOFT_TEAMS.subject }}</div>
{{ preview.processedTemplates.MICROSOFT_TEAMS.body }}
@if (preview.processedTemplates.MICROSOFT_TEAMS.button?.enabled) {
<button mat-raised-button>{{ preview.processedTemplates.MICROSOFT_TEAMS.button?.text }}</button>
}
</div>
</section>
}
</div>
}
</mat-step>
</mat-horizontal-stepper>
</div>
<mat-divider></mat-divider>
<div mat-dialog-actions class="tb-dialog-actions">
<button mat-stroked-button *ngIf="selectedIndex > 0"
(click)="backStep()">{{ 'action.back' | translate }}</button>
@if (selectedIndex > 0) {
<button mat-stroked-button (click)="backStep()">{{ 'action.back' | translate }}</button>
}
<span class="flex-1"></span>
<button mat-raised-button
color="primary"

1
ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.scss

@ -218,6 +218,7 @@
width: 100%;;
overflow-y: auto;
scrollbar-gutter: stable;
padding-top: 4px;
@media #{$mat-gt-sm} {
min-width: 500px;
}

79
ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-action-button-configuration.component.html

@ -29,58 +29,63 @@
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<div class="flex flex-row xs:flex-col gt-xs:gap-2">
<mat-form-field class="mat-block flex-1" *ngIf="!hideButtonText">
<mat-label translate>notification.button-text</mat-label>
<input matInput formControlName="text" required>
<mat-error
*ngIf="actionButtonConfigForm.get('text').hasError('required')">
{{ 'notification.button-text-required' | translate }}
</mat-error>
<mat-error
*ngIf="actionButtonConfigForm.get('text').hasError('maxlength')">
{{ 'notification.button-text-max-length' | translate :
@if (!hideButtonText) {
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>notification.button-text</mat-label>
<input matInput formControlName="text" required>
@if (actionButtonConfigForm.get('text').hasError('required')) {
<mat-error>{{ 'notification.button-text-required' | translate }}</mat-error>
}
@if (actionButtonConfigForm.get('text').hasError('maxlength')) {
<mat-error>{{ 'notification.button-text-max-length' | translate :
{length: actionButtonConfigForm.get('text').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
}}</mat-error>
}
</mat-form-field>
}
</div>
<div class="grid gt-sm:grid-cols-3 gt-sm:gap-4">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.action-type</mat-label>
<mat-select formControlName="linkType">
<mat-option *ngFor="let actionButtonLinkType of actionButtonLinkTypes"
[value]="actionButtonLinkType">
{{ actionButtonLinkTypeTranslateMap.get(actionButtonLinkType) | translate }}
</mat-option>
@for (actionButtonLinkType of actionButtonLinkTypes; track actionButtonLinkType) {
<mat-option [value]="actionButtonLinkType">
{{ actionButtonLinkTypeTranslateMap.get(actionButtonLinkType) | translate }}
</mat-option>
}
</mat-select>
</mat-form-field>
@if(actionButtonConfigForm.get('linkType').value === actionButtonLinkType.LINK) {
<mat-form-field class="gt-sm:col-span-2">
@if (actionButtonConfigForm.get('linkType').value === actionButtonLinkType.LINK) {
<mat-form-field class="gt-sm:col-span-2" appearance="outline">
<mat-label translate>notification.link</mat-label>
<input matInput formControlName="link" required>
<mat-error
*ngIf="actionButtonConfigForm.get('link').hasError('required')">
{{ 'notification.link-required' | translate }}
</mat-error>
<mat-error
*ngIf="actionButtonConfigForm.get('link').hasError('maxlength')">
{{ 'notification.link-max-length' | translate :
{length: actionButtonConfigForm.get('link').getError('maxlength').requiredLength}
}}
</mat-error>
@if (actionButtonConfigForm.get('link').hasError('required')) {
<mat-error>{{ 'notification.link-required' | translate }}</mat-error>
}
@if (actionButtonConfigForm.get('link').hasError('maxlength')) {
<mat-error>{{ 'notification.link-max-length' | translate :
{length: actionButtonConfigForm.get('link').getError('maxlength').requiredLength}
}}</mat-error>
}
</mat-form-field>
} @else {
<tb-dashboard-autocomplete required formControlName="dashboardId">
<tb-dashboard-autocomplete
required
appearance="outline"
formControlName="dashboardId">
</tb-dashboard-autocomplete>
<tb-dashboard-state-autocomplete [dashboardId]="actionButtonConfigForm.get('dashboardId').value"
formControlName="dashboardState">
<tb-dashboard-state-autocomplete
appearance="outline"
[dashboardId]="actionButtonConfigForm.get('dashboardId').value"
formControlName="dashboardState">
</tb-dashboard-state-autocomplete>
}
</div>
<mat-slide-toggle formControlName="setEntityIdInState" class="toggle"
*ngIf="actionButtonConfigForm.get('linkType').value === actionButtonLinkType.DASHBOARD">
{{ 'notification.set-entity-from-notification' | translate }}
</mat-slide-toggle>
@if (actionButtonConfigForm.get('linkType').value === actionButtonLinkType.DASHBOARD) {
<mat-slide-toggle formControlName="setEntityIdInState" class="toggle">
{{ 'notification.set-entity-from-notification' | translate }}
</mat-slide-toggle>
}
</ng-template>
</mat-expansion-panel>
</div>

486
ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html

@ -18,7 +18,7 @@
<section class="tb-template-header">
<div class="tb-form-panel-title" translate>notification.customize-messages</div>
<div class="tb-form-panel-hint tb-hint-available-params">
<span class="content">{{ 'notification.input-fields-support-templatization' | translate}}</span>
<span class="content">{{ 'notification.input-fields-support-templatization' | translate }}</span>
<span tb-help-popup="{{ NotificationTemplateTypeTranslateMap.get(notificationType).helpId }}"
tb-help-popup-placement="bottom"
trigger-style="letter-spacing:0.25px;font-size:12px;"
@ -27,265 +27,235 @@
</div>
</section>
<section [formGroup]="templateConfigurationForm" class="tb-form-panel no-border no-padding">
<section class="tb-form-panel tb-slide-toggle stroked"
[formGroupName]="NotificationDeliveryMethod.WEB"
*ngIf="templateConfigurationForm.get('WEB.enabled').value">
<mat-expansion-panel class="tb-settings" expanded="{{ expandedForm(NotificationDeliveryMethod.WEB) }}">
<mat-expansion-panel-header class="fill-width flex flex-row flex-wrap">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.WEB).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.WEB).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
<mat-error *ngIf="templateConfigurationForm.get('WEB.subject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
<mat-error *ngIf="templateConfigurationForm.get('WEB.subject').hasError('maxlength')">
{{'notification.subject-max-length' | translate :
{length: templateConfigurationForm.get('WEB.subject').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="templateConfigurationForm.get('WEB.body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
<mat-error *ngIf="templateConfigurationForm.get('WEB.body').hasError('maxlength')">
{{ 'notification.message-max-length' | translate :
{length: templateConfigurationForm.get('WEB.body').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
<section formGroupName="additionalConfig" class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between" formGroupName="icon">
<mat-slide-toggle formControlName="enabled" class="mat-slide">
{{ 'icon.icon' | translate }}
</mat-slide-toggle>
<div class="flex flex-row items-center justify-start gap-2">
<tb-material-icon-select asBoxInput
[color]="templateConfigurationForm.get('WEB.additionalConfig.icon.color').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
@if (templateConfigurationForm.get('WEB.enabled').value) {
<section class="tb-form-panel tb-slide-toggle stroked" [formGroupName]="NotificationDeliveryMethod.WEB">
<mat-expansion-panel class="tb-settings" expanded="{{ expandedForm(NotificationDeliveryMethod.WEB) }}">
<mat-expansion-panel-header class="fill-width flex flex-row flex-wrap">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.WEB).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.WEB).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
@if (templateConfigurationForm.get('WEB.subject').hasError('required')) {
<mat-error>{{ 'notification.subject-required' | translate }}</mat-error>
}
@if (templateConfigurationForm.get('WEB.subject').hasError('maxlength')) {
<mat-error>{{ 'notification.subject-max-length' | translate :
{length: templateConfigurationForm.get('WEB.subject').getError('maxlength').requiredLength}
}}</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.message</mat-label>
<textarea matInput cdkTextareaAutosize cols="1" cdkAutosizeMinRows="1" formControlName="body"></textarea>
@if (templateConfigurationForm.get('WEB.body').hasError('required')) {
<mat-error>{{ 'notification.message-required' | translate }}</mat-error>
}
@if (templateConfigurationForm.get('WEB.body').hasError('maxlength')) {
<mat-error>{{ 'notification.message-max-length' | translate :
{length: templateConfigurationForm.get('WEB.body').getError('maxlength').requiredLength}
}}</mat-error>
}
</mat-form-field>
<section formGroupName="additionalConfig" class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between" formGroupName="icon">
<mat-slide-toggle formControlName="enabled" class="mat-slide">
{{ 'icon.icon' | translate }}
</mat-slide-toggle>
<div class="flex flex-row items-center justify-start gap-2">
<tb-material-icon-select asBoxInput
[color]="templateConfigurationForm.get('WEB.additionalConfig.icon.color').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput formControlName="color"></tb-color-input>
</div>
</div>
</div>
<tb-notification-action-button-configuration
actionTitle="{{ 'notification.action-button' | translate }}"
formControlName="actionButtonConfig">
</tb-notification-action-button-configuration>
</section>
</ng-template>
</mat-expansion-panel>
</section>
<section class="tb-form-panel tb-slide-toggle stroked"
[formGroupName]="NotificationDeliveryMethod.MOBILE_APP"
*ngIf="templateConfigurationForm.get('MOBILE_APP.enabled').value">
<mat-expansion-panel class="tb-settings" expanded="{{ expandedForm(NotificationDeliveryMethod.MOBILE_APP) }}">
<mat-expansion-panel-header class="fill-width flex flex-row flex-wrap">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MOBILE_APP).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MOBILE_APP).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
<mat-error *ngIf="templateConfigurationForm.get('MOBILE_APP.subject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
<mat-error *ngIf="templateConfigurationForm.get('MOBILE_APP.subject').hasError('maxlength')">
{{ 'notification.subject-max-length' | translate :
{length: templateConfigurationForm.get('MOBILE_APP.subject').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="templateConfigurationForm.get('MOBILE_APP.body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
<mat-error *ngIf="templateConfigurationForm.get('MOBILE_APP.body').hasError('maxlength')">
{{ 'notification.message-max-length' | translate :
{length: templateConfigurationForm.get('MOBILE_APP.body').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
<div formGroupName="additionalConfig" class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between" formGroupName="icon">
<mat-slide-toggle formControlName="enabled" class="mat-slide">
{{ 'icon.icon' | translate }}
</mat-slide-toggle>
<div class="flex flex-row items-center justify-start gap-2">
<tb-material-icon-select asBoxInput
[color]="templateConfigurationForm.get('MOBILE_APP.additionalConfig.icon.color').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
<tb-notification-action-button-configuration
actionTitle="{{ 'notification.action-button' | translate }}"
formControlName="actionButtonConfig">
</tb-notification-action-button-configuration>
</section>
</ng-template>
</mat-expansion-panel>
</section>
}
@if (templateConfigurationForm.get('MOBILE_APP.enabled').value) {
<section class="tb-form-panel tb-slide-toggle stroked" [formGroupName]="NotificationDeliveryMethod.MOBILE_APP">
<mat-expansion-panel class="tb-settings" expanded="{{ expandedForm(NotificationDeliveryMethod.MOBILE_APP) }}">
<mat-expansion-panel-header class="fill-width flex flex-row flex-wrap">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MOBILE_APP).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MOBILE_APP).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
@if (templateConfigurationForm.get('MOBILE_APP.subject').hasError('required')) {
<mat-error>{{ 'notification.subject-required' | translate }}</mat-error>
}
@if (templateConfigurationForm.get('MOBILE_APP.subject').hasError('maxlength')) {
<mat-error>{{ 'notification.subject-max-length' | translate :
{length: templateConfigurationForm.get('MOBILE_APP.subject').getError('maxlength').requiredLength}
}}</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.message</mat-label>
<textarea matInput cdkTextareaAutosize cols="1" cdkAutosizeMinRows="1" formControlName="body"></textarea>
@if (templateConfigurationForm.get('MOBILE_APP.body').hasError('required')) {
<mat-error>{{ 'notification.message-required' | translate }}</mat-error>
}
@if (templateConfigurationForm.get('MOBILE_APP.body').hasError('maxlength')) {
<mat-error>{{ 'notification.message-max-length' | translate :
{length: templateConfigurationForm.get('MOBILE_APP.body').getError('maxlength').requiredLength}
}}</mat-error>
}
</mat-form-field>
<div formGroupName="additionalConfig" class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between" formGroupName="icon">
<mat-slide-toggle formControlName="enabled" class="mat-slide">
{{ 'icon.icon' | translate }}
</mat-slide-toggle>
<div class="flex flex-row items-center justify-start gap-2">
<tb-material-icon-select asBoxInput
[color]="templateConfigurationForm.get('MOBILE_APP.additionalConfig.icon.color').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput formControlName="color"></tb-color-input>
</div>
</div>
<tb-notification-action-button-configuration
formControlName="onClick"
hideButtonText
[sliderHint]="notificationTapActionHint"
actionTitle="{{ 'notification.notification-tap-action' | translate }}">
</tb-notification-action-button-configuration>
</div>
<tb-notification-action-button-configuration
formControlName="onClick"
hideButtonText
[sliderHint]="notificationTapActionHint"
actionTitle="{{ 'notification.notification-tap-action' | translate }}">
</tb-notification-action-button-configuration>
</div>
</ng-template>
</mat-expansion-panel>
</section>
<section class="tb-form-panel tb-slide-toggle stroked"
[formGroupName]="NotificationDeliveryMethod.SMS"
*ngIf="templateConfigurationForm.get('SMS.enabled').value">
<mat-expansion-panel class="tb-settings" expanded="{{ expandedForm(NotificationDeliveryMethod.SMS) }}">
<mat-expansion-panel-header class="fill-width flex flex-row flex-wrap">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SMS).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SMS).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block" subscriptSizing="dynamic">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-hint></mat-hint>
<mat-error *ngIf="templateConfigurationForm.get('SMS.body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
<mat-error *ngIf="templateConfigurationForm.get('SMS.body').hasError('maxlength')">
{{ 'notification.message-max-length' | translate :
{length: templateConfigurationForm.get('SMS.body').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
</ng-template>
</mat-expansion-panel>
</section>
<section class="tb-form-panel tb-slide-toggle stroked"
[formGroupName]="NotificationDeliveryMethod.EMAIL"
*ngIf="templateConfigurationForm.get('EMAIL.enabled').value">
<mat-expansion-panel class="tb-settings" expanded="{{ expandedForm(NotificationDeliveryMethod.EMAIL) }}">
<mat-expansion-panel-header class="fill-width flex flex-row flex-wrap">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.EMAIL).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.EMAIL).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
<mat-error *ngIf="templateConfigurationForm.get('EMAIL.subject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
<mat-error *ngIf="templateConfigurationForm.get('EMAIL.subject').hasError('maxlength')">
{{'notification.subject-max-length' | translate :
{length: templateConfigurationForm.get('EMAIL.subject').getError('maxlength').requiredLength}
}}
</mat-error>
</mat-form-field>
<mat-label class="tb-title tb-required"
[class.tb-error]="(interacted || templateConfigurationForm.get('EMAIL.body').touched) && templateConfigurationForm.get('EMAIL.body').hasError('required')"
translate>notification.message
</mat-label>
<editor [init]="tinyMceOptions" formControlName="body"></editor>
<mat-error class="tb-mat-error"
*ngIf="(interacted || templateConfigurationForm.get('EMAIL.body').touched) && templateConfigurationForm.get('EMAIL.body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</ng-template>
</mat-expansion-panel>
</section>
<section class="tb-form-panel tb-slide-toggle stroked"
[formGroupName]="NotificationDeliveryMethod.SLACK"
*ngIf="templateConfigurationForm.get('SLACK.enabled').value">
<mat-expansion-panel class="tb-settings" expanded="{{ expandedForm(NotificationDeliveryMethod.SLACK) }}">
<mat-expansion-panel-header class="fill-width flex flex-row flex-wrap">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SLACK).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SLACK).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="templateConfigurationForm.get('SLACK.body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
</ng-template>
</mat-expansion-panel>
</section>
<section class="tb-form-panel tb-slide-toggle stroked"
[formGroupName]="NotificationDeliveryMethod.MICROSOFT_TEAMS"
*ngIf="templateConfigurationForm.get('MICROSOFT_TEAMS.enabled').value">
<mat-expansion-panel class="tb-settings" expanded="{{ expandedForm(NotificationDeliveryMethod.MICROSOFT_TEAMS) }}">
<mat-expansion-panel-header class="fill-width flex flex-row flex-wrap">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MICROSOFT_TEAMS).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MICROSOFT_TEAMS).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body">
</textarea>
<mat-error *ngIf="templateConfigurationForm.get('MICROSOFT_TEAMS.body').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
<div class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between">
<div translate>notification.theme-color</div>
<tb-color-input asBoxInput formControlName="themeColor"></tb-color-input>
</ng-template>
</mat-expansion-panel>
</section>
}
@if (templateConfigurationForm.get('SMS.enabled').value) {
<section class="tb-form-panel tb-slide-toggle stroked" [formGroupName]="NotificationDeliveryMethod.SMS">
<mat-expansion-panel class="tb-settings" expanded="{{ expandedForm(NotificationDeliveryMethod.SMS) }}">
<mat-expansion-panel-header class="fill-width flex flex-row flex-wrap">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SMS).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SMS).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>notification.message</mat-label>
<textarea matInput cdkTextareaAutosize cols="1" cdkAutosizeMinRows="1" formControlName="body"></textarea>
<mat-hint></mat-hint>
@if (templateConfigurationForm.get('SMS.body').hasError('required')) {
<mat-error>{{ 'notification.message-required' | translate }}</mat-error>
}
@if (templateConfigurationForm.get('SMS.body').hasError('maxlength')) {
<mat-error>{{ 'notification.message-max-length' | translate :
{length: templateConfigurationForm.get('SMS.body').getError('maxlength').requiredLength}
}}</mat-error>
}
</mat-form-field>
</ng-template>
</mat-expansion-panel>
</section>
}
@if (templateConfigurationForm.get('EMAIL.enabled').value) {
<section class="tb-form-panel tb-slide-toggle stroked" [formGroupName]="NotificationDeliveryMethod.EMAIL">
<mat-expansion-panel class="tb-settings" expanded="{{ expandedForm(NotificationDeliveryMethod.EMAIL) }}">
<mat-expansion-panel-header class="fill-width flex flex-row flex-wrap">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.EMAIL).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.EMAIL).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
@if (templateConfigurationForm.get('EMAIL.subject').hasError('required')) {
<mat-error>{{ 'notification.subject-required' | translate }}</mat-error>
}
@if (templateConfigurationForm.get('EMAIL.subject').hasError('maxlength')) {
<mat-error>{{ 'notification.subject-max-length' | translate :
{length: templateConfigurationForm.get('EMAIL.subject').getError('maxlength').requiredLength}
}}</mat-error>
}
</mat-form-field>
<mat-label class="tb-title tb-required"
[class.tb-error]="(interacted || templateConfigurationForm.get('EMAIL.body').touched) && templateConfigurationForm.get('EMAIL.body').hasError('required')"
translate>notification.message
</mat-label>
<editor [init]="tinyMceOptions" formControlName="body"></editor>
@if ((interacted || templateConfigurationForm.get('EMAIL.body').touched) && templateConfigurationForm.get('EMAIL.body').hasError('required')) {
<mat-error class="tb-mat-error">{{ 'notification.message-required' | translate }}</mat-error>
}
</ng-template>
</mat-expansion-panel>
</section>
}
@if (templateConfigurationForm.get('SLACK.enabled').value) {
<section class="tb-form-panel tb-slide-toggle stroked" [formGroupName]="NotificationDeliveryMethod.SLACK">
<mat-expansion-panel class="tb-settings" expanded="{{ expandedForm(NotificationDeliveryMethod.SLACK) }}">
<mat-expansion-panel-header class="fill-width flex flex-row flex-wrap">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SLACK).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SLACK).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.message</mat-label>
<textarea matInput cdkTextareaAutosize cols="1" cdkAutosizeMinRows="1" formControlName="body"></textarea>
@if (templateConfigurationForm.get('SLACK.body').hasError('required')) {
<mat-error>{{ 'notification.message-required' | translate }}</mat-error>
}
</mat-form-field>
</ng-template>
</mat-expansion-panel>
</section>
}
@if (templateConfigurationForm.get('MICROSOFT_TEAMS.enabled').value) {
<section class="tb-form-panel tb-slide-toggle stroked" [formGroupName]="NotificationDeliveryMethod.MICROSOFT_TEAMS">
<mat-expansion-panel class="tb-settings" expanded="{{ expandedForm(NotificationDeliveryMethod.MICROSOFT_TEAMS) }}">
<mat-expansion-panel-header class="fill-width flex flex-row flex-wrap">
<mat-panel-title class="template-tittle">
<tb-icon>{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MICROSOFT_TEAMS).icon }}</tb-icon>
{{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MICROSOFT_TEAMS).name | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject">
</mat-form-field>
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.message</mat-label>
<textarea matInput cdkTextareaAutosize cols="1" cdkAutosizeMinRows="1" formControlName="body"></textarea>
@if (templateConfigurationForm.get('MICROSOFT_TEAMS.body').hasError('required')) {
<mat-error>{{ 'notification.message-required' | translate }}</mat-error>
}
</mat-form-field>
<div class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between">
<div translate>notification.theme-color</div>
<tb-color-input asBoxInput formControlName="themeColor"></tb-color-input>
</div>
<tb-notification-action-button-configuration
actionTitle="{{ 'notification.action-button' | translate }}"
formControlName="button">
</tb-notification-action-button-configuration>
</div>
<tb-notification-action-button-configuration
actionTitle="{{ 'notification.action-button' | translate }}"
formControlName="button">
</tb-notification-action-button-configuration>
</div>
</ng-template>
</mat-expansion-panel>
</section>
</ng-template>
</mat-expansion-panel>
</section>
}
</section>

55
ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.html

@ -25,8 +25,11 @@
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
@if (isLoading$ | async) {
<mat-progress-bar color="warn" mode="indeterminate"></mat-progress-bar>
} @else {
<div style="height: 4px;"></div>
}
<div mat-dialog-content>
<mat-horizontal-stepper linear #notificationTemplateStepper
labelPosition="end"
@ -38,33 +41,37 @@
<mat-step [stepControl]="templateNotificationForm">
<ng-template matStepLabel>{{ 'notification.setup' | translate }}</ng-template>
<form [formGroup]="templateNotificationForm" style="padding-bottom: 16px;">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>notification.name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="templateNotificationForm.get('name').hasError('required')">
{{ 'notification.name-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block flex-1" *ngIf="!hideSelectType">
<mat-label translate>notification.type</mat-label>
<mat-select formControlName="notificationType">
<mat-option *ngFor="let notificationType of notificationTypes" [value]="notificationType">
{{ notificationTemplateTypeTranslateMap.get(notificationType).name | translate }}
</mat-option>
</mat-select>
@if (templateNotificationForm.get('name').hasError('required')) {
<mat-error>{{ 'notification.name-required' | translate }}</mat-error>
}
</mat-form-field>
@if (!hideSelectType) {
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>notification.type</mat-label>
<mat-select formControlName="notificationType">
@for (notificationType of notificationTypes; track notificationType) {
<mat-option [value]="notificationType">
{{ notificationTemplateTypeTranslateMap.get(notificationType).name | translate }}
</mat-option>
}
</mat-select>
</mat-form-field>
}
<section formGroupName="configuration">
<label [class.tb-error]="templateNotificationForm.get('configuration.deliveryMethodsTemplates').hasError('atLeastOne')"
class="tb-title tb-required">{{ "notification.delivery-methods" | translate }}</label>
<div class="tb-hint" translate>notification.at-least-one-should-be-selected</div>
<section formGroupName="deliveryMethodsTemplates" class="delivery-methods-container">
<section *ngFor="let deliveryMethods of notificationDeliveryMethods"
class="tb-form-panel stroked"
[formGroupName]="deliveryMethods">
<mat-slide-toggle formControlName="enabled">
{{ notificationDeliveryMethodInfoMap.get(deliveryMethods).name | translate }}
</mat-slide-toggle>
</section>
@for (deliveryMethods of notificationDeliveryMethods; track deliveryMethods) {
<section class="tb-form-panel stroked" [formGroupName]="deliveryMethods">
<mat-slide-toggle formControlName="enabled">
{{ notificationDeliveryMethodInfoMap.get(deliveryMethods).name | translate }}
</mat-slide-toggle>
</section>
}
</section>
</section>
</form>
@ -86,8 +93,10 @@
</div>
<mat-divider></mat-divider>
<div mat-dialog-actions class="tb-dialog-actions">
<button mat-stroked-button *ngIf="selectedIndex > 0"
(click)="backStep()">{{ 'action.back' | translate }}</button>
@if (selectedIndex > 0) {
<button mat-stroked-button
(click)="backStep()">{{ 'action.back' | translate }}</button>
}
<span class="flex-1"></span>
<button mat-raised-button
color="primary"

1
ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.scss

@ -92,6 +92,7 @@
width: 100%;;
overflow-y: auto;
scrollbar-gutter: stable;
padding-top: 4px;
@media #{$mat-gt-sm} {
min-width: 500px;
}

195
ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html

@ -65,34 +65,45 @@
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit" class="flex flex-col min-w-full">
<div class="flex flex-row xs:flex-col gt-xs:gap-2">
<mat-form-field class="mat-block flex-full gt-xs:max-w-45%">
<mat-form-field class="mat-block flex-full gt-xs:max-w-45%" appearance="outline">
<mat-label translate>ota-update.title</mat-label>
<input matInput formControlName="title" type="text" required [readonly]="!isAdd">
<mat-error *ngIf="entityForm.get('title').hasError('required')">
{{ 'ota-update.title-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('title').hasError('maxlength')">
{{ 'ota-update.title-max-length' | translate }}
</mat-error>
@if (entityForm.get('title').hasError('required')) {
<mat-error>
{{ 'ota-update.title-required' | translate }}
</mat-error>
}
@if (entityForm.get('title').hasError('maxlength')) {
<mat-error>
{{ 'ota-update.title-max-length' | translate }}
</mat-error>
}
</mat-form-field>
<mat-form-field class="mat-block flex-1">
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>ota-update.version</mat-label>
<input matInput formControlName="version" type="text" required [readonly]="!isAdd">
<mat-error *ngIf="entityForm.get('version').hasError('required')">
{{ 'ota-update.version-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('version').hasError('maxlength')">
{{ 'ota-update.version-max-length' | translate }}
</mat-error>
@if (entityForm.get('version').hasError('required')) {
<mat-error>
{{ 'ota-update.version-required' | translate }}
</mat-error>
}
@if (entityForm.get('version').hasError('maxlength')) {
<mat-error>
{{ 'ota-update.version-max-length' | translate }}
</mat-error>
}
</mat-form-field>
</div>
<mat-form-field class="mat-block flex-1" style="margin-bottom: 8px">
<mat-form-field class="mat-block flex-1" style="margin-bottom: 8px" appearance="outline">
<mat-label translate>ota-update.version-tag</mat-label>
<input matInput formControlName="tag" type="text" [readonly]="!isAdd">
<mat-hint *ngIf="isAdd" translate>ota-update.version-tag-hint</mat-hint>
@if (isAdd) {
<mat-hint translate>ota-update.version-tag-hint</mat-hint>
}
</mat-form-field>
<tb-device-profile-autocomplete
formControlName="deviceProfileId"
appearance="outline"
required
[class.pb-4]="isAdd"
[class.pb-0]="!isAdd"
@ -101,81 +112,99 @@
[addNewProfile]="false"
[showDetailsPageLink]="true">
</tb-device-profile-autocomplete>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>ota-update.package-type</mat-label>
<mat-select formControlName="type" required>
<mat-option *ngFor="let packageType of packageTypes" [value]="packageType">
{{ otaUpdateTypeTranslationMap.get(packageType) | translate }}
</mat-option>
@for ( packageType of packageTypes; track packageType) {
<mat-option [value]="packageType">
{{ otaUpdateTypeTranslationMap.get(packageType) | translate }}
</mat-option>
}
</mat-select>
</mat-form-field>
<section *ngIf="isAdd">
<div class="mat-caption" style="margin: -8px 0 8px;" translate>ota-update.warning-after-save-no-edit</div>
<mat-radio-group formControlName="isURL" class="gap-4">
<mat-radio-button [value]="false">{{ "ota-update.upload-binary-file" | translate }}</mat-radio-button>
<mat-radio-button [value]="true">{{ "ota-update.use-external-url" | translate }}</mat-radio-button>
</mat-radio-group>
</section>
<section *ngIf="!entityForm.get('isURL').value">
<section *ngIf="isAdd">
<tb-file-input
formControlName="file"
workFromFileObj="true"
label="{{ 'ota-update.package-file' | translate }}"
[required]="!entityForm.get('isURL').value"
dropLabel="{{'ota-update.drop-package-file-or' | translate}}">
</tb-file-input>
<mat-checkbox formControlName="generateChecksum" style="margin-top: 16px">
{{ 'ota-update.auto-generate-checksum' | translate }}
</mat-checkbox>
@if (isAdd) {
<section>
<div class="mat-caption" style="margin: -8px 0 8px;" translate>ota-update.warning-after-save-no-edit</div>
<mat-radio-group formControlName="isURL" class="gap-4">
<mat-radio-button [value]="false">{{ "ota-update.upload-binary-file" | translate }}</mat-radio-button>
<mat-radio-button [value]="true">{{ "ota-update.use-external-url" | translate }}</mat-radio-button>
</mat-radio-group>
</section>
<div class="flex flex-row xs:flex-col sm:gap-2 md:flex-col gt-xs:gap-2"
*ngIf="!(isAdd && entityForm.get('generateChecksum').value)">
<mat-form-field class="mat-block flex-full gt-xs:max-w-33%">
<mat-label translate>ota-update.checksum-algorithm</mat-label>
<mat-select formControlName="checksumAlgorithm">
<mat-option *ngFor="let checksumAlgorithm of checksumAlgorithms" [value]="checksumAlgorithm">
{{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block flex-1">
<mat-label translate>ota-update.checksum</mat-label>
<input matInput formControlName="checksum" type="text">
<mat-hint *ngIf="isAdd" translate>ota-update.checksum-hint</mat-hint>
}
@if (!entityForm.get('isURL').value) {
<section>
@if (isAdd) {
<section>
<tb-file-input
formControlName="file"
workFromFileObj="true"
label="{{ 'ota-update.package-file' | translate }}"
[required]="!entityForm.get('isURL').value"
dropLabel="{{'ota-update.drop-package-file-or' | translate}}">
</tb-file-input>
<mat-checkbox formControlName="generateChecksum" style="margin-top: 16px">
{{ 'ota-update.auto-generate-checksum' | translate }}
</mat-checkbox>
</section>
}
@if (!(isAdd && entityForm.get('generateChecksum').value)) {
<div class="flex flex-row xs:flex-col sm:gap-2 md:flex-col gt-xs:gap-2">
<mat-form-field class="mat-block flex-full gt-xs:max-w-33%" appearance="outline">
<mat-label translate>ota-update.checksum-algorithm</mat-label>
<mat-select formControlName="checksumAlgorithm">
@for ( checksumAlgorithm of checksumAlgorithms; track checksumAlgorithm) {
<mat-option [value]="checksumAlgorithm">
{{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>ota-update.checksum</mat-label>
<input matInput formControlName="checksum" type="text">
@if (isAdd) {
<mat-hint translate>ota-update.checksum-hint</mat-hint>
}
</mat-form-field>
</div>
}
@if (!isAdd) {
<section>
<div class="flex flex-row xs:flex-col sm:gap-2 md:flex-col gt-md:gap-2">
<mat-form-field class="mat-block flex-full gt-xs:max-w-33%" appearance="outline">
<mat-label translate>ota-update.file-name</mat-label>
<input matInput formControlName="fileName" type="text">
</mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>ota-update.file-size-bytes</mat-label>
<input matInput formControlName="dataSize" type="text">
</mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>ota-update.content-type</mat-label>
<input matInput formControlName="contentType" type="text">
</mat-form-field>
</div>
</section>
}
</section>
}
@if (entityForm.get('isURL').value) {
<section style="margin-top: 8px">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>ota-update.direct-url</mat-label>
<input matInput formControlName="url"
type="text"
[required]="entityForm.get('isURL').value">
@if (entityForm.get('url').hasError('required') || entityForm.get('url').hasError('pattern')) {
<mat-error translate>
ota-update.direct-url-required
</mat-error>
}
</mat-form-field>
</div>
<section *ngIf="!isAdd">
<div class="flex flex-row xs:flex-col sm:gap-2 md:flex-col gt-md:gap-2">
<mat-form-field class="mat-block flex-full gt-xs:max-w-33%">
<mat-label translate>ota-update.file-name</mat-label>
<input matInput formControlName="fileName" type="text">
</mat-form-field>
<mat-form-field class="mat-block flex-1">
<mat-label translate>ota-update.file-size-bytes</mat-label>
<input matInput formControlName="dataSize" type="text">
</mat-form-field>
<mat-form-field class="mat-block flex-1">
<mat-label translate>ota-update.content-type</mat-label>
<input matInput formControlName="contentType" type="text">
</mat-form-field>
</div>
</section>
</section>
<section *ngIf="entityForm.get('isURL').value" style="margin-top: 8px">
<mat-form-field class="mat-block">
<mat-label translate>ota-update.direct-url</mat-label>
<input matInput formControlName="url"
type="text"
[required]="entityForm.get('isURL').value">
<mat-error *ngIf="entityForm.get('url').hasError('required') || entityForm.get('url').hasError('pattern')"
translate>
ota-update.direct-url-required
</mat-error>
</mat-form-field>
</section>
}
<div formGroupName="additionalInfo">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>ota-update.description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>

60
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html

@ -23,16 +23,19 @@
</div>
<form [formGroup]="ruleNodeFormGroup" class="mat-padding">
<section class="title-row align-center flex">
<mat-form-field class="mat-block flex-1">
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>rulenode.name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="ruleNodeFormGroup.get('name').hasError('required')
|| ruleNodeFormGroup.get('name').hasError('pattern')">
{{ 'rulenode.name-required' | translate }}
</mat-error>
<mat-error *ngIf="ruleNodeFormGroup.get('name').hasError('maxlength')">
{{ 'rulenode.name-max-length' | translate }}
</mat-error>
@if (ruleNodeFormGroup.get('name').hasError('required') || ruleNodeFormGroup.get('name').hasError('pattern')) {
<mat-error>
{{ 'rulenode.name-required' | translate }}
</mat-error>
}
@if (ruleNodeFormGroup.get('name').hasError('maxlength')) {
<mat-error>
{{ 'rulenode.name-max-length' | translate }}
</mat-error>
}
</mat-form-field>
<section class="mb-5 flex max-w-xs flex-row">
<tb-entity-debug-settings-button
@ -41,17 +44,18 @@
formControlName="debugSettings"
[entityType]="entityType.RULE_NODE"
/>
<button mat-stroked-button
class="tb-rounded-btn flex-1"
color="primary"
*ngIf="isSingleton()"
[disabled]="ruleNodeFormGroup.get('singletonMode').disabled || disabled"
[class.active]="ruleNodeFormGroup.get('singletonMode').value"
(click)="onSingleModeChange($event)">
<mat-icon [class.invisible]="!ruleNodeFormGroup.get('singletonMode').value">checkmark</mat-icon>
<span translate>rulenode.singleton</span>
<input class="hidden" formControlName="singletonMode">
</button>
@if (isSingleton()) {
<button mat-stroked-button
class="tb-rounded-btn flex-1"
color="primary"
[disabled]="ruleNodeFormGroup.get('singletonMode').disabled || disabled"
[class.active]="ruleNodeFormGroup.get('singletonMode').value"
(click)="onSingleModeChange($event)">
<mat-icon [class.invisible]="!ruleNodeFormGroup.get('singletonMode').value">checkmark</mat-icon>
<span translate>rulenode.singleton</span>
<input class="hidden" formControlName="singletonMode">
</button>
}
</section>
</section>
<tb-rule-node-config #ruleNodeConfigComponent
@ -65,15 +69,17 @@
</tb-rule-node-config>
<div class="description-block">
<tb-queue-autocomplete
*ngIf="isAddQueue()"
[queueType]="serviceType"
subscriptSizing="dynamic"
[autocompleteHint]="isSingleton() ? 'rulenode.queue-singleton-hint' : 'rulenode.queue-hint'"
formControlName="queueName">
</tb-queue-autocomplete>
@if (isAddQueue()) {
<tb-queue-autocomplete
[queueType]="serviceType"
appearance="outline"
subscriptSizing="dynamic"
[autocompleteHint]="isSingleton() ? 'rulenode.queue-singleton-hint' : 'rulenode.queue-hint'"
formControlName="queueName">
</tb-queue-autocomplete>
}
<div formGroupName="additionalInfo">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rulenode.rule-node-description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>

18
ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.html

@ -77,22 +77,22 @@
</div>
<div class="mat-padding flex flex-col">
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rulechain.name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="entityForm.get('name').hasError('required')">
{{ 'rulechain.name-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
{{ 'rulechain.name-max-length' | translate }}
</mat-error>
@if (entityForm.get('name').hasError('required')) {
<mat-error>{{ 'rulechain.name-required' | translate }}</mat-error>
}
@if (entityForm.get('name').hasError('maxlength')) {
<mat-error>{{ 'rulechain.name-max-length' | translate }}</mat-error>
}
</mat-form-field>
<mat-checkbox class="flex-1" formControlName="debugMode" style="padding-bottom: 16px;">
{{ 'rulechain.debug-mode' | translate }}
</mat-checkbox>
<div formGroupName="additionalInfo">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rulechain.description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field>

21
ui-ngx/src/app/modules/home/pages/widget/widget-type.component.html

@ -44,15 +44,19 @@
<div class="mat-padding flex flex-col">
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>widget.title</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="entityForm.get('name').hasError('required')">
{{ 'widget.title-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('name').hasError('maxlength')">
{{ 'widget.title-max-length' | translate }}
</mat-error>
@if (entityForm.get('name').hasError('required')) {
<mat-error>
{{ 'widget.title-required' | translate }}
</mat-error>
}
@if (entityForm.get('name').hasError('maxlength')) {
<mat-error>
{{ 'widget.title-max-length' | translate }}
</mat-error>
}
</mat-form-field>
<tb-gallery-image-input class="flex-1"
label="{{'widget.image-preview' | translate}}"
@ -60,6 +64,7 @@
</tb-gallery-image-input>
<tb-string-items-list
label="{{ 'widget.tags' | translate }}"
appearance="outline"
formControlName="tags">
</tb-string-items-list>
<div class="flex flex-col gap-4">
@ -70,7 +75,7 @@
{{ 'widget.deprecated' | translate }}
</mat-slide-toggle>
</div>
<mat-form-field class="mat-block mt-5">
<mat-form-field class="mat-block mt-5" appearance="outline">
<mat-label translate>widget.description</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1" maxlength="1024" #descriptionInput></textarea>
<mat-hint align="end">{{descriptionInput.value?.length || 0}}/1024</mat-hint>

38
ui-ngx/src/app/shared/components/contact.component.html

@ -17,54 +17,56 @@
-->
<section [formGroup]="parentForm">
<tb-country-autocomplete
appearance="outline"
formControlName="country"
(selectCountryCode)="changeCountry($event)">
</tb-country-autocomplete>
<div class="tb-form-row column-xs tb-standard-fields no-border no-padding">
<mat-form-field class="flex" subscriptSizing="dynamic">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>contact.city</mat-label>
<input matInput formControlName="city">
<mat-error *ngIf="parentForm.get('city').hasError('maxlength')">
{{ 'contact.city-max-length' | translate }}
</mat-error>
@if (parentForm.get('city').hasError('maxlength')) {
<mat-error>{{ 'contact.city-max-length' | translate }}</mat-error>
}
<mat-hint></mat-hint>
</mat-form-field>
<mat-form-field class="flex" subscriptSizing="dynamic">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>contact.state</mat-label>
<input matInput formControlName="state">
<mat-error *ngIf="parentForm.get('state').hasError('maxlength')">
{{ 'contact.state-max-length' | translate }}
</mat-error>
@if (parentForm.get('state').hasError('maxlength')) {
<mat-error>{{ 'contact.state-max-length' | translate }}</mat-error>
}
<mat-hint></mat-hint>
</mat-form-field>
<mat-form-field class="flex" subscriptSizing="dynamic">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>contact.postal-code</mat-label>
<input matInput formControlName="zip">
<mat-error *ngIf="parentForm.get('zip').hasError('pattern')">
{{ 'contact.postal-code-invalid' | translate }}
</mat-error>
@if (parentForm.get('zip').hasError('pattern')) {
<mat-error>{{ 'contact.postal-code-invalid' | translate }}</mat-error>
}
<mat-hint></mat-hint>
</mat-form-field>
</div>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>contact.address</mat-label>
<input matInput formControlName="address">
</mat-form-field>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>contact.address2</mat-label>
<input matInput formControlName="address2">
</mat-form-field>
<tb-phone-input [required]="false"
[defaultCountry]="phoneInputDefaultCountry"
label="{{ 'contact.phone' | translate }}"
appearance="outline"
[enableFlagsSelect]="true"
formControlName="phone">
</tb-phone-input>
<mat-form-field class="mat-block">
<mat-form-field class="mat-block" appearance="outline">
<mat-label translate>contact.email</mat-label>
<input matInput type="email" formControlName="email">
<mat-error *ngIf="parentForm.get('email').hasError('email')">
{{ 'user.invalid-email-format' | translate }}
</mat-error>
@if (parentForm.get('email').hasError('email')) {
<mat-error>{{ 'user.invalid-email-format' | translate }}</mat-error>
}
</mat-form-field>
</section>

2
ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<mat-form-field [formGroup]="selectDashboardStateFormGroup" class="mat-block">
<mat-form-field [formGroup]="selectDashboardStateFormGroup" class="mat-block" [appearance]="appearance">
<mat-label>{{ label }}</mat-label>
<input matInput type="text" placeholder="{{ placeholder }}"
#dashboardStateInput

5
ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.ts

@ -24,7 +24,7 @@ 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 { FloatLabelType } from '@angular/material/form-field';
import { MatFormFieldAppearance } from '@angular/material/form-field';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
@Component({
@ -51,6 +51,9 @@ export class DashboardStateAutocompleteComponent implements ControlValueAccessor
@Input()
label: string = this.translate.instant('widget-action.target-dashboard-state');
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Input()
placeholder: string;

2
ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<mat-form-field [formGroup]="keysListFormGroup" class="mat-block tb-chip-list">
<mat-form-field [formGroup]="keysListFormGroup" class="mat-block tb-chip-list" [appearance]="appearance">
<mat-chip-grid #chipList>
<mat-chip-row
*ngFor="let key of modelValue"

4
ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts

@ -29,6 +29,7 @@ import { MatChipInputEvent, MatChipGrid } from '@angular/material/chips';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { isEqual } from '@core/utils';
import { MatFormFieldAppearance } from '@angular/material/form-field';
@Component({
selector: 'tb-entity-keys-list',
@ -65,6 +66,9 @@ export class EntityKeysListComponent implements ControlValueAccessor, OnInit, Af
@Input()
dataKeyType: DataKeyType;
@Input()
appearance: MatFormFieldAppearance = 'fill';
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;

2
ui-ngx/src/app/shared/components/notification/template-autocomplete.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<mat-form-field [formGroup]="selectTemplateFormGroup" class="mat-block">
<mat-form-field [formGroup]="selectTemplateFormGroup" class="mat-block" [appearance]="appearance">
<mat-label translate>notification.template</mat-label>
<input matInput type="text"
#templateInput

5
ui-ngx/src/app/shared/components/notification/template-autocomplete.component.ts

@ -41,6 +41,7 @@ import {
import { MatDialog } from '@angular/material/dialog';
import { MatButton } from '@angular/material/button';
import { coerceBoolean } from '@shared/decorators/coercion';
import { MatFormFieldAppearance } from '@angular/material/form-field';
@Component({
selector: 'tb-template-autocomplete',
@ -71,6 +72,8 @@ export class TemplateAutocompleteComponent implements ControlValueAccessor, OnIn
@coerceBoolean()
allowEdit = false;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Input()
disabled: boolean;
@ -217,7 +220,7 @@ export class TemplateAutocompleteComponent implements ControlValueAccessor, OnIn
button._elementRef.nativeElement.blur();
this.createTemplateByName($event);
}
createTemplateByName($event: Event, name?: string) {
$event?.stopPropagation();
this.openNotificationTemplateDialog({

2
ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<mat-form-field [formGroup]="otaPackageFormGroup" class="mat-block">
<mat-form-field [formGroup]="otaPackageFormGroup" class="mat-block" [appearance]="appearance">
<mat-label>{{ placeholderText | translate }}</mat-label>
<input matInput type="text"
#packageInput

4
ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts

@ -35,6 +35,7 @@ import { getEntityDetailsPageURL, isDefinedAndNotNull } from '@core/utils';
import { AuthUser } from '@shared/models/user.model';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum';
import { MatFormFieldAppearance } from '@angular/material/form-field';
@Component({
selector: 'tb-ota-package-autocomplete',
@ -93,6 +94,9 @@ export class OtaPackageAutocompleteComponent implements ControlValueAccessor, On
@Input()
showDetailsPageLink = false;
@Input()
appearance: MatFormFieldAppearance = 'fill';
private requiredValue: boolean;
get required(): boolean {

2
ui-ngx/src/app/shared/components/queue/queue-autocomplete.component.html

@ -16,7 +16,7 @@
-->
<mat-form-field [formGroup]="selectQueueFormGroup"
class="mat-block autocomplete-queue" subscriptSizing="{{subscriptSizing}}">
class="mat-block autocomplete-queue" [subscriptSizing]="subscriptSizing" [appearance]="appearance">
<mat-label>{{ 'queue.queue-name' | translate }}</mat-label>
<input matInput type="text"
#queueInput

5
ui-ngx/src/app/shared/components/queue/queue-autocomplete.component.ts

@ -31,7 +31,7 @@ import { QueueService } from '@core/http/queue.service';
import { PageLink } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order';
import { emptyPageData } from '@shared/models/page/page-data';
import { SubscriptSizing } from '@angular/material/form-field';
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
@Component({
selector: 'tb-queue-autocomplete',
@ -77,6 +77,9 @@ export class QueueAutocompleteComponent implements ControlValueAccessor, OnInit
@Input()
disabled: boolean;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@ViewChild('queueInput', {static: true}) queueInput: ElementRef;
filteredQueues: Observable<Array<BaseData<EntityId>>>;

2
ui-ngx/src/app/shared/components/slack-conversation-autocomplete.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<mat-form-field [formGroup]="conversationSlackFormGroup" class="mat-block">
<mat-form-field [formGroup]="conversationSlackFormGroup" class="mat-block" [appearance]="appearance">
<mat-label>{{ labelText }}</mat-label>
<input matInput type="text"
#slackInput

4
ui-ngx/src/app/shared/components/slack-conversation-autocomplete.component.ts

@ -27,6 +27,7 @@ import { TruncatePipe } from '@shared/pipe/truncate.pipe';
import { SlackChanelType, SlackConversation } from '@shared/models/notification.models';
import { NotificationService } from '@core/http/notification.service';
import { isEqual } from '@core/utils';
import { MatFormFieldAppearance } from '@angular/material/form-field';
@Component({
selector: 'tb-slack-conversation-autocomplete',
@ -67,6 +68,9 @@ export class SlackConversationAutocompleteComponent implements ControlValueAcces
@Input()
token: string;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@ViewChild('slackInput', {static: true}) slackInput: ElementRef;
slackConversation$: Observable<Array<SlackConversation>>;

Loading…
Cancel
Save