UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

421 lines (418 loc) 148 kB
import * as i0 from '@angular/core'; import { EventEmitter, Output, ViewChild, Component, Injectable, NgModule, Input } from '@angular/core'; import * as i1$2 from '@angular/router'; import { gettext } from '@c8y/ngx-components/gettext'; import * as i2 from '@c8y/ngx-components'; import { ValidationPattern, IconDirective, C8yTranslateDirective, FormGroupComponent, TypeaheadComponent, ForOfDirective, ListItemComponent, HighlightComponent, RequiredInputPlaceholderDirective, FilePickerComponent, ProductExperienceDirective, C8yTranslatePipe, BuiltInActionType, Status, TitleComponent, BreadcrumbComponent, BreadcrumbItemComponent, ActionBarItemComponent, HelpComponent, DataGridComponent, EmptyStateContextDirective, EmptyStateComponent, ColumnDirective, ListGroupComponent, ListItemIconComponent, ListItemBodyComponent, memoize, NavigatorNode, hookNavigator, hookRoute, ModalSelectionMode, ViewContext } from '@c8y/ngx-components'; import * as i1$1 from '@c8y/ngx-components/repository/shared'; import { PRODUCT_EXPERIENCE_REPOSITORY_SHARED, RepositoryType, SoftwareTypeComponent, RepositoryItemNameGridColumn, DescriptionGridColumn, DeviceTypeGridColumn, TypeGridColumn, VersionsGridColumn, FileDownloadComponent, SharedRepositoryModule, RepositorySelectModalComponent } from '@c8y/ngx-components/repository/shared'; import * as i4$1 from '@ngx-translate/core'; import * as i1 from 'ngx-bootstrap/modal'; import * as i4 from '@angular/forms'; import { FormsModule } from '@angular/forms'; import { isUndefined, assign, get, head, set, filter, has } from 'lodash-es'; import { BehaviorSubject, from, Subject, merge, of, combineLatest, pipe } from 'rxjs'; import { tap, debounceTime, distinctUntilChanged, switchMap, map, shareReplay, distinctUntilKeyChanged, withLatestFrom, takeUntil, take, share, mergeMap, filter as filter$1 } from 'rxjs/operators'; import { NgIf, NgClass, AsyncPipe, NgFor, NgStyle } from '@angular/common'; import { PopoverDirective } from 'ngx-bootstrap/popover'; import { __decorate, __metadata } from 'tslib'; import * as i2$1 from '@c8y/client'; import { QueriesUtil, OperationStatus } from '@c8y/client'; import { TooltipDirective } from 'ngx-bootstrap/tooltip'; import { OperationDetailsComponent, OperationDetailsModule } from '@c8y/ngx-components/operations/operation-details'; class AddSoftwareModalComponent { constructor(modal, repositoryService, alert) { this.modal = modal; this.repositoryService = repositoryService; this.alert = alert; this.PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_REPOSITORY_SHARED; this.saved = new EventEmitter(); this.onInput = new BehaviorSubject(''); this.model = { selected: undefined, version: undefined, description: undefined, deviceType: undefined, softwareType: undefined, binary: { file: undefined, url: undefined } }; this.saving = false; this.softwarePreselected = false; this.textForSoftwareUrlPopover = gettext(`Path for binaries can vary depending on device agent implementation, for example: /software/binaries/software1.bin https://software/binary/123 ftp://software/binary/123.tar.gz `); this.ValidationPattern = ValidationPattern; } ngOnInit() { this.setInitialState(); this.loadSoftwares(); } setInitialState() { if (this.model.selected) { this.softwarePreselected = true; } } loadSoftwares() { this.inputSubscription$ = this.onInput .pipe(tap(() => { if (!this.softwarePreselected) { this.model.description = null; if (this.form) { this.form.form.get('description').reset(); } } }), debounceTime(300), distinctUntilChanged(), switchMap(searchStr => this.getSoftwareResult(searchStr))) .subscribe(result => { this.softwaresResult = result; }); } getSoftwareResult(searchStr) { return from(this.repositoryService.listRepositoryEntries(RepositoryType.SOFTWARE, { partialName: searchStr, skipLegacy: true })); } async save() { this.saving = true; this.repositoryService .create(this.model, RepositoryType.SOFTWARE) .then(savedSoftware => { this.successMsg(); this.saving = false; this.saved.next(savedSoftware); this.cancel(); }) .catch(e => { this.saving = false; this.saved.error(e); this.cancel(); }); } successMsg() { const msg = gettext('Software added.'); this.alert.success(msg); } cancel() { this.modal.hide(); this.saved.complete(); } ngOnDestroy() { this.inputSubscription$.unsubscribe(); } onFile(dropped) { if (!isUndefined(dropped.url)) { this.model.binary = { url: dropped.url }; return; } else if (dropped.droppedFiles) { this.model.binary = { file: dropped.droppedFiles[0].file }; return; } else { this.model.binary = { file: undefined, url: undefined }; } } onSoftwareSelect(software) { assign(this.model, { selected: software, description: software.description, deviceType: get(software, 'c8y_Filter.type'), softwareType: software }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AddSoftwareModalComponent, deps: [{ token: i1.BsModalRef }, { token: i1$1.RepositoryService }, { token: i2.AlertService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: AddSoftwareModalComponent, isStandalone: true, selector: "c8y-add-software-software-modal", outputs: { saved: "saved" }, viewQueries: [{ propertyName: "form", first: true, predicate: ["softwareForm"], descendants: true }], ngImport: i0, template: "<div class=\"viewport-modal\">\n <div class=\"modal-header dialog-header\">\n <i [c8yIcon]=\"'c8y-tools'\"></i>\n <div\n class=\"modal-title\"\n id=\"addSoftwareModalTitle\"\n translate\n >\n Add software\n </div>\n </div>\n <div\n class=\"p-16 text-center separator-bottom\"\n *ngIf=\"!softwarePreselected\"\n >\n <p\n class=\"text-medium text-16\"\n translate\n >\n Select or create new software\n </p>\n </div>\n <form\n class=\"d-contents\"\n autocomplete=\"off\"\n #softwareForm=\"ngForm\"\n (ngSubmit)=\"softwareForm.form.valid && save()\"\n >\n <div class=\"modal-inner-scroll\">\n <div\n class=\"modal-body\"\n id=\"addSoftwareModalDescription\"\n >\n <div [hidden]=\"softwarePreselected\">\n <c8y-form-group>\n <label\n for=\"softwareName\"\n translate\n >\n Software\n </label>\n <c8y-typeahead\n placeholder=\"{{ 'Select or enter' | translate }}\"\n name=\"softwareName\"\n [(ngModel)]=\"model.selected\"\n (onSearch)=\"onInput.next($event)\"\n [required]=\"true\"\n >\n <c8y-li\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n *c8yFor=\"\n let software of softwaresResult;\n loadMore: 'auto';\n notFound: notFoundTemplate\n \"\n (click)=\"onSoftwareSelect(software)\"\n [active]=\"model.selected === software\"\n >\n <c8y-highlight\n [text]=\"software.name || '--'\"\n [pattern]=\"onInput | async\"\n ></c8y-highlight>\n </c8y-li>\n <ng-template #notFoundTemplate>\n <c8y-li\n class=\"bg-level-2 p-8\"\n *ngIf=\"(onInput | async)?.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`software`' | translate }}\"\n type=\"button\"\n >\n {{ 'Add new`software`' | translate }}\n </button>\n </c8y-li>\n </ng-template>\n </c8y-typeahead>\n </c8y-form-group>\n\n <c8y-form-group>\n <label\n for=\"softwareDescription\"\n translate\n >\n Description\n </label>\n <input\n class=\"form-control\"\n id=\"softwareDescription\"\n placeholder=\"{{ 'e.g. Cloud connectivity software' | translate }}\"\n name=\"description\"\n autocomplete=\"off\"\n [(ngModel)]=\"model.description\"\n [disabled]=\"model.selected?.id\"\n [required]=\"true\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label\n class=\"control-label\"\n for=\"softwareDeviceTypeFilter\"\n >\n {{ 'Device type filter' | translate }}\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'If the filter is set, the software will show up for installation only for devices of that type. If no filter is set, it will be available for all devices.'\n | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n <input\n class=\"form-control\"\n id=\"softwareDeviceTypeFilter\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n name=\"softwareDeviceTypeFilter\"\n [(ngModel)]=\"model.deviceType\"\n [disabled]=\"model.selected?.id\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label\n for=\"softwareType\"\n translate\n >\n Software type\n </label>\n <c8y-software-type\n name=\"softwareType\"\n [(ngModel)]=\"model.softwareType\"\n [disabled]=\"model.selected?.id\"\n ></c8y-software-type>\n </c8y-form-group>\n </div>\n\n <c8y-form-group>\n <label\n for=\"softwareVersion\"\n translate\n >\n Version\n </label>\n <input\n class=\"form-control\"\n id=\"softwareVersion\"\n placeholder=\"{{ 'e.g.' | translate }} 1.0.0\"\n name=\"version\"\n autocomplete=\"off\"\n [(ngModel)]=\"model.version\"\n [required]=\"true\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <div\n class=\"legend form-block m-t-40\"\n translate\n >\n Software file\n </div>\n <c8y-file-picker\n [maxAllowedFiles]=\"1\"\n [allowedUploadChoices]=\"['uploadBinary', 'uploadUrl', 'provided']\"\n (onFilesPicked)=\"onFile($event)\"\n [fileUrlPopover]=\"textForSoftwareUrlPopover\"\n ></c8y-file-picker>\n </c8y-form-group>\n </div>\n </div>\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 {{ 'Cancel' | translate }}\n </button>\n\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Add software' | translate }}\"\n type=\"submit\"\n [ngClass]=\"{ 'btn-pending': saving }\"\n [disabled]=\"\n !softwareForm.form.valid ||\n softwareForm.form.pristine ||\n saving ||\n (!model.binary?.url && !model.binary?.file)\n \"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.SOFTWARE.EVENTS.REPOSITORY\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.SOFTWARE.COMPONENTS.ADD_SOFTWARE_MODAL,\n result:\n softwarePreselected || model.selected?.id\n ? PRODUCT_EXPERIENCE.SOFTWARE.RESULTS.ADD_SOFTWARE_VERSION\n : PRODUCT_EXPERIENCE.SOFTWARE.RESULTS.ADD_SOFTWARE\n }\"\n >\n {{ 'Add software' | translate }}\n </button>\n </div>\n </form>\n</div>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.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: "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: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "directive", type: PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "component", type: SoftwareTypeComponent, selector: "c8y-software-type", inputs: ["softwareTypeMO", "disabled", "style", "required", "placeholder", "emitResultsOnly", "showBtnInNotFoundMessage", "allowFreeEntries", "showClearSelectionOption", "clearSelectionOptionLabel", "presetSoftwareTypes"], outputs: ["onSelectSoftware"] }, { kind: "component", type: FilePickerComponent, selector: "c8y-file-picker", inputs: ["maxAllowedFiles", "uploadChoice", "allowedUploadChoices", "fileUrl", "fileBinary", "config", "filePickerIndex", "fileUrlPopover"], outputs: ["onFilesPicked"] }, { kind: "directive", type: ProductExperienceDirective, selector: "[c8yProductExperience]", inputs: ["actionName", "actionData", "inherit", "suppressDataOverriding"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AddSoftwareModalComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-add-software-software-modal', imports: [ IconDirective, C8yTranslateDirective, NgIf, FormsModule, FormGroupComponent, TypeaheadComponent, ForOfDirective, ListItemComponent, HighlightComponent, RequiredInputPlaceholderDirective, PopoverDirective, SoftwareTypeComponent, FilePickerComponent, ProductExperienceDirective, NgClass, AsyncPipe, C8yTranslatePipe ], template: "<div class=\"viewport-modal\">\n <div class=\"modal-header dialog-header\">\n <i [c8yIcon]=\"'c8y-tools'\"></i>\n <div\n class=\"modal-title\"\n id=\"addSoftwareModalTitle\"\n translate\n >\n Add software\n </div>\n </div>\n <div\n class=\"p-16 text-center separator-bottom\"\n *ngIf=\"!softwarePreselected\"\n >\n <p\n class=\"text-medium text-16\"\n translate\n >\n Select or create new software\n </p>\n </div>\n <form\n class=\"d-contents\"\n autocomplete=\"off\"\n #softwareForm=\"ngForm\"\n (ngSubmit)=\"softwareForm.form.valid && save()\"\n >\n <div class=\"modal-inner-scroll\">\n <div\n class=\"modal-body\"\n id=\"addSoftwareModalDescription\"\n >\n <div [hidden]=\"softwarePreselected\">\n <c8y-form-group>\n <label\n for=\"softwareName\"\n translate\n >\n Software\n </label>\n <c8y-typeahead\n placeholder=\"{{ 'Select or enter' | translate }}\"\n name=\"softwareName\"\n [(ngModel)]=\"model.selected\"\n (onSearch)=\"onInput.next($event)\"\n [required]=\"true\"\n >\n <c8y-li\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n *c8yFor=\"\n let software of softwaresResult;\n loadMore: 'auto';\n notFound: notFoundTemplate\n \"\n (click)=\"onSoftwareSelect(software)\"\n [active]=\"model.selected === software\"\n >\n <c8y-highlight\n [text]=\"software.name || '--'\"\n [pattern]=\"onInput | async\"\n ></c8y-highlight>\n </c8y-li>\n <ng-template #notFoundTemplate>\n <c8y-li\n class=\"bg-level-2 p-8\"\n *ngIf=\"(onInput | async)?.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`software`' | translate }}\"\n type=\"button\"\n >\n {{ 'Add new`software`' | translate }}\n </button>\n </c8y-li>\n </ng-template>\n </c8y-typeahead>\n </c8y-form-group>\n\n <c8y-form-group>\n <label\n for=\"softwareDescription\"\n translate\n >\n Description\n </label>\n <input\n class=\"form-control\"\n id=\"softwareDescription\"\n placeholder=\"{{ 'e.g. Cloud connectivity software' | translate }}\"\n name=\"description\"\n autocomplete=\"off\"\n [(ngModel)]=\"model.description\"\n [disabled]=\"model.selected?.id\"\n [required]=\"true\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label\n class=\"control-label\"\n for=\"softwareDeviceTypeFilter\"\n >\n {{ 'Device type filter' | translate }}\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'If the filter is set, the software will show up for installation only for devices of that type. If no filter is set, it will be available for all devices.'\n | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n <input\n class=\"form-control\"\n id=\"softwareDeviceTypeFilter\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n name=\"softwareDeviceTypeFilter\"\n [(ngModel)]=\"model.deviceType\"\n [disabled]=\"model.selected?.id\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label\n for=\"softwareType\"\n translate\n >\n Software type\n </label>\n <c8y-software-type\n name=\"softwareType\"\n [(ngModel)]=\"model.softwareType\"\n [disabled]=\"model.selected?.id\"\n ></c8y-software-type>\n </c8y-form-group>\n </div>\n\n <c8y-form-group>\n <label\n for=\"softwareVersion\"\n translate\n >\n Version\n </label>\n <input\n class=\"form-control\"\n id=\"softwareVersion\"\n placeholder=\"{{ 'e.g.' | translate }} 1.0.0\"\n name=\"version\"\n autocomplete=\"off\"\n [(ngModel)]=\"model.version\"\n [required]=\"true\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <div\n class=\"legend form-block m-t-40\"\n translate\n >\n Software file\n </div>\n <c8y-file-picker\n [maxAllowedFiles]=\"1\"\n [allowedUploadChoices]=\"['uploadBinary', 'uploadUrl', 'provided']\"\n (onFilesPicked)=\"onFile($event)\"\n [fileUrlPopover]=\"textForSoftwareUrlPopover\"\n ></c8y-file-picker>\n </c8y-form-group>\n </div>\n </div>\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 {{ 'Cancel' | translate }}\n </button>\n\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Add software' | translate }}\"\n type=\"submit\"\n [ngClass]=\"{ 'btn-pending': saving }\"\n [disabled]=\"\n !softwareForm.form.valid ||\n softwareForm.form.pristine ||\n saving ||\n (!model.binary?.url && !model.binary?.file)\n \"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.SOFTWARE.EVENTS.REPOSITORY\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.SOFTWARE.COMPONENTS.ADD_SOFTWARE_MODAL,\n result:\n softwarePreselected || model.selected?.id\n ? PRODUCT_EXPERIENCE.SOFTWARE.RESULTS.ADD_SOFTWARE_VERSION\n : PRODUCT_EXPERIENCE.SOFTWARE.RESULTS.ADD_SOFTWARE\n }\"\n >\n {{ 'Add software' | translate }}\n </button>\n </div>\n </form>\n</div>\n" }] }], ctorParameters: () => [{ type: i1.BsModalRef }, { type: i1$1.RepositoryService }, { type: i2.AlertService }], propDecorators: { form: [{ type: ViewChild, args: ['softwareForm', { static: false }] }], saved: [{ type: Output }] } }); class SoftwareListComponent { constructor(repositoryService, gridService, modalService, bsModalService, translateService, alertService, router, activatedRoute) { this.repositoryService = repositoryService; this.gridService = gridService; this.modalService = modalService; this.bsModalService = bsModalService; this.translateService = translateService; this.alertService = alertService; this.router = router; this.activatedRoute = activatedRoute; this.sizeRequestDone = false; this.refresh$ = new EventEmitter(); this.columns = [ new RepositoryItemNameGridColumn({ filterLabel: gettext('Filter software by name') }), new DescriptionGridColumn({ filterLabel: gettext('Filter software by description'), placeholder: gettext('Cloud connectivity software') }), new DeviceTypeGridColumn({ filterLabel: gettext('Filter software by device type') }), new TypeGridColumn({ header: gettext('Software type'), filterLabel: gettext('Filter by software type'), example: 'yum', path: 'softwareType', repositoryType: RepositoryType.SOFTWARE }), new VersionsGridColumn() ]; this.actionControls = []; this.pagination = { pageSize: 50, currentPage: 1 }; this.noResultsMessage = gettext('No results to display.'); this.noDataMessage = gettext('No software to display.'); this.noResultsSubtitle = gettext('Refine your search terms or check your spelling.'); this.noDataSubtitle = gettext('Add a new software by clicking below.'); this.serverSideDataCallback = this.onDataSourceModifier.bind(this); } ngOnInit() { this.actionControls.push({ type: BuiltInActionType.Edit, callback: this.editSoftware.bind(this) }); this.actionControls.push({ type: BuiltInActionType.Delete, callback: this.deleteSoftware.bind(this) }); } async onDataSourceModifier(dataSourceModifier) { const dataRequest = this.repositoryService.listRepositoryEntries(RepositoryType.SOFTWARE, { query: this.gridService.getQueryObj(dataSourceModifier.columns), skipDefaultOrder: true, params: { pageSize: dataSourceModifier.pagination.pageSize, currentPage: dataSourceModifier.pagination.currentPage } }); const filtererdSizeRequest = this.repositoryService .listRepositoryEntries(RepositoryType.SOFTWARE, { skipDefaultOrder: true, query: this.gridService.getQueryObj(dataSourceModifier.columns), params: { pageSize: 1 } }) .then(response => response?.paging?.totalPages); this.sizeRequest = this.repositoryService .listRepositoryEntries(RepositoryType.SOFTWARE, { skipDefaultOrder: true, params: { pageSize: 1 } }) .then(response => { this.sizeRequestDone = true; return response?.paging?.totalPages; }); const [dataResponse, size, filteredSize] = await Promise.all([ dataRequest, this.sizeRequest, filtererdSizeRequest ]); const { res, data, paging } = dataResponse; const serverSideDataResult = { res, data, paging, filteredSize, size }; return serverSideDataResult; } addSoftware() { const config = { class: 'modal-sm', ariaDescribedby: 'addSoftwareModalDescription', ariaLabelledBy: 'addSoftwareModalTitle', ignoreBackdropClick: true, keyboard: false }; const modalRef = this.bsModalService.show(AddSoftwareModalComponent, config); modalRef.content.saved.subscribe(savedSoftware => this.editSoftware(savedSoftware)); } editSoftware(software) { this.router.navigate([software.id], { relativeTo: this.activatedRoute }); } async deleteSoftware(software) { try { const title = gettext('Delete software'); const body = ` ${this.translateService.instant(gettext('You are about to delete software "{{ name }}" with all its versions.'), { name: software.name })} ${this.translateService.instant(gettext('This operation is irreversible.'))} ${this.translateService.instant(gettext('Do you want to proceed?'))} `; const labels = { ok: gettext('Delete') }; await this.modalService.confirm(title, body, Status.DANGER, labels, {}, { eventName: PRODUCT_EXPERIENCE_REPOSITORY_SHARED.SOFTWARE.EVENTS.REPOSITORY }); await this.repositoryService.delete(software); this.alertService.success(gettext('Software deleted.')); this.refresh$.next(); } catch (ex) { // only if not cancel from modal if (ex) { this.alertService.addServerFailure(ex); } } } trackByName(_index, column) { return column.name; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SoftwareListComponent, deps: [{ token: i1$1.RepositoryService }, { token: i2.DataGridService }, { token: i2.ModalService }, { token: i1.BsModalService }, { token: i4$1.TranslateService }, { token: i2.AlertService }, { token: i1$2.Router }, { token: i1$2.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SoftwareListComponent, isStandalone: true, selector: "c8y-software-list", ngImport: i0, template: "<c8y-title>\n {{ 'Software repository' | translate }}\n</c8y-title>\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=\"c8y-tools\"\n label=\"{{ 'Software 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 software' | translate }}\"\n type=\"button\"\n (click)=\"addSoftware()\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add software' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<c8y-help\n src=\"/docs/device-management-application/managing-device-data/#managing-software\"\n></c8y-help>\n\n<div class=\"content-fullpage border-top border-bottom\">\n <c8y-data-grid\n [title]=\"'Software' | 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' : 'c8y-tools'\"\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 <p *ngIf=\"stats?.size === 0\">\n <button\n class=\"btn btn-primary\"\n [title]=\"'Add software' | translate\"\n type=\"button\"\n (click)=\"addSoftware()\"\n >\n {{ 'Add software' | translate }}\n </button>\n </p>\n </c8y-ui-empty-state>\n <ng-container *ngFor=\"let column of columns; trackBy: trackByName\">\n <c8y-column [name]=\"column.name\"></c8y-column>\n </ng-container>\n </c8y-data-grid>\n</div>\n", dependencies: [{ kind: "component", type: TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { 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", "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: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: ColumnDirective, selector: "c8y-column", inputs: ["name"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SoftwareListComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-software-list', imports: [ TitleComponent, BreadcrumbComponent, BreadcrumbItemComponent, ActionBarItemComponent, IconDirective, HelpComponent, DataGridComponent, EmptyStateContextDirective, EmptyStateComponent, NgIf, NgFor, ColumnDirective, C8yTranslatePipe ], template: "<c8y-title>\n {{ 'Software repository' | translate }}\n</c8y-title>\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=\"c8y-tools\"\n label=\"{{ 'Software 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 software' | translate }}\"\n type=\"button\"\n (click)=\"addSoftware()\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add software' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<c8y-help\n src=\"/docs/device-management-application/managing-device-data/#managing-software\"\n></c8y-help>\n\n<div class=\"content-fullpage border-top border-bottom\">\n <c8y-data-grid\n [title]=\"'Software' | 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' : 'c8y-tools'\"\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 <p *ngIf=\"stats?.size === 0\">\n <button\n class=\"btn btn-primary\"\n [title]=\"'Add software' | translate\"\n type=\"button\"\n (click)=\"addSoftware()\"\n >\n {{ 'Add software' | translate }}\n </button>\n </p>\n </c8y-ui-empty-state>\n <ng-container *ngFor=\"let column of columns; trackBy: trackByName\">\n <c8y-column [name]=\"column.name\"></c8y-column>\n </ng-container>\n </c8y-data-grid>\n</div>\n" }] }], ctorParameters: () => [{ type: i1$1.RepositoryService }, { type: i2.DataGridService }, { type: i2.ModalService }, { type: i1.BsModalService }, { type: i4$1.TranslateService }, { type: i2.AlertService }, { type: i1$2.Router }, { type: i1$2.ActivatedRoute }] }); class SoftwareDetailsComponent { constructor(activatedRoute, inventoryService, repositoryService, alertService, translateService, modalService, bsModalService, gainsightService, router) { this.activatedRoute = activatedRoute; this.inventoryService = inventoryService; this.repositoryService = repositoryService; this.alertService = alertService; this.translateService = translateService; this.modalService = modalService; this.bsModalService = bsModalService; this.gainsightService = gainsightService; this.router = router; this.reload$ = new Subject(); this.reloading$ = new BehaviorSubject(false); this.isSoftwareTypeChanged = false; this.updateSoftware$ = new Subject(); this.softwareUpdated$ = new Subject(); this.baseVersionsUpdated$ = new Subject(); this.software$ = merge(this.activatedRoute.params.pipe(map(params => params.id), switchMap(id => from(this.inventoryService.detail(id).then(result => result.data)))), this.reload$.pipe(tap(() => this.reloading$.next(true)), switchMap(() => this.activatedRoute.params), map(params => params.id), switchMap(id => from(this.inventoryService.detail(id).then(result => result.data))), tap(() => this.reloading$.next(false))), this.softwareUpdated$).pipe(shareReplay(1)); this.baseVersions$ = merge(this.software$.pipe(distinctUntilKeyChanged('id')), this.baseVersionsUpdated$, this.reload$).pipe(switchMap(() => this.software$), switchMap(software => this.repositoryService.listBaseVersions(software)), shareReplay(1)); this.isLegacy$ = this.software$.pipe(map(software => this.repositoryService.isLegacyEntry(software)), shareReplay(1)); this.destroy$ = new Subject(); } ngOnInit() { this.updateSoftware$ .pipe(withLatestFrom(this.software$), switchMap(([softwarePartial, software]) => this.inventoryService.update({ id: software.id, ...softwarePartial })), map(({ data }) => data), tap(software => this.softwareUpdated$.next(software)), tap(() => this.alertService.success(gettext('Saved.'))), tap(() => this.gainsightService.triggerEvent(PRODUCT_EXPERIENCE_REPOSITORY_SHARED.SOFTWARE.EVENTS.REPOSITORY, { result: PRODUCT_EXPERIENCE_REPOSITORY_SHARED.SOFTWARE.RESULTS.EDIT_SOFTWARE })), takeUntil(this.destroy$)) .subscribe(); this.software$.subscribe(software => { this.softwareTypeObject = software; }); } getBinaryName$(binaryUrl) { return this.repositoryService.getBinaryName$(binaryUrl); } addBaseVersion() { this.software$ .pipe(take(1), switchMap(software => { const initialState = { model: { selected: software, description: software.description } }; const config = { class: 'modal-sm', ariaDescribedby: 'addSoftwareModalDescription', ariaLabelledBy: 'addSoftwareModalTitle', ignoreBackdropClick: true, keyboard: false, initialState }; const modalRef = this.bsModalService.show(AddSoftwareModalComponent, config); return modalRef.content.saved; })) .subscribe(() => this.baseVersionsUpdated$.next()); } async deleteBaseVersion(baseVersion) { try { const title = gettext('Delete software'); const body = ` ${this.translateService.instant(gettext('You are about to delete software {{ version }}.'), { version: baseVersion.c8y_Software.version })} ${this.translateService.instant(gettext('This operation is irreversible.'))} ${this.translateService.instant(gettext('Do you want to proceed?'))} `; const labels = { ok: gettext('Delete') }; await this.modalService.confirm(title, body, Status.DANGER, labels); const isLastVersion = await this.baseVersions$ .pipe(map(versions => versions?.data?.length === 1), take(1)) .toPromise(); if (isLastVersion) { await this.repositoryService.delete(this.softwareTypeObject); this.router.navigateByUrl('/software'); } else { await this.repositoryService.delete(baseVersion); this.baseVersionsUpdated$.next(); } this.alertService.success(gettext('Software deleted.')); } catch (ex) { // only if not cancel from modal if (ex) { this.alertService.addServerFailure(ex); } } } onSelectSoftwareType(software) { this.isSoftwareTypeChanged = !(this.softwareTypeObject?.softwareType === software?.softwareType); this.softwareTypeObject = software; } ngOnDestroy() { this.destroy$.next(true); this.destroy$.unsubscribe(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SoftwareDetailsComponent, deps: [{ token: i1$2.ActivatedRoute }, { token: i2$1.InventoryService }, { token: i1$1.RepositoryService }, { token: i2.AlertService }, { token: i4$1.TranslateService }, { token: i2.ModalService }, { token: i1.BsModalService }, { token: i2.GainsightService }, { token: i1$2.Router }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SoftwareDetailsComponent, isStandalone: true, selector: "c8y-software-details", viewQueries: [{ propertyName: "softwareType", first: true, predicate: SoftwareTypeComponent, descendants: true, static: true }], ngImport: i0, template: "<c8y-title>\n {{ (software$ | async)?.name }}\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=\"c8y-tools\"\n path=\"#/software\"\n label=\"{{ 'Software repository' | translate }}\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n icon=\"c8y-tools\"\n label=\"{{ (software$ | async)?.name }}\"\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 software' | translate }}\"\n type=\"button\"\n (click)=\"addBaseVersion()\"\n *ngIf=\"!(isLegacy$ | async)\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add software' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Reload' | translate }}\"\n type=\"button\"\n (click)=\"reload$.next()\"\n >\n <i\n c8yIcon=\"refresh\"\n [ngClass]=\"{ 'icon-spin': reloading$ | async }\"\n ></i>\n {{ 'Reload' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<div class=\"row\">\n <div class=\"col-lg-12 col-lg-max\">\n <div class=\"card card--fullpage\">\n <div class=\"card-block bg-level-1 flex-no-shrink p-t-24 p-b-24 overflow-visible\">\n <div class=\"content-flex-70\">\n <div class=\"text-center\">\n <i class=\"c8y-icon-duocolor icon-48 c8y-icon c8y-icon-tools\"></i>\n <p>\n <small class=\"label label-info\">Software</small>\n </p>\n </div>\n <div class=\"flex-grow col-10\">\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label class=\"control-label\">\n {{ 'Name' | translate }}\n </label>\n <div class=\"input-group input-group-editable\">\n <input\n class=\"form-control\"\n [ngStyle]=\"{ 'width.ch': (software$ | async)?.name?.length + 2 || 31 }\"\n placeholder=\"{{ 'e.g. My software' | translate }}\"\n type=\"text\"\n required\n #nameInput\n [ngModel]=\"(software$ | async)?.name\"\n #nameModel=\"ngModel\"\n />\n <span></span>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Save' | translate }}\"\n type=\"button\"\n (click)=\"updateSoftware$.next({ name: nameInput.value }); nameModel.reset()\"\n [disabled]=\"nameInput.value.length === 0\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </div>\n </c8y-form-group>\n </div>\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label class=\"control-label\">\n {{ 'Description' | translate }}\n </label>\n <div class=\"input-group input-group-editable\">\n <input\n class=\"form-control\"\n [ngStyle]=\"{ 'width.ch': (software$ | async)?.description?.length + 2 || 31 }\"\n placeholder=\"{{ 'e.g. Cloud connectivity software' | translate }}\"\n type=\"text\"\n #descriptionInput\n [ngModel]=\"(software$ | async)?.description\"\n #descriptionModel=\"ngModel\"\n />\n <span></span>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Save' | translate }}\"\n type=\"button\"\n (click)=\"\n updateSoftware$.next({ description: descriptionInput.value });\n descriptionModel.reset()\n \"\n