@c8y/ngx-components 1023.92.0 → 1023.96.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/ai/agents/scada/index.d.ts +10 -0
  2. package/ai/agents/scada/index.d.ts.map +1 -0
  3. package/ai/ai-chat/index.d.ts +1 -1
  4. package/ai/ai-chat/index.d.ts.map +1 -1
  5. package/computed-asset-properties/index.d.ts +89 -1
  6. package/computed-asset-properties/index.d.ts.map +1 -1
  7. package/context-dashboard/index.d.ts +18 -8
  8. package/context-dashboard/index.d.ts.map +1 -1
  9. package/fesm2022/c8y-ngx-components-ai-agent-chat.mjs +2 -2
  10. package/fesm2022/c8y-ngx-components-ai-agent-chat.mjs.map +1 -1
  11. package/fesm2022/c8y-ngx-components-ai-agents-scada.mjs +893 -0
  12. package/fesm2022/c8y-ngx-components-ai-agents-scada.mjs.map +1 -0
  13. package/fesm2022/c8y-ngx-components-ai-ai-chat.mjs +1 -0
  14. package/fesm2022/c8y-ngx-components-ai-ai-chat.mjs.map +1 -1
  15. package/fesm2022/c8y-ngx-components-asset-properties.mjs +2 -2
  16. package/fesm2022/c8y-ngx-components-asset-properties.mjs.map +1 -1
  17. package/fesm2022/{c8y-ngx-components-computed-asset-properties-c8y-ngx-components-computed-asset-properties-Cy9R6Ixb.mjs → c8y-ngx-components-computed-asset-properties-c8y-ngx-components-computed-asset-properties-CHZX7ALq.mjs} +10 -9
  18. package/fesm2022/c8y-ngx-components-computed-asset-properties-c8y-ngx-components-computed-asset-properties-CHZX7ALq.mjs.map +1 -0
  19. package/fesm2022/{c8y-ngx-components-computed-asset-properties-fieldbus-item-status-config.component-LrmlAh6V.mjs → c8y-ngx-components-computed-asset-properties-fieldbus-item-status-config.component-DgWGXzEx.mjs} +2 -2
  20. package/fesm2022/{c8y-ngx-components-computed-asset-properties-fieldbus-item-status-config.component-LrmlAh6V.mjs.map → c8y-ngx-components-computed-asset-properties-fieldbus-item-status-config.component-DgWGXzEx.mjs.map} +1 -1
  21. package/fesm2022/{c8y-ngx-components-computed-asset-properties-last-measurement-config.component-f1u1TcCl.mjs → c8y-ngx-components-computed-asset-properties-last-measurement-config.component-yfc45-l_.mjs} +2 -2
  22. package/fesm2022/{c8y-ngx-components-computed-asset-properties-last-measurement-config.component-f1u1TcCl.mjs.map → c8y-ngx-components-computed-asset-properties-last-measurement-config.component-yfc45-l_.mjs.map} +1 -1
  23. package/fesm2022/c8y-ngx-components-computed-asset-properties.mjs +1 -1
  24. package/fesm2022/c8y-ngx-components-context-dashboard.mjs +44 -36
  25. package/fesm2022/c8y-ngx-components-context-dashboard.mjs.map +1 -1
  26. package/fesm2022/c8y-ngx-components-global-context.mjs +112 -22
  27. package/fesm2022/c8y-ngx-components-global-context.mjs.map +1 -1
  28. package/fesm2022/c8y-ngx-components-repository-configuration.mjs +292 -115
  29. package/fesm2022/c8y-ngx-components-repository-configuration.mjs.map +1 -1
  30. package/fesm2022/c8y-ngx-components-repository-firmware.mjs +2 -2
  31. package/fesm2022/c8y-ngx-components-repository-firmware.mjs.map +1 -1
  32. package/fesm2022/c8y-ngx-components-repository-software.mjs +1 -1
  33. package/fesm2022/c8y-ngx-components-repository-software.mjs.map +1 -1
  34. package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget.mjs +2 -1
  35. package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget.mjs.map +1 -1
  36. package/fesm2022/c8y-ngx-components-widgets-definitions-scada.mjs +169 -0
  37. package/fesm2022/c8y-ngx-components-widgets-definitions-scada.mjs.map +1 -0
  38. package/fesm2022/c8y-ngx-components-widgets-definitions.mjs +2 -1
  39. package/fesm2022/c8y-ngx-components-widgets-definitions.mjs.map +1 -1
  40. package/fesm2022/c8y-ngx-components-widgets-exports.mjs +8 -1
  41. package/fesm2022/c8y-ngx-components-widgets-exports.mjs.map +1 -1
  42. package/fesm2022/c8y-ngx-components-widgets-implementations-alarms.mjs +2 -2
  43. package/fesm2022/c8y-ngx-components-widgets-implementations-alarms.mjs.map +1 -1
  44. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-graph.mjs +3 -3
  45. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-graph.mjs.map +1 -1
  46. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-list.mjs +2 -2
  47. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-list.mjs.map +1 -1
  48. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-table.mjs +2 -2
  49. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-table.mjs.map +1 -1
  50. package/fesm2022/c8y-ngx-components-widgets-implementations-events.mjs +2 -2
  51. package/fesm2022/c8y-ngx-components-widgets-implementations-events.mjs.map +1 -1
  52. package/fesm2022/c8y-ngx-components-widgets-implementations-html-widget.mjs +90 -32
  53. package/fesm2022/c8y-ngx-components-widgets-implementations-html-widget.mjs.map +1 -1
  54. package/fesm2022/c8y-ngx-components-widgets-implementations-kpi.mjs +2 -2
  55. package/fesm2022/c8y-ngx-components-widgets-implementations-kpi.mjs.map +1 -1
  56. package/fesm2022/c8y-ngx-components-widgets-implementations-scada.mjs +1698 -0
  57. package/fesm2022/c8y-ngx-components-widgets-implementations-scada.mjs.map +1 -0
  58. package/fesm2022/c8y-ngx-components.mjs +190 -40
  59. package/fesm2022/c8y-ngx-components.mjs.map +1 -1
  60. package/global-context/index.d.ts +58 -3
  61. package/global-context/index.d.ts.map +1 -1
  62. package/index.d.ts +74 -22
  63. package/index.d.ts.map +1 -1
  64. package/locales/de.po +108 -0
  65. package/locales/es.po +108 -0
  66. package/locales/fr.po +108 -0
  67. package/locales/ja_JP.po +108 -0
  68. package/locales/ko.po +108 -0
  69. package/locales/locales.pot +150 -3
  70. package/locales/nl.po +108 -0
  71. package/locales/pl.po +108 -0
  72. package/locales/pt_BR.po +108 -0
  73. package/locales/zh_CN.po +108 -0
  74. package/locales/zh_TW.po +108 -0
  75. package/package.json +1 -1
  76. package/repository/configuration/index.d.ts +64 -39
  77. package/repository/configuration/index.d.ts.map +1 -1
  78. package/widgets/cockpit-exports/index.d.ts +6 -0
  79. package/widgets/cockpit-exports/index.d.ts.map +1 -1
  80. package/widgets/definitions/index.d.ts +1 -0
  81. package/widgets/definitions/index.d.ts.map +1 -1
  82. package/widgets/definitions/scada/index.d.ts +6 -0
  83. package/widgets/definitions/scada/index.d.ts.map +1 -0
  84. package/widgets/device-management-exports/index.d.ts +6 -0
  85. package/widgets/device-management-exports/index.d.ts.map +1 -1
  86. package/widgets/exports/index.d.ts +8 -1
  87. package/widgets/exports/index.d.ts.map +1 -1
  88. package/widgets/implementations/html-widget/index.d.ts +30 -12
  89. package/widgets/implementations/html-widget/index.d.ts.map +1 -1
  90. package/widgets/implementations/scada/index.d.ts +415 -0
  91. package/widgets/implementations/scada/index.d.ts.map +1 -0
  92. package/fesm2022/c8y-ngx-components-computed-asset-properties-c8y-ngx-components-computed-asset-properties-Cy9R6Ixb.mjs.map +0 -1
@@ -1,24 +1,26 @@
1
1
  import * as i0 from '@angular/core';
2
- import { EventEmitter, Injectable, Component, Pipe, Input, Output, ViewChild, NgModule } from '@angular/core';
2
+ import { EventEmitter, Injectable, Component, Pipe, Input, Output, ViewChild, NgModule, inject, ChangeDetectorRef, signal, computed, ChangeDetectionStrategy } from '@angular/core';
3
3
  import * as i1 from '@angular/router';
4
4
  import * as i4 from '@c8y/client';
5
- import { OperationStatus } from '@c8y/client';
5
+ import { OperationStatus, InventoryBinaryService } from '@c8y/client';
6
6
  import { gettext } from '@c8y/ngx-components/gettext';
7
7
  import * as i2 from '@c8y/ngx-components';
8
- import { IconDirective, C8yTranslatePipe, C8yTranslateDirective, FormGroupComponent, RequiredInputPlaceholderDirective, Permissions, DatePipe, FilterInputComponent, ActionBarItemComponent, TabsetAriaDirective, EmptyStateComponent, hookRoute, ViewContext, ValidationPattern, TypeaheadComponent, ForOfDirective, ListItemComponent, HighlightComponent, FilePickerComponent, BuiltInActionType, Status, TitleComponent, BreadcrumbComponent, BreadcrumbItemComponent, HelpComponent, DataGridComponent, EmptyStateContextDirective, GuideDocsComponent, GuideHrefDirective, NavigatorNode, hookNavigator, CoreModule, FormsModule as FormsModule$1 } from '@c8y/ngx-components';
8
+ import { IconDirective, C8yTranslatePipe, C8yTranslateDirective, FormGroupComponent, RequiredInputPlaceholderDirective, Permissions, DatePipe, FilterInputComponent, ActionBarItemComponent, TabsetAriaDirective, EmptyStateComponent, hookRoute, ViewContext, BottomDrawerRef, AlertService, ValidationPattern, isBinaryFile, TypeaheadComponent, ForOfDirective, ListItemComponent, HighlightComponent, FilePickerComponent, MessagesComponent, SelectComponent, DataGridService, BottomDrawerService, ModalService, BuiltInActionType, Status, TitleComponent, BreadcrumbComponent, BreadcrumbItemComponent, HelpComponent, DataGridComponent, EmptyStateContextDirective, GuideDocsComponent, GuideHrefDirective, NavigatorNode, hookNavigator, CoreModule, FormsModule as FormsModule$1 } from '@c8y/ngx-components';
9
9
  import * as i3 from '@c8y/ngx-components/repository/shared';
10
- import { DeviceConfigurationOperation, RepositoryType, SharedRepositoryModule, RepositoryItemNameGridColumn, DescriptionGridColumn, FileGridColumn, DeviceTypeGridColumn, TypeGridColumn } from '@c8y/ngx-components/repository/shared';
10
+ import { DeviceConfigurationOperation, RepositoryType, SharedRepositoryModule, RepositoryService, RepositoryItemNameGridColumn, DescriptionGridColumn, FileGridColumn, DeviceTypeGridColumn, TypeGridColumn } from '@c8y/ngx-components/repository/shared';
11
11
  import { NgIf, NgClass, NgFor, AsyncPipe } from '@angular/common';
12
12
  import * as i4$1 from '@angular/forms';
13
- import { FormsModule } from '@angular/forms';
13
+ import { FormsModule, FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
14
14
  import { OperationDetailsComponent, OperationDetailsModule } from '@c8y/ngx-components/operations/operation-details';
15
- import { has, cloneDeep, uniqBy, isUndefined } from 'lodash-es';
15
+ import { has, cloneDeep, uniqBy } from 'lodash-es';
16
16
  import { saveAs } from 'file-saver';
17
- import * as i3$1 from 'ngx-bootstrap/modal';
17
+ import * as i1$1 from 'ngx-bootstrap/modal';
18
18
  import { map } from 'rxjs/operators';
19
- import * as i1$1 from 'ngx-bootstrap/tabs';
19
+ import * as i1$2 from 'ngx-bootstrap/tabs';
20
20
  import { TabsetComponent, TabDirective, TabsModule } from 'ngx-bootstrap/tabs';
21
- import * as i4$2 from '@ngx-translate/core';
21
+ import { TranslateService } from '@ngx-translate/core';
22
+ import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
23
+ import { EditorComponent, MonacoEditorMarkerValidatorDirective } from '@c8y/ngx-components/editor';
22
24
  import { pipe } from 'rxjs';
23
25
 
24
26
  class DeviceConfigurationService {
@@ -232,7 +234,7 @@ class SaveToRepositoryComponent {
232
234
  this._cancel();
233
235
  this.modal.hide();
234
236
  }
235
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SaveToRepositoryComponent, deps: [{ token: i3$1.BsModalRef }, { token: i2.AlertService }, { token: i3.RepositoryService }], target: i0.ɵɵFactoryTarget.Component }); }
237
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SaveToRepositoryComponent, deps: [{ token: i1$1.BsModalRef }, { token: i2.AlertService }, { token: i3.RepositoryService }], target: i0.ɵɵFactoryTarget.Component }); }
236
238
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: SaveToRepositoryComponent, isStandalone: true, selector: "c8y-save-config-to-configuration-repository", ngImport: i0, template: "<div class=\"modal-header dialog-header\">\n <i c8yIcon=\"gears\"></i>\n <h4 id=\"modal-title\" translate>\n Save configuration\n </h4>\n</div>\n<div class=\"modal-body\" id=\"modal-body\">\n <form #saveConfigurationSnapshot=\"ngForm\" class=\"p-t-24\">\n <c8y-form-group>\n <label translate for=\"name\">Name</label>\n <input\n id=\"name\"\n type=\"text\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"name\"\n [(ngModel)]=\"configSnapshot.name\"\n required\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"deviceType\">Device type</label>\n <input\n id=\"deviceType\"\n class=\"form-control\"\n rows=\"6\"\n name=\"deviceType\"\n [(ngModel)]=\"configSnapshot.deviceType\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"description\">Description</label>\n <input\n type=\"text\"\n id=\"description\"\n class=\"form-control\"\n maxlength=\"254\"\n autocomplete=\"off\"\n name=\"description\"\n [(ngModel)]=\"configSnapshot.description\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"configurationType\">Configuration type</label>\n <input\n id=\"configurationType\"\n class=\"form-control\"\n rows=\"6\"\n name=\"configurationType\"\n [(ngModel)]=\"configSnapshot.configurationType\"\n />\n </c8y-form-group>\n </form>\n</div>\n<div class=\"modal-footer\">\n <button title=\"{{ 'Cancel' | translate }}\" class=\"btn btn-default\" (click)=\"close()\" translate>\n Cancel\n </button>\n\n <button\n title=\"{{ 'Save configuration to repository' | translate }}\"\n class=\"btn btn-primary\"\n (click)=\"save()\"\n [disabled]=\"saveConfigurationSnapshot.form.invalid\"\n translate\n >\n Save\n </button>\n</div>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i4$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
237
239
  }
238
240
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SaveToRepositoryComponent, decorators: [{
@@ -245,7 +247,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
245
247
  RequiredInputPlaceholderDirective,
246
248
  C8yTranslatePipe
247
249
  ], template: "<div class=\"modal-header dialog-header\">\n <i c8yIcon=\"gears\"></i>\n <h4 id=\"modal-title\" translate>\n Save configuration\n </h4>\n</div>\n<div class=\"modal-body\" id=\"modal-body\">\n <form #saveConfigurationSnapshot=\"ngForm\" class=\"p-t-24\">\n <c8y-form-group>\n <label translate for=\"name\">Name</label>\n <input\n id=\"name\"\n type=\"text\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"name\"\n [(ngModel)]=\"configSnapshot.name\"\n required\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"deviceType\">Device type</label>\n <input\n id=\"deviceType\"\n class=\"form-control\"\n rows=\"6\"\n name=\"deviceType\"\n [(ngModel)]=\"configSnapshot.deviceType\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"description\">Description</label>\n <input\n type=\"text\"\n id=\"description\"\n class=\"form-control\"\n maxlength=\"254\"\n autocomplete=\"off\"\n name=\"description\"\n [(ngModel)]=\"configSnapshot.description\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label translate for=\"configurationType\">Configuration type</label>\n <input\n id=\"configurationType\"\n class=\"form-control\"\n rows=\"6\"\n name=\"configurationType\"\n [(ngModel)]=\"configSnapshot.configurationType\"\n />\n </c8y-form-group>\n </form>\n</div>\n<div class=\"modal-footer\">\n <button title=\"{{ 'Cancel' | translate }}\" class=\"btn btn-default\" (click)=\"close()\" translate>\n Cancel\n </button>\n\n <button\n title=\"{{ 'Save configuration to repository' | translate }}\"\n class=\"btn btn-primary\"\n (click)=\"save()\"\n [disabled]=\"saveConfigurationSnapshot.form.invalid\"\n translate\n >\n Save\n </button>\n</div>\n" }]
248
- }], ctorParameters: () => [{ type: i3$1.BsModalRef }, { type: i2.AlertService }, { type: i3.RepositoryService }] });
250
+ }], ctorParameters: () => [{ type: i1$1.BsModalRef }, { type: i2.AlertService }, { type: i3.RepositoryService }] });
249
251
 
250
252
  class SourceCodePreviewComponent {
251
253
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SourceCodePreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
@@ -407,7 +409,7 @@ class ConfigurationPreviewComponent {
407
409
  this.deviceConfigurationService.updateConfigurations();
408
410
  }
409
411
  }
410
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigurationPreviewComponent, deps: [{ token: DeviceConfigurationService }, { token: i2.OperationRealtimeService }, { token: i3$1.BsModalService }, { token: i4.UserService }, { token: i2.AppStateService }, { token: i3.RepositoryService }, { token: i4.OperationService }, { token: i2.AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
412
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigurationPreviewComponent, deps: [{ token: DeviceConfigurationService }, { token: i2.OperationRealtimeService }, { token: i1$1.BsModalService }, { token: i4.UserService }, { token: i2.AppStateService }, { token: i3.RepositoryService }, { token: i4.OperationService }, { token: i2.AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
411
413
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: ConfigurationPreviewComponent, isStandalone: true, selector: "c8y-device-configuration-preview", inputs: { device: "device", configurationType: "configurationType", configSnapshot: "configSnapshot", canSaveSnapshot: "canSaveSnapshot", actionButtonText: "actionButtonText", actionButtonIcon: "actionButtonIcon", isLegacy: "isLegacy", operationToTrigger: "operationToTrigger" }, ngImport: i0, template: "<div class=\"content-flex-55 p-b-16\">\n <div class=\"col-7 p-t-4\">\n <p>\n <span class=\"text-label-small text-uppercase m-r-4\" translate>Configuration</span>\n <span *ngIf=\"configSnapshot?.name; else emptyText\">\n <strong>{{ configSnapshot.name }}</strong>\n </span>\n <ng-template #emptyText>---</ng-template>\n </p>\n <p>\n <span class=\"text-label-small text-uppercase m-r-4\" translate>Last updated</span>\n <small *ngIf=\"configSnapshot?.time; else emptyDate\">\n {{ configSnapshot.time | c8yDate }}\n </small>\n <ng-template #emptyDate>---</ng-template>\n </p>\n </div>\n <div class=\"col-5\">\n <button\n id=\"action-btn\"\n class=\"btn btn-default btn-sm pull-right\"\n type=\"button\"\n title=\"{{ actionButtonText | translate }}\"\n (click)=\"createDeviceOperation()\"\n [disabled]=\"isCreateOperationDisabled()\"\n *ngIf=\"canCallAction\"\n >\n <i [c8yIcon]=\"actionButtonIcon\"></i>\n {{ actionButtonText | translate }}\n </button>\n </div>\n</div>\n<div class=\"c8y-empty-state text-left\" *ngIf=\"!configSnapshot?.binary && showBinary()\">\n <h1 [c8yIcon]=\"'file-image-o'\"></h1>\n <p>\n <strong translate>No preview available.</strong>\n <br />\n <small *ngIf=\"configSnapshot?.binary !== ''; else emptyFile\" translate>\n The file is not available.\n </small>\n <ng-template #emptyFile>\n <small translate>The file is empty.</small>\n </ng-template>\n </p>\n</div>\n<div *ngIf=\"configSnapshot?.binary && showBinary()\" class=\"flex-grow d-flex d-col\">\n <c8y-source-code-preview\n [text]=\"configSnapshot.binary\"\n [isDisabled]=\"true\"\n class=\"d-contents\"\n ></c8y-source-code-preview>\n <div *ngIf=\"canSaveSnapshot\" class=\"p-t-16\">\n <button\n title=\"{{ 'Download' | translate }}\"\n type=\"button\"\n class=\"btn btn-primary btn-sm pull-right m-l-8\"\n (click)=\"download()\"\n >\n {{ 'Download' | translate }}\n </button>\n <button\n title=\"{{ 'Save to repository' | translate }}\"\n *ngIf=\"hasPermission()\"\n type=\"button\"\n class=\"btn btn-default btn-sm pull-right\"\n (click)=\"saveToRepository()\"\n >\n {{ 'Save to repository' | translate }}\n </button>\n </div>\n</div>\n<div *ngIf=\"showOperation()\">\n <c8y-operation-details [operation]=\"operation\"></c8y-operation-details>\n</div>\n", dependencies: [{ kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: SourceCodePreviewComponent, selector: "c8y-source-code-preview", inputs: ["isDisabled", "text"] }, { kind: "component", type: OperationDetailsComponent, selector: "c8y-operation-details", inputs: ["operation"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: DatePipe, name: "c8yDate" }] }); }
412
414
  }
413
415
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigurationPreviewComponent, decorators: [{
@@ -421,7 +423,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
421
423
  C8yTranslatePipe,
422
424
  DatePipe
423
425
  ], template: "<div class=\"content-flex-55 p-b-16\">\n <div class=\"col-7 p-t-4\">\n <p>\n <span class=\"text-label-small text-uppercase m-r-4\" translate>Configuration</span>\n <span *ngIf=\"configSnapshot?.name; else emptyText\">\n <strong>{{ configSnapshot.name }}</strong>\n </span>\n <ng-template #emptyText>---</ng-template>\n </p>\n <p>\n <span class=\"text-label-small text-uppercase m-r-4\" translate>Last updated</span>\n <small *ngIf=\"configSnapshot?.time; else emptyDate\">\n {{ configSnapshot.time | c8yDate }}\n </small>\n <ng-template #emptyDate>---</ng-template>\n </p>\n </div>\n <div class=\"col-5\">\n <button\n id=\"action-btn\"\n class=\"btn btn-default btn-sm pull-right\"\n type=\"button\"\n title=\"{{ actionButtonText | translate }}\"\n (click)=\"createDeviceOperation()\"\n [disabled]=\"isCreateOperationDisabled()\"\n *ngIf=\"canCallAction\"\n >\n <i [c8yIcon]=\"actionButtonIcon\"></i>\n {{ actionButtonText | translate }}\n </button>\n </div>\n</div>\n<div class=\"c8y-empty-state text-left\" *ngIf=\"!configSnapshot?.binary && showBinary()\">\n <h1 [c8yIcon]=\"'file-image-o'\"></h1>\n <p>\n <strong translate>No preview available.</strong>\n <br />\n <small *ngIf=\"configSnapshot?.binary !== ''; else emptyFile\" translate>\n The file is not available.\n </small>\n <ng-template #emptyFile>\n <small translate>The file is empty.</small>\n </ng-template>\n </p>\n</div>\n<div *ngIf=\"configSnapshot?.binary && showBinary()\" class=\"flex-grow d-flex d-col\">\n <c8y-source-code-preview\n [text]=\"configSnapshot.binary\"\n [isDisabled]=\"true\"\n class=\"d-contents\"\n ></c8y-source-code-preview>\n <div *ngIf=\"canSaveSnapshot\" class=\"p-t-16\">\n <button\n title=\"{{ 'Download' | translate }}\"\n type=\"button\"\n class=\"btn btn-primary btn-sm pull-right m-l-8\"\n (click)=\"download()\"\n >\n {{ 'Download' | translate }}\n </button>\n <button\n title=\"{{ 'Save to repository' | translate }}\"\n *ngIf=\"hasPermission()\"\n type=\"button\"\n class=\"btn btn-default btn-sm pull-right\"\n (click)=\"saveToRepository()\"\n >\n {{ 'Save to repository' | translate }}\n </button>\n </div>\n</div>\n<div *ngIf=\"showOperation()\">\n <c8y-operation-details [operation]=\"operation\"></c8y-operation-details>\n</div>\n" }]
424
- }], ctorParameters: () => [{ type: DeviceConfigurationService }, { type: i2.OperationRealtimeService }, { type: i3$1.BsModalService }, { type: i4.UserService }, { type: i2.AppStateService }, { type: i3.RepositoryService }, { type: i4.OperationService }, { type: i2.AlertService }], propDecorators: { device: [{
426
+ }], ctorParameters: () => [{ type: DeviceConfigurationService }, { type: i2.OperationRealtimeService }, { type: i1$1.BsModalService }, { type: i4.UserService }, { type: i2.AppStateService }, { type: i3.RepositoryService }, { type: i4.OperationService }, { type: i2.AlertService }], propDecorators: { device: [{
425
427
  type: Input
426
428
  }], configurationType: [{
427
429
  type: Input
@@ -607,7 +609,7 @@ class ConfigurationRepositoryDeviceTabModule {
607
609
  }
608
610
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigurationRepositoryDeviceTabModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
609
611
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: ConfigurationRepositoryDeviceTabModule, imports: [SharedRepositoryModule,
610
- OperationDetailsModule, i1$1.TabsModule, DeviceConfigurationComponent,
612
+ OperationDetailsModule, i1$2.TabsModule, DeviceConfigurationComponent,
611
613
  DeviceConfigurationListComponent,
612
614
  ConfigurationPreviewComponent,
613
615
  ConfigurationFilterPipe,
@@ -642,19 +644,95 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
642
644
  }]
643
645
  }] });
644
646
 
647
+ const EXT_LANGUAGE_MAP = {
648
+ json: 'json',
649
+ yaml: 'yaml',
650
+ yml: 'yaml',
651
+ xml: 'xml',
652
+ html: 'html',
653
+ css: 'css',
654
+ ts: 'typescript',
655
+ js: 'javascript',
656
+ md: 'markdown',
657
+ toml: 'ini',
658
+ properties: 'ini',
659
+ ini: 'ini',
660
+ conf: 'ini',
661
+ cfg: 'ini',
662
+ sh: 'shell'
663
+ };
664
+ function detectLanguage(filename) {
665
+ const ext = (filename.split('.').pop() ?? '').toLowerCase();
666
+ return EXT_LANGUAGE_MAP[ext] ?? 'plaintext';
667
+ }
668
+ const EXT_MIME_MAP = {
669
+ json: 'application/json',
670
+ xml: 'application/xml',
671
+ yaml: 'application/yaml',
672
+ yml: 'application/yaml',
673
+ js: 'application/javascript',
674
+ ts: 'application/typescript',
675
+ html: 'text/html',
676
+ css: 'text/css',
677
+ md: 'text/markdown',
678
+ sh: 'application/x-sh',
679
+ toml: 'application/toml'
680
+ };
681
+ function mimeForFilename(filename) {
682
+ const ext = (filename.split('.').pop() ?? '').toLowerCase();
683
+ return EXT_MIME_MAP[ext] ?? 'text/plain';
684
+ }
685
+ /** Sentinel value meaning "derive language from filename". */
686
+ const AUTO_LANGUAGE = 'auto';
687
+ const EDITOR_LANGUAGES = [
688
+ { value: AUTO_LANGUAGE, label: gettext('Auto (from filename)') },
689
+ { value: 'json', label: gettext('JSON') },
690
+ { value: 'javascript', label: gettext('JavaScript') },
691
+ { value: 'typescript', label: gettext('TypeScript') },
692
+ { value: 'css', label: gettext('CSS') },
693
+ { value: 'html', label: gettext('HTML') },
694
+ { value: 'yaml', label: gettext('YAML') },
695
+ { value: 'markdown', label: gettext('Markdown') },
696
+ { value: 'xml', label: gettext('XML') },
697
+ { value: 'ini', label: gettext('INI / TOML / Properties') },
698
+ { value: 'shell', label: gettext('Shell') },
699
+ { value: 'plaintext', label: gettext('Plain text') }
700
+ ];
645
701
  class ConfigurationDetailComponent {
646
- constructor(repositoryService, bsModalRef, alert) {
647
- this.repositoryService = repositoryService;
648
- this.bsModalRef = bsModalRef;
649
- this.alert = alert;
650
- this.binary = {
651
- file: undefined,
652
- url: undefined
653
- };
654
- this.pattern = '';
702
+ constructor() {
703
+ this.repositoryService = inject(RepositoryService);
704
+ this.bottomDrawerRef = inject(BottomDrawerRef);
705
+ this.alert = inject(AlertService);
706
+ this.inventoryBinaryService = inject(InventoryBinaryService);
707
+ this.cdr = inject(ChangeDetectorRef);
708
+ this.binary = { file: undefined, url: undefined };
655
709
  this.mo = {};
656
- this.saving = false;
657
- this.uploadChoice = 'uploadBinary';
710
+ this.form = new FormGroup({
711
+ version: new FormControl('', [
712
+ Validators.required,
713
+ Validators.maxLength(254),
714
+ Validators.pattern(ValidationPattern.rules.noWhiteSpaceOnly.pattern)
715
+ ]),
716
+ deviceType: new FormControl('', [
717
+ Validators.maxLength(254),
718
+ Validators.pattern(ValidationPattern.rules.noWhiteSpaceOnly.pattern)
719
+ ]),
720
+ description: new FormControl('', [
721
+ Validators.maxLength(254),
722
+ Validators.pattern(ValidationPattern.rules.noWhiteSpaceOnly.pattern)
723
+ ]),
724
+ uploadChoice: new FormControl('uploadBinary'),
725
+ editorFilename: new FormControl('', [
726
+ Validators.pattern(ValidationPattern.rules.noWhiteSpaceOnly.pattern)
727
+ ]),
728
+ editorLanguage: new FormControl(EDITOR_LANGUAGES[0]),
729
+ editorContent: new FormControl('')
730
+ });
731
+ this.configurationTypeMO = {};
732
+ this.saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
733
+ this.pattern = signal('', ...(ngDevMode ? [{ debugName: "pattern" }] : []));
734
+ this.isExternalUrl = signal(false, ...(ngDevMode ? [{ debugName: "isExternalUrl" }] : []));
735
+ this.ValidationPattern = ValidationPattern;
658
736
  this.textForConfigurationUrlPopover = gettext(`Path for binaries can vary depending on device agent implementation, for example:
659
737
  /configuration/binaries/configuration1.bin
660
738
  https://configuration/binary/123
@@ -665,58 +743,146 @@ class ConfigurationDetailComponent {
665
743
  this._save = resolve;
666
744
  this._cancel = reject;
667
745
  });
668
- this.ValidationPattern = ValidationPattern;
746
+ this.settled = false;
747
+ this.editorFilenameValue = toSignal(this.form.controls.editorFilename.valueChanges, {
748
+ initialValue: ''
749
+ });
750
+ this.editorLanguageValue = toSignal(this.form.controls.editorLanguage.valueChanges, {
751
+ initialValue: EDITOR_LANGUAGES[0]
752
+ });
753
+ /** User's manual pick (when not "auto") wins over auto-detection from filename. */
754
+ this.editorLanguage = computed(() => {
755
+ const picked = this.editorLanguageValue()?.value;
756
+ return !picked || picked === AUTO_LANGUAGE
757
+ ? detectLanguage(this.editorFilenameValue() ?? '')
758
+ : picked;
759
+ }, ...(ngDevMode ? [{ debugName: "editorLanguage" }] : []));
760
+ this.editorLanguages = EDITOR_LANGUAGES;
761
+ this.submitButtonTitle = computed(() => this.mo?.id ? gettext('Update configuration') : gettext('Add configuration'), ...(ngDevMode ? [{ debugName: "submitButtonTitle" }] : []));
762
+ this.form.controls.uploadChoice.valueChanges.pipe(takeUntilDestroyed()).subscribe(choice => {
763
+ const ctrl = this.form.controls.editorFilename;
764
+ if (choice === 'editInline') {
765
+ ctrl.addValidators(Validators.required);
766
+ }
767
+ else {
768
+ ctrl.removeValidators(Validators.required);
769
+ }
770
+ ctrl.updateValueAndValidity();
771
+ });
772
+ // Drawer can be closed externally (Escape, navigation, programmatic close).
773
+ // Make sure the result promise is always settled, otherwise callers awaiting it hang.
774
+ this.bottomDrawerRef.onClosed$.pipe(takeUntilDestroyed()).subscribe(() => {
775
+ if (!this.settled) {
776
+ this.settled = true;
777
+ this._cancel('Drawer closed');
778
+ }
779
+ });
669
780
  }
670
781
  async ngOnInit() {
671
782
  this.configs = await this.repositoryService.listRepositoryEntries(RepositoryType.CONFIGURATION);
672
- if (this.mo) {
673
- this.uploadChoice = this.binary.file ? 'uploadBinary' : 'uploadUrl';
674
- this.existingBinary = this.binary.file;
783
+ this.setPipe('');
784
+ if (this.mo?.id) {
675
785
  this.configurationTypeMO = this.mo;
786
+ const hasExternalUrl = !!this.binary?.url && !this.inventoryBinaryService.getIdFromUrl(this.binary.url);
787
+ if (hasExternalUrl) {
788
+ this.isExternalUrl.set(true);
789
+ this.form.patchValue({
790
+ version: this.version ?? '',
791
+ deviceType: this.deviceType ?? '',
792
+ description: this.description ?? '',
793
+ uploadChoice: 'uploadUrl'
794
+ });
795
+ }
796
+ else {
797
+ const patchValues = {
798
+ version: this.version ?? '',
799
+ deviceType: this.deviceType ?? '',
800
+ description: this.description ?? '',
801
+ uploadChoice: 'editInline'
802
+ };
803
+ if (this.binary?.file) {
804
+ this.existingBinary = this.binary.file;
805
+ if (await isBinaryFile(this.binary.file)) {
806
+ patchValues.uploadChoice = 'uploadBinary';
807
+ }
808
+ else {
809
+ patchValues.editorContent = await this.binary.file.text();
810
+ patchValues.editorFilename = this.binary.file.name;
811
+ }
812
+ }
813
+ this.form.patchValue(patchValues);
814
+ }
815
+ this.form.markAsDirty();
676
816
  }
677
- this.setPipe('');
678
- this.submitButtonTitle = this.mo.id
679
- ? gettext('Update configuration')
680
- : gettext('Add configuration');
817
+ this.cdr.markForCheck();
681
818
  }
682
819
  cancel() {
683
- this.bsModalRef.hide();
684
- this._cancel();
820
+ this.settled = true;
821
+ this._cancel('User canceled');
822
+ this.bottomDrawerRef.close();
685
823
  }
686
824
  setPipe(filterStr) {
687
- this.pattern = filterStr;
688
- this.filterPipe = pipe(map(data => uniqBy(data, 'configurationType')), map(data => {
689
- return data.filter(mo => mo.configurationType &&
690
- mo.configurationType.toLowerCase().indexOf(filterStr.toLowerCase()) > -1);
691
- }));
692
- }
693
- onFile(dropped) {
694
- this.configurationForm.form.markAsDirty();
695
- if (!isUndefined(dropped.url)) {
696
- this.binary = {
697
- url: dropped.url
698
- };
699
- return;
825
+ this.pattern.set(filterStr);
826
+ this.filterPipe = pipe(map(data => uniqBy(data, 'configurationType')), map(data => data.filter(mo => mo.configurationType &&
827
+ mo.configurationType.toLowerCase().indexOf(filterStr.toLowerCase()) > -1)));
828
+ }
829
+ onUploadChoiceChange(choice) {
830
+ this.form.controls.uploadChoice.setValue(choice);
831
+ this.form.markAsDirty();
832
+ }
833
+ onFileBinaryChange(file) {
834
+ this.binary = { ...this.binary, file };
835
+ this.form.markAsDirty();
836
+ this.cdr.markForCheck();
837
+ }
838
+ onFileUrlChange(url) {
839
+ this.binary = { ...this.binary, url };
840
+ this.form.markAsDirty();
841
+ this.cdr.markForCheck();
842
+ }
843
+ onTextFilePicked({ name, content }) {
844
+ this.form.patchValue({
845
+ uploadChoice: 'editInline',
846
+ editorFilename: name,
847
+ editorContent: content
848
+ });
849
+ this.form.markAsDirty();
850
+ this.cdr.markForCheck();
851
+ }
852
+ get isSaveDisabled() {
853
+ if (this.saving() || !this.form.valid || this.form.pristine) {
854
+ return true;
700
855
  }
701
- else if (dropped.droppedFiles) {
702
- this.binary = {
703
- file: dropped.droppedFiles[0].file
704
- };
705
- return;
856
+ const { uploadChoice, editorFilename, editorContent } = this.form.value;
857
+ if (uploadChoice === 'uploadBinary') {
858
+ return !this.binary?.file;
706
859
  }
707
- else {
708
- this.binary = {
709
- file: undefined,
710
- url: undefined
711
- };
860
+ if (uploadChoice === 'uploadUrl') {
861
+ return !this.binary?.url?.trim();
862
+ }
863
+ if (uploadChoice === 'editInline') {
864
+ return !editorFilename?.trim() || !editorContent;
712
865
  }
866
+ return false;
713
867
  }
714
868
  async save() {
869
+ if (this.isSaveDisabled) {
870
+ return;
871
+ }
715
872
  try {
716
- this.saving = true;
717
- const { version, description, binary, deviceType } = this;
718
- if (this.existingBinary === this.binary.file) {
719
- binary.file = undefined;
873
+ this.saving.set(true);
874
+ const { version, deviceType, description, uploadChoice, editorFilename, editorContent } = this.form.value;
875
+ let binary = { ...this.binary };
876
+ if (uploadChoice === 'uploadUrl') {
877
+ binary = { url: this.binary?.url };
878
+ }
879
+ else if (uploadChoice === 'editInline') {
880
+ binary = {
881
+ file: new File([editorContent], editorFilename, { type: mimeForFilename(editorFilename) })
882
+ };
883
+ }
884
+ else if (this.existingBinary === this.binary?.file) {
885
+ binary = { ...this.binary, file: undefined };
720
886
  }
721
887
  await this.repositoryService.create({
722
888
  version,
@@ -725,52 +891,65 @@ class ConfigurationDetailComponent {
725
891
  deviceType,
726
892
  configurationType: this.configurationTypeMO?.configurationType
727
893
  }, RepositoryType.CONFIGURATION, this.mo);
728
- this.alert.success(this.mo.id ? gettext('Configuration updated.') : gettext('Configuration created.'));
729
- this.bsModalRef.hide();
894
+ this.alert.success(this.mo?.id ? gettext('Configuration updated.') : gettext('Configuration created.'));
895
+ this.settled = true;
730
896
  this._save();
897
+ this.bottomDrawerRef.close();
731
898
  }
732
899
  catch (ex) {
900
+ // Keep the drawer open so the user can fix and retry.
901
+ // Do NOT settle the result promise here.
733
902
  this.alert.addServerFailure(ex);
734
- this._cancel();
735
903
  }
736
904
  finally {
737
- this.saving = false;
905
+ this.saving.set(false);
738
906
  }
739
907
  }
740
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigurationDetailComponent, deps: [{ token: i3.RepositoryService }, { token: i3$1.BsModalRef }, { token: i2.AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
741
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: ConfigurationDetailComponent, isStandalone: true, selector: "c8y-configuration-detail", viewQueries: [{ propertyName: "configurationForm", first: true, predicate: ["configurationForm"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"viewport-modal\">\n <div\n class=\"modal-header dialog-header\"\n id=\"configurationModalTitle\"\n >\n <i [c8yIcon]=\"'cogs'\"></i>\n <h4\n id=\"modal-title\"\n translate\n *ngIf=\"mo.id\"\n >\n Update configuration\n </h4>\n <h4\n id=\"modal-title\"\n translate\n *ngIf=\"!mo.id\"\n >\n Add configuration\n </h4>\n </div>\n\n <form\n class=\"d-contents\"\n #configurationForm=\"ngForm\"\n (ngSubmit)=\"configurationForm.form.valid && save()\"\n >\n <div\n class=\"modal-inner-scroll\"\n id=\"modal-body\"\n >\n <div\n class=\"modal-body\"\n id=\"configurationModalDescription\"\n >\n <c8y-form-group>\n <label translate>Name</label>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} hosts\"\n name=\"version\"\n type=\"text\"\n autocomplete=\"off\"\n required\n maxlength=\"254\"\n [(ngModel)]=\"version\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label translate>Device type</label>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n name=\"deviceType\"\n type=\"text\"\n autocomplete=\"off\"\n maxlength=\"254\"\n [(ngModel)]=\"deviceType\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label translate>Description</label>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g. Host configuration' | translate }} c8y_Linux\"\n name=\"description\"\n type=\"text\"\n autocomplete=\"off\"\n maxlength=\"254\"\n [(ngModel)]=\"description\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label translate>Configuration type</label>\n <c8y-typeahead\n placeholder=\"{{ 'e.g.' | translate }} ssh\"\n name=\"confType\"\n [(ngModel)]=\"configurationTypeMO\"\n maxlength=\"254\"\n (onSearch)=\"setPipe($event)\"\n displayProperty=\"configurationType\"\n >\n <c8y-li\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n *c8yFor=\"let config of configs; pipe: filterPipe; notFound: notFoundTemplate\"\n (click)=\"configurationTypeMO = config; setPipe('')\"\n [active]=\"configurationTypeMO === config\"\n >\n <c8y-highlight\n [text]=\"config.configurationType || '--'\"\n [pattern]=\"pattern\"\n ></c8y-highlight>\n </c8y-li>\n <ng-template #notFoundTemplate>\n <c8y-li\n class=\"bg-level-2 p-8\"\n *ngIf=\"pattern.length > 0\"\n >\n <span translate>No match found.</span>\n <button\n class=\"btn btn-primary btn-xs m-l-8\"\n title=\"{{ 'Add new`configuration type`' | translate }}\"\n type=\"button\"\n translate\n >\n Add new`configuration type`\n </button>\n </c8y-li>\n </ng-template>\n </c8y-typeahead>\n </c8y-form-group>\n\n <c8y-form-group>\n <div\n class=\"legend form-block m-t-40\"\n translate\n >\n Configuration file\n </div>\n <c8y-file-picker\n [maxAllowedFiles]=\"1\"\n (onFilesPicked)=\"onFile($event)\"\n [uploadChoice]=\"uploadChoice\"\n [fileUrl]=\"binary.url\"\n [fileBinary]=\"binary.file\"\n [fileUrlPopover]=\"textForConfigurationUrlPopover\"\n ></c8y-file-picker>\n </c8y-form-group>\n </div>\n </div>\n\n <div class=\"modal-footer\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n >\n <span translate>Cancel</span>\n </button>\n <button\n class=\"btn btn-primary\"\n title=\"{{ submitButtonTitle | translate }}\"\n type=\"submit\"\n [ngClass]=\"{ 'btn-pending': saving }\"\n [disabled]=\"\n !configurationForm.valid ||\n configurationForm.pristine ||\n (!binary?.url?.trim() && !binary?.file) ||\n saving\n \"\n >\n {{ submitButtonTitle | translate }}\n </button>\n </div>\n </form>\n</div>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i4$1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i4$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: TypeaheadComponent, selector: "c8y-typeahead", inputs: ["required", "maxlength", "disabled", "allowFreeEntries", "placeholder", "displayProperty", "icon", "name", "autoClose", "hideNew", "container", "selected", "title", "highlightFirstItem"], outputs: ["onSearch", "onIconClick"] }, { kind: "directive", type: ForOfDirective, selector: "[c8yFor]", inputs: ["c8yForOf", "c8yForLoadMore", "c8yForPipe", "c8yForNotFound", "c8yForMaxIterations", "c8yForLoadingTemplate", "c8yForLoadNextLabel", "c8yForLoadingLabel", "c8yForRealtime", "c8yForRealtimeOptions", "c8yForComparator", "c8yForEnableVirtualScroll", "c8yForVirtualScrollElementSize", "c8yForVirtualScrollStrategy", "c8yForVirtualScrollContainerHeight"], outputs: ["c8yForCount", "c8yForChange", "c8yForLoadMoreComponent"] }, { kind: "component", type: ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: HighlightComponent, selector: "c8y-highlight", inputs: ["pattern", "text", "elementClass", "shouldTrimPattern"] }, { kind: "component", type: FilePickerComponent, selector: "c8y-file-picker", inputs: ["maxAllowedFiles", "uploadChoice", "allowedUploadChoices", "fileUrl", "fileBinary", "config", "filePickerIndex", "fileUrlPopover"], outputs: ["onFilesPicked"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
908
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigurationDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
909
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ConfigurationDetailComponent, isStandalone: true, selector: "c8y-configuration-detail", inputs: { version: "version", description: "description", binary: "binary", deviceType: "deviceType", mo: "mo" }, host: { classAttribute: "d-contents" }, ngImport: i0, template: "<div class=\"card-header j-c-center separator flex-no-shrink p-24\">\n <i\n class=\"m-r-8\"\n [c8yIcon]=\"'cogs'\"\n aria-hidden=\"true\"\n ></i>\n <h4 id=\"drawerTitle\">\n @if (mo?.id) {\n {{ 'Update configuration' | translate }}\n } @else {\n {{ 'Add configuration' | translate }}\n }\n </h4>\n</div>\n\n<form\n class=\"d-contents\"\n [formGroup]=\"form\"\n (ngSubmit)=\"save()\"\n>\n <div\n class=\"card-body inner-scroll p-24\"\n [class.d-col]=\"form.value.uploadChoice === 'editInline'\"\n >\n <div class=\"row\">\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confVersion\"\n translate\n >\n Name\n </label>\n <input\n class=\"form-control\"\n id=\"confVersion\"\n placeholder=\"{{ 'e.g.' | translate }} hosts\"\n aria-required=\"true\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"version\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confDeviceType\"\n translate\n >\n Device type\n </label>\n <input\n class=\"form-control\"\n id=\"confDeviceType\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"deviceType\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confDescription\"\n translate\n >\n Description\n </label>\n <input\n class=\"form-control\"\n id=\"confDescription\"\n placeholder=\"{{ 'e.g.' | translate }} Host configuration\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"description\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label translate>Configuration type</label>\n <c8y-typeahead\n placeholder=\"{{ 'e.g.' | translate }} ssh\"\n name=\"confType\"\n data-cy=\"conf-detail--configuration-type\"\n [ngModel]=\"configurationTypeMO\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"configurationTypeMO = $event\"\n maxlength=\"254\"\n (onSearch)=\"setPipe($event)\"\n displayProperty=\"configurationType\"\n >\n @let patternStr = pattern();\n <c8y-li\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n *c8yFor=\"let config of configs; pipe: filterPipe; notFound: notFoundTemplate\"\n (click)=\"configurationTypeMO = config; setPipe('')\"\n [active]=\"configurationTypeMO === config\"\n >\n <c8y-highlight\n [text]=\"config.configurationType || '--'\"\n [pattern]=\"patternStr\"\n ></c8y-highlight>\n </c8y-li>\n <ng-template #notFoundTemplate>\n @if (patternStr.length > 0) {\n <c8y-li class=\"bg-level-2 p-8\">\n <span translate>No match found.</span>\n <button\n class=\"btn btn-primary btn-xs m-l-8\"\n title=\"{{ 'Add new`configuration type`' | translate }}\"\n type=\"button\"\n translate\n >\n Add new`configuration type`\n </button>\n </c8y-li>\n }\n </ng-template>\n </c8y-typeahead>\n </c8y-form-group>\n </div>\n\n <fieldset\n class=\"d-col\"\n [class.flex-grow]=\"form.value.uploadChoice === 'editInline'\"\n [class.min-height-0]=\"form.value.uploadChoice === 'editInline'\"\n >\n <legend\n class=\"legend form-block m-t-40\"\n translate\n >\n Configuration file\n </legend>\n\n @if (isExternalUrl()) {\n <p\n class=\"text-muted m-b-8\"\n translate\n >\n Content is sourced from a remote URL and cannot be edited inline.\n </p>\n <c8y-form-group>\n <label\n for=\"confFileUrl\"\n translate\n >\n File URL\n </label>\n <input\n class=\"form-control\"\n id=\"confFileUrl\"\n placeholder=\"https://...\"\n type=\"url\"\n autocomplete=\"off\"\n [ngModel]=\"binary.url\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"binary = { url: $event }; form.markAsDirty()\"\n />\n </c8y-form-group>\n } @else {\n <c8y-file-picker\n [maxAllowedFiles]=\"1\"\n [allowedUploadChoices]=\"['uploadBinary', 'uploadUrl', 'editInline']\"\n [uploadChoice]=\"form.controls.uploadChoice.value\"\n (uploadChoiceChange)=\"onUploadChoiceChange($event)\"\n [fileUrl]=\"binary.url\"\n (fileUrlChange)=\"onFileUrlChange($event)\"\n [fileBinary]=\"binary.file\"\n (fileBinaryChange)=\"onFileBinaryChange($event)\"\n (textFilePicked)=\"onTextFilePicked($event)\"\n [fileUrlPopover]=\"textForConfigurationUrlPopover\"\n >\n <ng-container c8yInlineEditor>\n <div class=\"row\">\n <c8y-form-group class=\"col-sm-9\">\n <label\n for=\"confEditorFilename\"\n translate\n >\n Filename\n </label>\n <input\n class=\"form-control\"\n id=\"confEditorFilename\"\n placeholder=\"config.json\"\n aria-required=\"true\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"editorFilename\"\n />\n </c8y-form-group>\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confEditorLanguage\"\n translate\n >\n Language\n </label>\n <c8y-select\n aria-label=\"{{ 'Editor language' | translate }}\"\n id=\"confEditorLanguage\"\n [items]=\"editorLanguages\"\n formControlName=\"editorLanguage\"\n ></c8y-select>\n </c8y-form-group>\n </div>\n <c8y-form-group class=\"d-col flex-grow min-height-0 m-b-0\">\n <label\n for=\"confEditorContent\"\n translate\n >\n Content\n </label>\n <c8y-editor\n class=\"flex-grow\"\n style=\"min-height: 95px\"\n id=\"confEditorContent\"\n formControlName=\"editorContent\"\n monacoEditorMarkerValidator\n [editorOptions]=\"{ language: editorLanguage() }\"\n ></c8y-editor>\n <c8y-messages></c8y-messages>\n </c8y-form-group>\n </ng-container>\n </c8y-file-picker>\n }\n </fieldset>\n </div>\n\n <div class=\"card-footer j-c-between separator flex-no-shrink p-24\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving()\"\n >\n <span translate>Cancel</span>\n </button>\n <button\n class=\"btn btn-primary\"\n [class.btn-pending]=\"saving()\"\n [title]=\"submitButtonTitle() | translate\"\n type=\"submit\"\n [disabled]=\"isSaveDisabled\"\n >\n {{ submitButtonTitle() | translate }}\n </button>\n </div>\n</form>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i4$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i4$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i4$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: TypeaheadComponent, selector: "c8y-typeahead", inputs: ["required", "maxlength", "disabled", "allowFreeEntries", "placeholder", "displayProperty", "icon", "name", "autoClose", "hideNew", "container", "selected", "title", "highlightFirstItem"], outputs: ["onSearch", "onIconClick"] }, { kind: "directive", type: ForOfDirective, selector: "[c8yFor]", inputs: ["c8yForOf", "c8yForLoadMore", "c8yForPipe", "c8yForNotFound", "c8yForMaxIterations", "c8yForLoadingTemplate", "c8yForLoadNextLabel", "c8yForLoadingLabel", "c8yForRealtime", "c8yForRealtimeOptions", "c8yForComparator", "c8yForEnableVirtualScroll", "c8yForVirtualScrollElementSize", "c8yForVirtualScrollStrategy", "c8yForVirtualScrollContainerHeight"], outputs: ["c8yForCount", "c8yForChange", "c8yForLoadMoreComponent"] }, { kind: "component", type: ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: HighlightComponent, selector: "c8y-highlight", inputs: ["pattern", "text", "elementClass", "shouldTrimPattern"] }, { kind: "component", type: FilePickerComponent, selector: "c8y-file-picker", inputs: ["maxAllowedFiles", "uploadChoice", "allowedUploadChoices", "fileUrl", "fileBinary", "config", "filePickerIndex", "fileUrlPopover", "editInlineConfirmTitle", "editInlineConfirmMessage"], outputs: ["onFilesPicked", "uploadChoiceChange", "fileBinaryChange", "fileUrlChange", "textFilePicked"] }, { kind: "component", type: EditorComponent, selector: "c8y-editor", inputs: ["editorOptions", "theme"], outputs: ["editorInit"] }, { kind: "directive", type: MonacoEditorMarkerValidatorDirective, selector: "c8y-editor [monacoEditorMarkerValidator]" }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage", "additionalMessages"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: SelectComponent, selector: "c8y-select", inputs: ["placeholder", "items", "selected", "container", "multi", "canSelectWithSpace", "disabled", "autoClose", "insideClick", "required", "canDeselect", "name", "icon", "filterItems"], outputs: ["onSelect", "onDeselect", "onIconClick"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
742
910
  }
743
911
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigurationDetailComponent, decorators: [{
744
912
  type: Component,
745
- args: [{ selector: 'c8y-configuration-detail', imports: [
913
+ args: [{ selector: 'c8y-configuration-detail', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'd-contents' }, imports: [
914
+ ReactiveFormsModule,
915
+ FormsModule,
746
916
  IconDirective,
747
- NgIf,
748
917
  C8yTranslateDirective,
749
- FormsModule,
750
918
  FormGroupComponent,
751
- RequiredInputPlaceholderDirective,
752
919
  TypeaheadComponent,
753
920
  ForOfDirective,
754
921
  ListItemComponent,
755
922
  HighlightComponent,
756
923
  FilePickerComponent,
757
924
  NgClass,
758
- C8yTranslatePipe
759
- ], template: "<div class=\"viewport-modal\">\n <div\n class=\"modal-header dialog-header\"\n id=\"configurationModalTitle\"\n >\n <i [c8yIcon]=\"'cogs'\"></i>\n <h4\n id=\"modal-title\"\n translate\n *ngIf=\"mo.id\"\n >\n Update configuration\n </h4>\n <h4\n id=\"modal-title\"\n translate\n *ngIf=\"!mo.id\"\n >\n Add configuration\n </h4>\n </div>\n\n <form\n class=\"d-contents\"\n #configurationForm=\"ngForm\"\n (ngSubmit)=\"configurationForm.form.valid && save()\"\n >\n <div\n class=\"modal-inner-scroll\"\n id=\"modal-body\"\n >\n <div\n class=\"modal-body\"\n id=\"configurationModalDescription\"\n >\n <c8y-form-group>\n <label translate>Name</label>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} hosts\"\n name=\"version\"\n type=\"text\"\n autocomplete=\"off\"\n required\n maxlength=\"254\"\n [(ngModel)]=\"version\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label translate>Device type</label>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n name=\"deviceType\"\n type=\"text\"\n autocomplete=\"off\"\n maxlength=\"254\"\n [(ngModel)]=\"deviceType\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label translate>Description</label>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g. Host configuration' | translate }} c8y_Linux\"\n name=\"description\"\n type=\"text\"\n autocomplete=\"off\"\n maxlength=\"254\"\n [(ngModel)]=\"description\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label translate>Configuration type</label>\n <c8y-typeahead\n placeholder=\"{{ 'e.g.' | translate }} ssh\"\n name=\"confType\"\n [(ngModel)]=\"configurationTypeMO\"\n maxlength=\"254\"\n (onSearch)=\"setPipe($event)\"\n displayProperty=\"configurationType\"\n >\n <c8y-li\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n *c8yFor=\"let config of configs; pipe: filterPipe; notFound: notFoundTemplate\"\n (click)=\"configurationTypeMO = config; setPipe('')\"\n [active]=\"configurationTypeMO === config\"\n >\n <c8y-highlight\n [text]=\"config.configurationType || '--'\"\n [pattern]=\"pattern\"\n ></c8y-highlight>\n </c8y-li>\n <ng-template #notFoundTemplate>\n <c8y-li\n class=\"bg-level-2 p-8\"\n *ngIf=\"pattern.length > 0\"\n >\n <span translate>No match found.</span>\n <button\n class=\"btn btn-primary btn-xs m-l-8\"\n title=\"{{ 'Add new`configuration type`' | translate }}\"\n type=\"button\"\n translate\n >\n Add new`configuration type`\n </button>\n </c8y-li>\n </ng-template>\n </c8y-typeahead>\n </c8y-form-group>\n\n <c8y-form-group>\n <div\n class=\"legend form-block m-t-40\"\n translate\n >\n Configuration file\n </div>\n <c8y-file-picker\n [maxAllowedFiles]=\"1\"\n (onFilesPicked)=\"onFile($event)\"\n [uploadChoice]=\"uploadChoice\"\n [fileUrl]=\"binary.url\"\n [fileBinary]=\"binary.file\"\n [fileUrlPopover]=\"textForConfigurationUrlPopover\"\n ></c8y-file-picker>\n </c8y-form-group>\n </div>\n </div>\n\n <div class=\"modal-footer\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n >\n <span translate>Cancel</span>\n </button>\n <button\n class=\"btn btn-primary\"\n title=\"{{ submitButtonTitle | translate }}\"\n type=\"submit\"\n [ngClass]=\"{ 'btn-pending': saving }\"\n [disabled]=\"\n !configurationForm.valid ||\n configurationForm.pristine ||\n (!binary?.url?.trim() && !binary?.file) ||\n saving\n \"\n >\n {{ submitButtonTitle | translate }}\n </button>\n </div>\n </form>\n</div>\n" }]
760
- }], ctorParameters: () => [{ type: i3.RepositoryService }, { type: i3$1.BsModalRef }, { type: i2.AlertService }], propDecorators: { configurationForm: [{
761
- type: ViewChild,
762
- args: ['configurationForm', { static: true }]
925
+ C8yTranslatePipe,
926
+ EditorComponent,
927
+ MonacoEditorMarkerValidatorDirective,
928
+ MessagesComponent,
929
+ RequiredInputPlaceholderDirective,
930
+ SelectComponent
931
+ ], template: "<div class=\"card-header j-c-center separator flex-no-shrink p-24\">\n <i\n class=\"m-r-8\"\n [c8yIcon]=\"'cogs'\"\n aria-hidden=\"true\"\n ></i>\n <h4 id=\"drawerTitle\">\n @if (mo?.id) {\n {{ 'Update configuration' | translate }}\n } @else {\n {{ 'Add configuration' | translate }}\n }\n </h4>\n</div>\n\n<form\n class=\"d-contents\"\n [formGroup]=\"form\"\n (ngSubmit)=\"save()\"\n>\n <div\n class=\"card-body inner-scroll p-24\"\n [class.d-col]=\"form.value.uploadChoice === 'editInline'\"\n >\n <div class=\"row\">\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confVersion\"\n translate\n >\n Name\n </label>\n <input\n class=\"form-control\"\n id=\"confVersion\"\n placeholder=\"{{ 'e.g.' | translate }} hosts\"\n aria-required=\"true\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"version\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confDeviceType\"\n translate\n >\n Device type\n </label>\n <input\n class=\"form-control\"\n id=\"confDeviceType\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"deviceType\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confDescription\"\n translate\n >\n Description\n </label>\n <input\n class=\"form-control\"\n id=\"confDescription\"\n placeholder=\"{{ 'e.g.' | translate }} Host configuration\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"description\"\n />\n </c8y-form-group>\n\n <c8y-form-group class=\"col-sm-3\">\n <label translate>Configuration type</label>\n <c8y-typeahead\n placeholder=\"{{ 'e.g.' | translate }} ssh\"\n name=\"confType\"\n data-cy=\"conf-detail--configuration-type\"\n [ngModel]=\"configurationTypeMO\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"configurationTypeMO = $event\"\n maxlength=\"254\"\n (onSearch)=\"setPipe($event)\"\n displayProperty=\"configurationType\"\n >\n @let patternStr = pattern();\n <c8y-li\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n *c8yFor=\"let config of configs; pipe: filterPipe; notFound: notFoundTemplate\"\n (click)=\"configurationTypeMO = config; setPipe('')\"\n [active]=\"configurationTypeMO === config\"\n >\n <c8y-highlight\n [text]=\"config.configurationType || '--'\"\n [pattern]=\"patternStr\"\n ></c8y-highlight>\n </c8y-li>\n <ng-template #notFoundTemplate>\n @if (patternStr.length > 0) {\n <c8y-li class=\"bg-level-2 p-8\">\n <span translate>No match found.</span>\n <button\n class=\"btn btn-primary btn-xs m-l-8\"\n title=\"{{ 'Add new`configuration type`' | translate }}\"\n type=\"button\"\n translate\n >\n Add new`configuration type`\n </button>\n </c8y-li>\n }\n </ng-template>\n </c8y-typeahead>\n </c8y-form-group>\n </div>\n\n <fieldset\n class=\"d-col\"\n [class.flex-grow]=\"form.value.uploadChoice === 'editInline'\"\n [class.min-height-0]=\"form.value.uploadChoice === 'editInline'\"\n >\n <legend\n class=\"legend form-block m-t-40\"\n translate\n >\n Configuration file\n </legend>\n\n @if (isExternalUrl()) {\n <p\n class=\"text-muted m-b-8\"\n translate\n >\n Content is sourced from a remote URL and cannot be edited inline.\n </p>\n <c8y-form-group>\n <label\n for=\"confFileUrl\"\n translate\n >\n File URL\n </label>\n <input\n class=\"form-control\"\n id=\"confFileUrl\"\n placeholder=\"https://...\"\n type=\"url\"\n autocomplete=\"off\"\n [ngModel]=\"binary.url\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"binary = { url: $event }; form.markAsDirty()\"\n />\n </c8y-form-group>\n } @else {\n <c8y-file-picker\n [maxAllowedFiles]=\"1\"\n [allowedUploadChoices]=\"['uploadBinary', 'uploadUrl', 'editInline']\"\n [uploadChoice]=\"form.controls.uploadChoice.value\"\n (uploadChoiceChange)=\"onUploadChoiceChange($event)\"\n [fileUrl]=\"binary.url\"\n (fileUrlChange)=\"onFileUrlChange($event)\"\n [fileBinary]=\"binary.file\"\n (fileBinaryChange)=\"onFileBinaryChange($event)\"\n (textFilePicked)=\"onTextFilePicked($event)\"\n [fileUrlPopover]=\"textForConfigurationUrlPopover\"\n >\n <ng-container c8yInlineEditor>\n <div class=\"row\">\n <c8y-form-group class=\"col-sm-9\">\n <label\n for=\"confEditorFilename\"\n translate\n >\n Filename\n </label>\n <input\n class=\"form-control\"\n id=\"confEditorFilename\"\n placeholder=\"config.json\"\n aria-required=\"true\"\n type=\"text\"\n autocomplete=\"off\"\n formControlName=\"editorFilename\"\n />\n </c8y-form-group>\n <c8y-form-group class=\"col-sm-3\">\n <label\n for=\"confEditorLanguage\"\n translate\n >\n Language\n </label>\n <c8y-select\n aria-label=\"{{ 'Editor language' | translate }}\"\n id=\"confEditorLanguage\"\n [items]=\"editorLanguages\"\n formControlName=\"editorLanguage\"\n ></c8y-select>\n </c8y-form-group>\n </div>\n <c8y-form-group class=\"d-col flex-grow min-height-0 m-b-0\">\n <label\n for=\"confEditorContent\"\n translate\n >\n Content\n </label>\n <c8y-editor\n class=\"flex-grow\"\n style=\"min-height: 95px\"\n id=\"confEditorContent\"\n formControlName=\"editorContent\"\n monacoEditorMarkerValidator\n [editorOptions]=\"{ language: editorLanguage() }\"\n ></c8y-editor>\n <c8y-messages></c8y-messages>\n </c8y-form-group>\n </ng-container>\n </c8y-file-picker>\n }\n </fieldset>\n </div>\n\n <div class=\"card-footer j-c-between separator flex-no-shrink p-24\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving()\"\n >\n <span translate>Cancel</span>\n </button>\n <button\n class=\"btn btn-primary\"\n [class.btn-pending]=\"saving()\"\n [title]=\"submitButtonTitle() | translate\"\n type=\"submit\"\n [disabled]=\"isSaveDisabled\"\n >\n {{ submitButtonTitle() | translate }}\n </button>\n </div>\n</form>\n" }]
932
+ }], ctorParameters: () => [], propDecorators: { version: [{
933
+ type: Input
934
+ }], description: [{
935
+ type: Input
936
+ }], binary: [{
937
+ type: Input
938
+ }], deviceType: [{
939
+ type: Input
940
+ }], mo: [{
941
+ type: Input
763
942
  }] } });
764
943
 
765
944
  class ConfigurationListComponent {
766
- constructor(alert, gridService, repositoryService, bsModalService, modalService, translateService, inventoryBinaryService) {
767
- this.alert = alert;
768
- this.gridService = gridService;
769
- this.repositoryService = repositoryService;
770
- this.bsModalService = bsModalService;
771
- this.modalService = modalService;
772
- this.translateService = translateService;
773
- this.inventoryBinaryService = inventoryBinaryService;
945
+ constructor() {
946
+ this.alert = inject(AlertService);
947
+ this.gridService = inject(DataGridService);
948
+ this.repositoryService = inject(RepositoryService);
949
+ this.bottomDrawerService = inject(BottomDrawerService);
950
+ this.modalService = inject(ModalService);
951
+ this.translateService = inject(TranslateService);
952
+ this.inventoryBinaryService = inject(InventoryBinaryService);
774
953
  this.refresh$ = new EventEmitter();
775
954
  this.pagination = {
776
955
  pageSize: 50,
@@ -868,43 +1047,44 @@ class ConfigurationListComponent {
868
1047
  }
869
1048
  async add() {
870
1049
  try {
871
- await this.bsModalService.show(ConfigurationDetailComponent, {
872
- class: 'modal-sm',
873
- ariaDescribedby: 'configurationModalDescription',
874
- ariaLabelledBy: 'configurationModalTitle',
875
- ignoreBackdropClick: true,
876
- keyboard: false
877
- }).content.result;
1050
+ const drawer = this.bottomDrawerService.openDrawer(ConfigurationDetailComponent, {
1051
+ disableClickOutside: true,
1052
+ closeOnNavigation: true
1053
+ });
1054
+ await drawer.instance.result;
878
1055
  this.refresh$.next();
879
1056
  }
880
1057
  catch (ex) {
881
- // intended empty
1058
+ // intended empty — user canceled or save failed (error displayed by detail component)
882
1059
  }
883
1060
  }
884
1061
  async edit(configuration) {
885
- const fileBinary = await this.repositoryService.getBinaryFile(configuration.url, {
886
- allowExternal: false
887
- });
1062
+ let fileBinary = null;
1063
+ try {
1064
+ fileBinary = await this.repositoryService.getBinaryFile(configuration.url, {
1065
+ allowExternal: false
1066
+ });
1067
+ }
1068
+ catch {
1069
+ // Binary unavailable — drawer still opens; editor will be empty
1070
+ }
888
1071
  try {
889
- const modal = this.bsModalService.show(ConfigurationDetailComponent, {
890
- class: 'modal-sm',
891
- ariaDescribedby: 'configurationModalDescription',
892
- ariaLabelledBy: 'configurationModalTitle',
893
- ignoreBackdropClick: true,
894
- keyboard: false,
1072
+ const drawer = this.bottomDrawerService.openDrawer(ConfigurationDetailComponent, {
1073
+ disableClickOutside: true,
1074
+ closeOnNavigation: true,
895
1075
  initialState: {
896
1076
  version: configuration.name,
897
1077
  deviceType: configuration.deviceType,
898
1078
  description: configuration.description,
899
- binary: { file: fileBinary, url: configuration.url }
1079
+ binary: { file: fileBinary, url: configuration.url },
1080
+ mo: configuration
900
1081
  }
901
- }).content;
902
- modal.mo = configuration;
903
- await modal.result;
1082
+ });
1083
+ await drawer.instance.result;
904
1084
  this.refresh$.next();
905
1085
  }
906
1086
  catch (ex) {
907
- // intended empty
1087
+ // intended empty — user canceled or save failed (error displayed by detail component)
908
1088
  }
909
1089
  }
910
1090
  isBinaryFile(configuration) {
@@ -948,12 +1128,12 @@ class ConfigurationListComponent {
948
1128
  trackByName(_index, column) {
949
1129
  return column.name;
950
1130
  }
951
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigurationListComponent, deps: [{ token: i2.AlertService }, { token: i2.DataGridService }, { token: i3.RepositoryService }, { token: i3$1.BsModalService }, { token: i2.ModalService }, { token: i4$2.TranslateService }, { token: i4.InventoryBinaryService }], target: i0.ɵɵFactoryTarget.Component }); }
952
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ConfigurationListComponent, isStandalone: true, selector: "c8y-configuration-list", viewQueries: [{ propertyName: "filter", first: true, predicate: FilterInputComponent, descendants: true }], ngImport: i0, template: "<c8y-title>\n <span\n class=\"m-r-4\"\n translate\n >\n Configuration repository\n </span>\n <small>\n {{ size$ | async }}\n <span translate>snapshots</span>\n </small>\n</c8y-title>\n\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n icon=\"c8y-management\"\n label=\"{{ 'Management' | translate }}\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n icon=\"gears\"\n label=\"{{ 'Configuration repository' | translate }}\"\n ></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Add configuration snapshot' | translate }}\"\n type=\"button\"\n (click)=\"add()\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add configuration snapshot' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<c8y-help\n src=\"/docs/device-management-application/managing-device-data/#managing-configurations\"\n></c8y-help>\n\n<div class=\"content-fullpage border-top border-bottom\">\n <c8y-data-grid\n [title]=\"'Configurations' | translate\"\n [refresh]=\"refresh$\"\n [pagination]=\"pagination\"\n [columns]=\"columns\"\n [actionControls]=\"actionControls\"\n [infiniteScroll]=\"'auto'\"\n [serverSideDataCallback]=\"serverSideDataCallback\"\n >\n <c8y-ui-empty-state\n [icon]=\"stats?.size > 0 ? 'search' : 'gears'\"\n [title]=\"stats?.size > 0 ? (noResultsMessage | translate) : (noDataMessage | translate)\"\n [subtitle]=\"stats?.size > 0 ? (noResultsSubtitle | translate) : (noDataSubtitle | translate)\"\n *emptyStateContext=\"let stats\"\n [horizontal]=\"stats?.size > 0\"\n >\n @if (stats?.size === 0) {\n <p>\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Add configuration snapshot' | translate }}\"\n type=\"button\"\n (click)=\"add()\"\n >\n {{ 'Add configuration snapshot' | translate }}\n </button>\n </p>\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a\n c8y-guide-href=\"/docs/device-management-application/managing-device-data/#managing-configurations\"\n >\n user documentation</a\n >.\n </small>\n </p>\n }\n </c8y-ui-empty-state>\n </c8y-data-grid>\n</div>\n", dependencies: [{ kind: "component", type: TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: BreadcrumbComponent, selector: "c8y-breadcrumb" }, { kind: "component", type: BreadcrumbItemComponent, selector: "c8y-breadcrumb-item", inputs: ["icon", "translate", "label", "path", "injector"] }, { kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: HelpComponent, selector: "c8y-help", inputs: ["src", "isCollapsed", "priority", "icon"] }, { kind: "component", type: DataGridComponent, selector: "c8y-data-grid", inputs: ["title", "loadMoreItemsLabel", "loadingItemsLabel", "showSearch", "refresh", "loading", "columns", "rows", "pagination", "childNodePagination", "infiniteScroll", "serverSideDataCallback", "selectable", "singleSelection", "selectionPrimaryKey", "displayOptions", "actionControls", "bulkActionControls", "headerActionControls", "searchText", "configureColumnsEnabled", "showCounterWarning", "activeClassName", "expandableRows", "treeGrid", "hideReload", "childNodesProperty", "parentNodeLabelProperty"], outputs: ["rowMouseOver", "rowMouseLeave", "rowClick", "onConfigChange", "onBeforeFilter", "onBeforeSearch", "onFilter", "itemsSelect", "onReload", "onAddCustomColumn", "onRemoveCustomColumn", "onColumnFilterReset", "onSort", "onPageSizeChange", "onColumnReordered", "onColumnVisibilityChange"] }, { kind: "directive", type: EmptyStateContextDirective, selector: "[emptyStateContext]" }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "component", type: GuideDocsComponent, selector: "[c8y-guide-docs]" }, { kind: "directive", type: GuideHrefDirective, selector: "[c8y-guide-href]", inputs: ["c8y-guide-href"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
1131
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigurationListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1132
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ConfigurationListComponent, isStandalone: true, selector: "c8y-configuration-list", ngImport: i0, template: "<c8y-title>\n <span\n class=\"m-r-4\"\n translate\n >\n Configuration repository\n </span>\n <small>\n {{ size$ | async }}\n <span translate>snapshots</span>\n </small>\n</c8y-title>\n\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n icon=\"c8y-management\"\n label=\"{{ 'Management' | translate }}\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n icon=\"gears\"\n label=\"{{ 'Configuration repository' | translate }}\"\n ></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Add configuration snapshot' | translate }}\"\n type=\"button\"\n (click)=\"add()\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add configuration snapshot' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<c8y-help\n src=\"/docs/device-management-application/managing-device-data/#managing-configurations\"\n></c8y-help>\n\n<div class=\"content-fullpage border-top border-bottom\">\n <c8y-data-grid\n [title]=\"'Configurations' | translate\"\n [refresh]=\"refresh$\"\n [pagination]=\"pagination\"\n [columns]=\"columns\"\n [actionControls]=\"actionControls\"\n [infiniteScroll]=\"'auto'\"\n [serverSideDataCallback]=\"serverSideDataCallback\"\n >\n <c8y-ui-empty-state\n [icon]=\"stats?.size > 0 ? 'search' : 'gears'\"\n [title]=\"stats?.size > 0 ? (noResultsMessage | translate) : (noDataMessage | translate)\"\n [subtitle]=\"stats?.size > 0 ? (noResultsSubtitle | translate) : (noDataSubtitle | translate)\"\n *emptyStateContext=\"let stats\"\n [horizontal]=\"stats?.size > 0\"\n >\n @if (stats?.size === 0) {\n <p>\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Add configuration snapshot' | translate }}\"\n type=\"button\"\n (click)=\"add()\"\n >\n {{ 'Add configuration snapshot' | translate }}\n </button>\n </p>\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a\n c8y-guide-href=\"/docs/device-management-application/managing-device-data/#managing-configurations\"\n >\n user documentation</a\n >.\n </small>\n </p>\n }\n </c8y-ui-empty-state>\n </c8y-data-grid>\n</div>\n", dependencies: [{ kind: "component", type: TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: BreadcrumbComponent, selector: "c8y-breadcrumb" }, { kind: "component", type: BreadcrumbItemComponent, selector: "c8y-breadcrumb-item", inputs: ["icon", "translate", "label", "path", "injector"] }, { kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: HelpComponent, selector: "c8y-help", inputs: ["src", "isCollapsed", "priority", "icon"] }, { kind: "component", type: DataGridComponent, selector: "c8y-data-grid", inputs: ["title", "loadMoreItemsLabel", "loadingItemsLabel", "showSearch", "refresh", "loading", "columns", "rows", "pagination", "childNodePagination", "infiniteScroll", "serverSideDataCallback", "selectable", "singleSelection", "selectionPrimaryKey", "displayOptions", "actionControls", "bulkActionControls", "headerActionControls", "searchText", "configureColumnsEnabled", "showCounterWarning", "activeClassName", "expandableRows", "treeGrid", "hideReload", "childNodesProperty", "parentNodeLabelProperty"], outputs: ["rowMouseOver", "rowMouseLeave", "rowClick", "onConfigChange", "onBeforeFilter", "onBeforeSearch", "onFilter", "itemsSelect", "onReload", "onAddCustomColumn", "onRemoveCustomColumn", "onColumnFilterReset", "onSort", "onPageSizeChange", "onColumnReordered", "onColumnVisibilityChange"] }, { kind: "directive", type: EmptyStateContextDirective, selector: "[emptyStateContext]" }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "component", type: GuideDocsComponent, selector: "[c8y-guide-docs]" }, { kind: "directive", type: GuideHrefDirective, selector: "[c8y-guide-href]", inputs: ["c8y-guide-href"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
953
1133
  }
954
1134
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigurationListComponent, decorators: [{
955
1135
  type: Component,
956
- args: [{ selector: 'c8y-configuration-list', imports: [
1136
+ args: [{ selector: 'c8y-configuration-list', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
957
1137
  TitleComponent,
958
1138
  C8yTranslateDirective,
959
1139
  BreadcrumbComponent,
@@ -969,10 +1149,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
969
1149
  AsyncPipe,
970
1150
  C8yTranslatePipe
971
1151
  ], template: "<c8y-title>\n <span\n class=\"m-r-4\"\n translate\n >\n Configuration repository\n </span>\n <small>\n {{ size$ | async }}\n <span translate>snapshots</span>\n </small>\n</c8y-title>\n\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n icon=\"c8y-management\"\n label=\"{{ 'Management' | translate }}\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n icon=\"gears\"\n label=\"{{ 'Configuration repository' | translate }}\"\n ></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Add configuration snapshot' | translate }}\"\n type=\"button\"\n (click)=\"add()\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add configuration snapshot' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<c8y-help\n src=\"/docs/device-management-application/managing-device-data/#managing-configurations\"\n></c8y-help>\n\n<div class=\"content-fullpage border-top border-bottom\">\n <c8y-data-grid\n [title]=\"'Configurations' | translate\"\n [refresh]=\"refresh$\"\n [pagination]=\"pagination\"\n [columns]=\"columns\"\n [actionControls]=\"actionControls\"\n [infiniteScroll]=\"'auto'\"\n [serverSideDataCallback]=\"serverSideDataCallback\"\n >\n <c8y-ui-empty-state\n [icon]=\"stats?.size > 0 ? 'search' : 'gears'\"\n [title]=\"stats?.size > 0 ? (noResultsMessage | translate) : (noDataMessage | translate)\"\n [subtitle]=\"stats?.size > 0 ? (noResultsSubtitle | translate) : (noDataSubtitle | translate)\"\n *emptyStateContext=\"let stats\"\n [horizontal]=\"stats?.size > 0\"\n >\n @if (stats?.size === 0) {\n <p>\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Add configuration snapshot' | translate }}\"\n type=\"button\"\n (click)=\"add()\"\n >\n {{ 'Add configuration snapshot' | translate }}\n </button>\n </p>\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a\n c8y-guide-href=\"/docs/device-management-application/managing-device-data/#managing-configurations\"\n >\n user documentation</a\n >.\n </small>\n </p>\n }\n </c8y-ui-empty-state>\n </c8y-data-grid>\n</div>\n" }]
972
- }], ctorParameters: () => [{ type: i2.AlertService }, { type: i2.DataGridService }, { type: i3.RepositoryService }, { type: i3$1.BsModalService }, { type: i2.ModalService }, { type: i4$2.TranslateService }, { type: i4.InventoryBinaryService }], propDecorators: { filter: [{
973
- type: ViewChild,
974
- args: [FilterInputComponent, { static: false }]
975
- }] } });
1152
+ }], ctorParameters: () => [] });
976
1153
 
977
1154
  class ConfigurationRepositoryNavigationFactory {
978
1155
  constructor() {
@@ -1043,5 +1220,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
1043
1220
  * Generated bundle index. Do not edit.
1044
1221
  */
1045
1222
 
1046
- export { ConfigurationDetailComponent, ConfigurationFilterPipe, ConfigurationListComponent, ConfigurationPreviewComponent, ConfigurationRepositoryDeviceTabModule, ConfigurationRepositoryListModule, ConfigurationRepositoryModule, DeviceConfigurationComponent, DeviceConfigurationGuard, DeviceConfigurationListComponent, DeviceConfigurationService, SaveToRepositoryComponent, SourceCodePreviewComponent, TextBasedConfigurationComponent };
1223
+ export { ConfigurationDetailComponent, ConfigurationFilterPipe, ConfigurationListComponent, ConfigurationPreviewComponent, ConfigurationRepositoryDeviceTabModule, ConfigurationRepositoryListModule, ConfigurationRepositoryModule, DeviceConfigurationComponent, DeviceConfigurationGuard, DeviceConfigurationListComponent, DeviceConfigurationService, SaveToRepositoryComponent, SourceCodePreviewComponent, TextBasedConfigurationComponent, detectLanguage };
1047
1224
  //# sourceMappingURL=c8y-ngx-components-repository-configuration.mjs.map