UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

566 lines (557 loc) 106 kB
import * as i0 from '@angular/core'; import { EventEmitter, Injectable, Component, Pipe, Input, Output, ViewChild, NgModule } from '@angular/core'; import * as i1 from '@angular/router'; import * as i4 from '@c8y/client'; import { OperationStatus } from '@c8y/client'; import { gettext } from '@c8y/ngx-components/gettext'; import * as i2 from '@c8y/ngx-components'; 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'; import * as i3 from '@c8y/ngx-components/repository/shared'; import { DeviceConfigurationOperation, RepositoryType, SharedRepositoryModule, RepositoryItemNameGridColumn, DescriptionGridColumn, FileGridColumn, DeviceTypeGridColumn, TypeGridColumn } from '@c8y/ngx-components/repository/shared'; import { NgIf, NgClass, NgFor, AsyncPipe } from '@angular/common'; import * as i4$1 from '@angular/forms'; import { FormsModule } from '@angular/forms'; import { OperationDetailsComponent, OperationDetailsModule } from '@c8y/ngx-components/operations/operation-details'; import { has, cloneDeep, uniqBy, isUndefined } from 'lodash-es'; import { saveAs } from 'file-saver'; import * as i3$1 from 'ngx-bootstrap/modal'; import { map } from 'rxjs/operators'; import * as i1$1 from 'ngx-bootstrap/tabs'; import { TabsetComponent, TabDirective, TabsModule } from 'ngx-bootstrap/tabs'; import * as i4$2 from '@ngx-translate/core'; import { pipe } from 'rxjs'; class DeviceConfigurationService { constructor() { this.configurationsUpdated = new EventEmitter(); } updateConfigurations(repositorySnapsOnly) { this.configurationsUpdated.emit(repositorySnapsOnly); } hasAnySupportedOperation(mo, operation) { const supported = mo.c8y_SupportedOperations; if (!supported) { return false; } if (!Array.isArray(operation)) { operation = [operation]; } return supported.some(supportedOperation => operation.includes(supportedOperation)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DeviceConfigurationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DeviceConfigurationService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DeviceConfigurationService, decorators: [{ type: Injectable }] }); class TextBasedConfigurationComponent { constructor(route, alertService, repositoryService, deviceConfigurationService, inventoryService) { this.route = route; this.alertService = alertService; this.repositoryService = repositoryService; this.deviceConfigurationService = deviceConfigurationService; this.inventoryService = inventoryService; this.reloadingConfig = false; } async ngOnInit() { await this.load(); } async load() { this.device = this.route.snapshot.parent.data.contextData; await this.loadDevice(); await this.loadOperation(); this.showTextBasedConfigReload = this.deviceConfigurationService.hasAnySupportedOperation(this.device, [DeviceConfigurationOperation.SEND_CONFIG]); this.showTextBasedConfigSave = this.deviceConfigurationService.hasAnySupportedOperation(this.device, [DeviceConfigurationOperation.CONFIG]); if (this.device.c8y_Configuration && this.device.c8y_Configuration.config) { this.config = this.device.c8y_Configuration.config; } } async loadOperation() { const operation = await this.repositoryService.getLastConfigUpdateOperation(this.device.id); if (operation !== null) { this.reloadingConfig = !!operation.c8y_SendConfiguration && (operation.status === OperationStatus.PENDING || operation.status === OperationStatus.EXECUTING); this.repositoryService.observeOperation(operation).subscribe(operationUpdate => { if (operationUpdate.status === OperationStatus.PENDING || operationUpdate.status === OperationStatus.EXECUTING) { this.latestOperation = operationUpdate; } else this.latestOperation = null; }); } } get savingConfig() { return this.latestOperation ? !!this.latestOperation.c8y_Configuration && (this.latestOperation.status === OperationStatus.PENDING || this.latestOperation.status === OperationStatus.EXECUTING) : false; } async reloadConfiguration() { this.reloadingConfig = true; const operationCfg = await this.repositoryService.createTextBasedConfigurationReloadOperation(this.device); try { this.repositoryService.createObservedOperation(operationCfg).subscribe(operationUpdate => this.onOperationReloadSuccess(operationUpdate), operationUpdate => this.onOperationReloadError(operationUpdate), () => this.onOperationReloadComplete()); } catch (ex) { this.alertService.addServerFailure(ex); } } async updateConfiguration(config) { const operationCfg = await this.repositoryService.createTextBasedConfigurationUpdateOperation(this.device, config); try { this.repositoryService.createObservedOperation(operationCfg).subscribe(operationUpdate => this.onOperationUpdateSuccess(operationUpdate), operationUpdate => this.onOperationUpdateError(operationUpdate), () => this.onOperationUpdateComplete()); } catch (ex) { this.alertService.addServerFailure(ex); } } onOperationReloadSuccess(operationUpdate) { this.latestOperation = operationUpdate; if (operationUpdate.status === OperationStatus.PENDING) { this.alertService.success(gettext('Configuration will be reloaded.')); } } onOperationReloadError(operationUpdate) { this.latestOperation = operationUpdate; this.reloadingConfig = false; } async onOperationReloadComplete() { await this.loadDevice(); this.config = this.device.c8y_Configuration.config; this.reloadingConfig = false; } onOperationUpdateSuccess(operationUpdate) { this.latestOperation = operationUpdate; if (operationUpdate.status === OperationStatus.PENDING) { this.alertService.success(gettext('Configuration will be updated.')); } } onOperationUpdateError(operationUpdate) { this.latestOperation = operationUpdate; } onOperationUpdateComplete() { this.device.c8y_Configuration.config = this.config; } async loadDevice() { this.device = (await this.inventoryService.detail(this.device.id, { withChildren: false })).data; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: TextBasedConfigurationComponent, deps: [{ token: i1.ActivatedRoute }, { token: i2.AlertService }, { token: i3.RepositoryService }, { token: DeviceConfigurationService }, { token: i4.InventoryService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: TextBasedConfigurationComponent, isStandalone: true, selector: "c8y-text-based-configuration", ngImport: i0, template: "<div class=\"d-flex d-col fit-h\">\n <fieldset class=\"card-block bg-level-1 fit-w\">\n <div class=\"content-flex-50\">\n <div class=\"m-l-auto d-flex\">\n <button\n class=\"btn btn-default btn-sm a-s-center m-t-8 m-b-8\"\n title=\"{{ 'Get configuration from device' | translate }}\"\n type=\"button\"\n *ngIf=\"showTextBasedConfigReload\"\n (click)=\"reloadConfiguration()\"\n [disabled]=\"reloadingConfig || savingConfig\"\n >\n <i\n class=\"m-r-4\"\n c8yIcon=\"refresh\"\n *ngIf=\"reloadingConfig\"\n [ngClass]=\"{ 'icon-spin': reloadingConfig }\"\n ></i>\n <i\n class=\"m-r-4\"\n c8yIcon=\"download\"\n *ngIf=\"!reloadingConfig\"\n ></i>\n\n {{ 'Get configuration from device' | translate }}\n </button>\n </div>\n </div>\n </fieldset>\n <div class=\"flex-grow\">\n <textarea\n class=\"form-control fit-h p-r-16 p-l-16\"\n [attr.aria-label]=\"'Operations' | translate\"\n [(ngModel)]=\"config\"\n [disabled]=\"reloadingConfig || savingConfig\"\n c8y-spellcheck=\"false\"\n ></textarea>\n </div>\n <c8y-operation-details\n class=\"bg-level-2 p-0\"\n *ngIf=\"latestOperation !== undefined\"\n [operation]=\"latestOperation\"\n ></c8y-operation-details>\n <div\n class=\"card-footer fit-w separator\"\n *ngIf=\"showTextBasedConfigSave\"\n >\n <button\n class=\"btn btn-primary\"\n id=\"send-config-btn\"\n type=\"button\"\n (click)=\"updateConfiguration(config)\"\n [disabled]=\"reloadingConfig || savingConfig || !config\"\n [ngClass]=\"{ 'btn-pending': savingConfig }\"\n >\n <span\n title=\"{{ 'Send' | translate }}\"\n *ngIf=\"!savingConfig\"\n >\n {{ 'Send configuration to device' | translate }}\n </span>\n <span\n title=\"{{ 'Sending\u2026' | translate }}\"\n *ngIf=\"savingConfig\"\n >\n {{ 'Sending\u2026' | translate }}\n </span>\n </button>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: OperationDetailsComponent, selector: "c8y-operation-details", inputs: ["operation"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: TextBasedConfigurationComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-text-based-configuration', imports: [NgIf, IconDirective, NgClass, FormsModule, OperationDetailsComponent, C8yTranslatePipe], template: "<div class=\"d-flex d-col fit-h\">\n <fieldset class=\"card-block bg-level-1 fit-w\">\n <div class=\"content-flex-50\">\n <div class=\"m-l-auto d-flex\">\n <button\n class=\"btn btn-default btn-sm a-s-center m-t-8 m-b-8\"\n title=\"{{ 'Get configuration from device' | translate }}\"\n type=\"button\"\n *ngIf=\"showTextBasedConfigReload\"\n (click)=\"reloadConfiguration()\"\n [disabled]=\"reloadingConfig || savingConfig\"\n >\n <i\n class=\"m-r-4\"\n c8yIcon=\"refresh\"\n *ngIf=\"reloadingConfig\"\n [ngClass]=\"{ 'icon-spin': reloadingConfig }\"\n ></i>\n <i\n class=\"m-r-4\"\n c8yIcon=\"download\"\n *ngIf=\"!reloadingConfig\"\n ></i>\n\n {{ 'Get configuration from device' | translate }}\n </button>\n </div>\n </div>\n </fieldset>\n <div class=\"flex-grow\">\n <textarea\n class=\"form-control fit-h p-r-16 p-l-16\"\n [attr.aria-label]=\"'Operations' | translate\"\n [(ngModel)]=\"config\"\n [disabled]=\"reloadingConfig || savingConfig\"\n c8y-spellcheck=\"false\"\n ></textarea>\n </div>\n <c8y-operation-details\n class=\"bg-level-2 p-0\"\n *ngIf=\"latestOperation !== undefined\"\n [operation]=\"latestOperation\"\n ></c8y-operation-details>\n <div\n class=\"card-footer fit-w separator\"\n *ngIf=\"showTextBasedConfigSave\"\n >\n <button\n class=\"btn btn-primary\"\n id=\"send-config-btn\"\n type=\"button\"\n (click)=\"updateConfiguration(config)\"\n [disabled]=\"reloadingConfig || savingConfig || !config\"\n [ngClass]=\"{ 'btn-pending': savingConfig }\"\n >\n <span\n title=\"{{ 'Send' | translate }}\"\n *ngIf=\"!savingConfig\"\n >\n {{ 'Send configuration to device' | translate }}\n </span>\n <span\n title=\"{{ 'Sending\u2026' | translate }}\"\n *ngIf=\"savingConfig\"\n >\n {{ 'Sending\u2026' | translate }}\n </span>\n </button>\n </div>\n</div>\n" }] }], ctorParameters: () => [{ type: i1.ActivatedRoute }, { type: i2.AlertService }, { type: i3.RepositoryService }, { type: DeviceConfigurationService }, { type: i4.InventoryService }] }); class DeviceConfigurationGuard { constructor(deviceConfigurationService) { this.deviceConfigurationService = deviceConfigurationService; } canActivate(route) { const contextData = route.data.contextData || route.parent.data.contextData; if (!contextData) { return false; } return ((contextData.c8y_SupportedConfigurations && contextData.c8y_SupportedConfigurations.length > 0) || this.deviceConfigurationService.hasAnySupportedOperation(contextData, [ DeviceConfigurationOperation.DOWNLOAD_CONFIG, DeviceConfigurationOperation.UPLOAD_CONFIG, DeviceConfigurationOperation.CONFIG, DeviceConfigurationOperation.SEND_CONFIG ]) || has(contextData, 'c8y_Configuration')); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DeviceConfigurationGuard, deps: [{ token: DeviceConfigurationService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DeviceConfigurationGuard }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DeviceConfigurationGuard, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: DeviceConfigurationService }] }); class ConfigurationFilterPipe { transform(items, filterTerm) { return filterTerm.trim().length === 0 ? items : items.filter((item) => this.filterContainString(item.name, filterTerm) || this.filterContainString(item.deviceType, filterTerm)); } filterContainString(name, filterTerm) { const term = filterTerm.toLowerCase().trim(); return name && name.toLowerCase().indexOf(term) > -1; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ConfigurationFilterPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.19", ngImport: i0, type: ConfigurationFilterPipe, isStandalone: true, name: "configurationFilterPipe" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ConfigurationFilterPipe, decorators: [{ type: Pipe, args: [{ name: 'configurationFilterPipe' }] }] }); class SaveToRepositoryComponent { constructor(modal, alertService, repositoryService) { this.modal = modal; this.alertService = alertService; this.repositoryService = repositoryService; this.result = new Promise((resolve, reject) => { this._save = resolve; this._cancel = reject; }); } async save() { { try { const configSnapshotData = { selected: { configurationType: this.configSnapshot.configurationType }, version: this.configSnapshot.name, deviceType: this.configSnapshot.deviceType, description: this.configSnapshot.description, binary: { file: new File([this.configSnapshot.binary], this.configSnapshot.name) } }; await this.repositoryService.create(configSnapshotData, RepositoryType.CONFIGURATION); this.alertService.success(gettext('Configuration saved.')); this._save(); } catch (ex) { this.alertService.addServerFailure(ex); } } } close() { this._cancel(); this.modal.hide(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SaveToRepositoryComponent, deps: [{ token: i3$1.BsModalRef }, { token: i2.AlertService }, { token: i3.RepositoryService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", 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" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SaveToRepositoryComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-save-config-to-configuration-repository', imports: [ IconDirective, C8yTranslateDirective, FormsModule, FormGroupComponent, RequiredInputPlaceholderDirective, C8yTranslatePipe ], 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" }] }], ctorParameters: () => [{ type: i3$1.BsModalRef }, { type: i2.AlertService }, { type: i3.RepositoryService }] }); class SourceCodePreviewComponent { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SourceCodePreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: SourceCodePreviewComponent, isStandalone: true, selector: "c8y-source-code-preview", inputs: { isDisabled: "isDisabled", text: "text" }, ngImport: i0, template: "<textarea\n [disabled]=\"isDisabled\"\n class=\"text-monospace form-control no-resize flex-grow\"\n rows=\"4\"\n >{{ text }}</textarea\n>\n" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SourceCodePreviewComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-source-code-preview', template: "<textarea\n [disabled]=\"isDisabled\"\n class=\"text-monospace form-control no-resize flex-grow\"\n rows=\"4\"\n >{{ text }}</textarea\n>\n" }] }], propDecorators: { isDisabled: [{ type: Input }], text: [{ type: Input }] } }); class ConfigurationPreviewComponent { set configurationType(type) { this._configurationType = type; this.setOperation(type); } get configurationType() { return this._configurationType; } constructor(deviceConfigurationService, operationRealtime, bsModal, user, appState, repositoryService, operationService, alertService) { this.deviceConfigurationService = deviceConfigurationService; this.operationRealtime = operationRealtime; this.bsModal = bsModal; this.user = user; this.appState = appState; this.repositoryService = repositoryService; this.operationService = operationService; this.alertService = alertService; this.isLegacy = false; this.canCallAction = true; this.deviceConfigurationOperation = DeviceConfigurationOperation; } async ngOnInit() { this.setCanCallAction(); this.setOperation(this._configurationType); this.operationsSubscription = this.operationRealtime .onAll$(this.device.id) .pipe(map(({ data }) => data)) .subscribe(operation => { this.updatePreview(operation); }); } async setOperation(configType) { const operationList = await this.repositoryService.getConfigFileOperationList(this.device.id, this.operationToTrigger); const operation = this.isLegacy ? operationList.find(op => op[this.operationToTrigger] && !op[this.operationToTrigger].type) : operationList.find(op => op[this.operationToTrigger].type === configType); this.operation = operation && operation.status !== OperationStatus.SUCCESSFUL ? operation : undefined; } setCanCallAction() { this.canCallAction = this.deviceConfigurationService.hasAnySupportedOperation(this.device, this.operationToTrigger); } async createDeviceOperation() { let operationCfg; if (this.operationToTrigger === DeviceConfigurationOperation.DOWNLOAD_CONFIG) { operationCfg = this.repositoryService.getDownloadConfigurationFileOperation(this.device, this._configurationType, this.configSnapshot, this.isLegacy); } if (this.operationToTrigger === DeviceConfigurationOperation.UPLOAD_CONFIG) { operationCfg = this.repositoryService.getUploadConfigurationFileOperation(this.device, this._configurationType, this.isLegacy); } try { this.operation = (await this.operationService.create(operationCfg)).data; } catch (ex) { this.alertService.addServerFailure(ex); } } showOperation() { if (this.operationToTrigger === DeviceConfigurationOperation.DOWNLOAD_CONFIG) { return !!this.operation; } return (this.operation && [OperationStatus.PENDING, OperationStatus.EXECUTING].includes(this.operation.status)); } showBinary() { if (this.operationToTrigger === DeviceConfigurationOperation.DOWNLOAD_CONFIG) { return true; } return !this.showOperation(); } isCreateOperationDisabled() { return (this.operation && [OperationStatus.PENDING, OperationStatus.EXECUTING].includes(this.operation.status)); } updatePreview(operation) { if (operation && operation[this.operationToTrigger] && (this.isLegacy || (operation[this.operationToTrigger].type && operation[this.operationToTrigger].type === this.configurationType))) { this.operation = operation; this.updateSnapshotsOnConfigUpload(operation); } } download() { const blob = new Blob([this.configSnapshot.binary], { type: this.configSnapshot.binaryType }); let fileName = this.configSnapshot.name; switch (this.configSnapshot.binaryType) { case 'text/csv': case 'application/csv': fileName = fileName.concat('.csv'); break; case 'text/yaml': case 'application/x-yaml': fileName = fileName.concat('.yaml'); break; case 'application/json': fileName = fileName.concat('.json'); break; } saveAs(blob, fileName); } async saveToRepository() { const initialState = { configSnapshot: cloneDeep(this.configSnapshot) }; const modal = this.bsModal.show(SaveToRepositoryComponent, { class: 'modal-sm', ariaDescribedby: 'modal-body', ariaLabelledBy: 'modal-title', initialState, ignoreBackdropClick: true }).content; try { await modal.result; this.deviceConfigurationService.updateConfigurations(true); modal.close(); } catch (ex) { // do nothing } } hasPermission() { return (this.user.hasAnyRole(this.appState.currentUser.value, [ Permissions.ROLE_INVENTORY_ADMIN, Permissions.ROLE_INVENTORY_CREATE ]) || (this.user.hasAnyRole(this.appState.currentUser.value, [ Permissions.ROLE_MANAGED_OBJECT_ADMIN, Permissions.ROLE_MANAGED_OBJECT_CREATE ]) && this.user.hasAnyRole(this.appState.currentUser.value, [ Permissions.ROLE_BINARY_ADMIN, Permissions.ROLE_BINARY_CREATE ]))); } ngOnDestroy() { if (this.operationsSubscription) { this.operationsSubscription.unsubscribe(); } } async updateSnapshotsOnConfigUpload(operation) { if (operation[DeviceConfigurationOperation.UPLOAD_CONFIG] && operation.status === OperationStatus.SUCCESSFUL) { this.deviceConfigurationService.updateConfigurations(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", 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 }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", 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" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ConfigurationPreviewComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-device-configuration-preview', imports: [ C8yTranslateDirective, NgIf, IconDirective, SourceCodePreviewComponent, OperationDetailsComponent, C8yTranslatePipe, DatePipe ], 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" }] }], 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: [{ type: Input }], configurationType: [{ type: Input }], configSnapshot: [{ type: Input }], canSaveSnapshot: [{ type: Input }], actionButtonText: [{ type: Input }], actionButtonIcon: [{ type: Input }], isLegacy: [{ type: Input }], operationToTrigger: [{ type: Input }] } }); class DeviceConfigurationListComponent { constructor() { this.configSelected = new EventEmitter(); this.filterTerm = ''; } showConfigurationTypePreview(config) { this.selectedConfig = config; this.configSelected.emit(config); } updatePipe(filterTerm) { this.filterTerm = filterTerm; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DeviceConfigurationListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: DeviceConfigurationListComponent, isStandalone: true, selector: "c8y-device-configuration-list", inputs: { items: "items", itemIcon: "itemIcon", emptyState: "emptyState", isFilterEnabled: "isFilterEnabled" }, outputs: { configSelected: "configSelected" }, ngImport: i0, template: "<div class=\"p-l-16 m-b-8\" *ngIf=\"isFilterEnabled\">\n <c8y-filter [icon]=\"'search'\" (onSearch)=\"updatePipe($event)\"></c8y-filter>\n</div>\n\n<!-- EMPTY STATE -->\n<div class=\"c8y-empty-state text-left\" *ngIf=\"items?.length === 0\">\n <h1 [c8yIcon]=\"emptyState.icon\"></h1>\n <p>\n <strong>{{ emptyState.title | translate }}</strong>\n <br />\n <small>{{ emptyState.text | translate }}</small>\n </p>\n</div>\n\n<!-- CONFIGURATIONS AVAILABLE -->\n<div class=\"c8y-nav-stacked\">\n <button\n type=\"button\"\n class=\"c8y-stacked-item d-flex\"\n [class.active]=\"config === selectedConfig\"\n *ngFor=\"let config of items | configurationFilterPipe: filterTerm\"\n (click)=\"showConfigurationTypePreview(config)\"\n >\n <div class=\"list-item-icon\">\n <i [c8yIcon]=\"itemIcon\"></i>\n </div>\n <div class=\"list-item-body text-truncate\">\n <div class=\"d-flex\">\n <span class=\"text-truncate\" title=\"{{ config.name }}\">{{ config.name }}</span>\n <span class=\"text-label-small m-l-auto m-t-auto m-b-auto\">{{ config.deviceType }}</span>\n </div>\n </div>\n </button>\n</div>\n\n<!-- for Carlos: config.configurationType to differentiate whether a config matches configuration type. -->\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FilterInputComponent, selector: "c8y-filter", inputs: ["icon", "filterTerm"], outputs: ["onSearch"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: ConfigurationFilterPipe, name: "configurationFilterPipe" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DeviceConfigurationListComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-device-configuration-list', imports: [ NgIf, FilterInputComponent, IconDirective, NgFor, C8yTranslatePipe, ConfigurationFilterPipe ], template: "<div class=\"p-l-16 m-b-8\" *ngIf=\"isFilterEnabled\">\n <c8y-filter [icon]=\"'search'\" (onSearch)=\"updatePipe($event)\"></c8y-filter>\n</div>\n\n<!-- EMPTY STATE -->\n<div class=\"c8y-empty-state text-left\" *ngIf=\"items?.length === 0\">\n <h1 [c8yIcon]=\"emptyState.icon\"></h1>\n <p>\n <strong>{{ emptyState.title | translate }}</strong>\n <br />\n <small>{{ emptyState.text | translate }}</small>\n </p>\n</div>\n\n<!-- CONFIGURATIONS AVAILABLE -->\n<div class=\"c8y-nav-stacked\">\n <button\n type=\"button\"\n class=\"c8y-stacked-item d-flex\"\n [class.active]=\"config === selectedConfig\"\n *ngFor=\"let config of items | configurationFilterPipe: filterTerm\"\n (click)=\"showConfigurationTypePreview(config)\"\n >\n <div class=\"list-item-icon\">\n <i [c8yIcon]=\"itemIcon\"></i>\n </div>\n <div class=\"list-item-body text-truncate\">\n <div class=\"d-flex\">\n <span class=\"text-truncate\" title=\"{{ config.name }}\">{{ config.name }}</span>\n <span class=\"text-label-small m-l-auto m-t-auto m-b-auto\">{{ config.deviceType }}</span>\n </div>\n </div>\n </button>\n</div>\n\n<!-- for Carlos: config.configurationType to differentiate whether a config matches configuration type. -->\n" }] }], propDecorators: { items: [{ type: Input }], itemIcon: [{ type: Input }], emptyState: [{ type: Input }], isFilterEnabled: [{ type: Input }], configSelected: [{ type: Output }] } }); class DeviceConfigurationComponent { constructor(route, deviceConfigurationService, realtime, repositoryService) { this.route = route; this.deviceConfigurationService = deviceConfigurationService; this.realtime = realtime; this.repositoryService = repositoryService; this.supportedConfigurations = []; this.showBinaryBasedConfig = false; this.configSnapshot = {}; this.reloading = false; this.deviceConfigurationService.configurationsUpdated.subscribe(repositorySnapsOnly => { this.updateSnapshots(repositorySnapsOnly); }); } ngOnInit() { this.device = this.route.snapshot.parent.data.contextData; if (this.device.c8y_SupportedConfigurations) { this.supportedConfigurations = this.device.c8y_SupportedConfigurations.map(item => ({ name: item })); } if (this.deviceConfigurationService.hasAnySupportedOperation(this.device, [ DeviceConfigurationOperation.DOWNLOAD_CONFIG, DeviceConfigurationOperation.UPLOAD_CONFIG ])) { this.supportedConfigurations.push({ name: gettext('Legacy configuration snapshot'), isLegacy: true }); } if (this.supportedConfigurations.length > 0) { this.showBinaryBasedConfig = true; } this.repositorySnapshotsEmptyState = { icon: 'gears', title: gettext('No configurations available.'), text: gettext('Add configuration to configuration repository') }; this.showTextBasedConfig = this.deviceConfigurationService.hasAnySupportedOperation(this.device, [ DeviceConfigurationOperation.CONFIG, DeviceConfigurationOperation.SEND_CONFIG ]) || has(this.device, 'c8y_Configuration'); } async onConfigTypeSelected(config) { this.configurationType = config.name; this.isLegacy = config.isLegacy; this.updateSnapshots(); } async onRepositoryConfigSelected(config) { this.repositorySnapshot = { id: config.id, time: config.creationTime, name: config.name, binaryUrl: config.url, deviceType: config.deviceType, configurationType: config.configurationType }; if (config.url) { try { const binary = await this.repositoryService.getBinaryFile(config.url, { allowExternal: false }); if (binary) { this.repositorySnapshot.binary = await binary.text(); } } catch (ex) { // do nothing } } } async updateSnapshots(repositorySnapsOnly) { this.reloading = true; this.repositorySnapshot = undefined; this.repositorySnapshots = await this.repositoryService.getSnapshotsFromRepository(this.device, this.configurationType); if (!repositorySnapsOnly) { this.configSnapshot = this.isLegacy ? await this.repositoryService.getLegacyConfigSnapshot(this.device) : await this.repositoryService.getConfigSnapshot(this.device, this.configurationType); } if (this.showTextBasedConfig) { await this.textBasedConfigurationComponent.load(); } this.reloading = false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DeviceConfigurationComponent, deps: [{ token: i1.ActivatedRoute }, { token: DeviceConfigurationService }, { token: i4.Realtime }, { token: i3.RepositoryService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: DeviceConfigurationComponent, isStandalone: true, selector: "c8y-device-configuration", viewQueries: [{ propertyName: "textBasedConfigurationComponent", first: true, predicate: TextBasedConfigurationComponent, descendants: true }], ngImport: i0, template: "<c8y-action-bar-item [placement]=\"'right'\">\n <button class=\"btn btn-link\" title=\"{{ 'Reload' | translate }}\" (click)=\"updateSnapshots()\">\n <i c8yIcon=\"refresh\" [ngClass]=\"{ 'icon-spin': reloading }\"></i>\n {{ 'Reload' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<div class=\"card content-fullpage card-has-tabs\">\n <tabset>\n <div class=\"card-header separator\" *ngIf=\"showBinaryBasedConfig && !showTextBasedConfig\">\n <div class=\"card-title\">{{ 'Configurations' | translate }}</div>\n </div>\n <div class=\"card-header separator\" *ngI